Find this useful? Enter your email to receive occasional updates for securing PHP code.

Signing you up...

Thank you for signing up!

PHP Decode

<?php declare(strict_types=1); /** * CakePHP(tm) : Rapid Development Framework (https://..

Decoded Output download

<?php
declare(strict_types=1);

/**
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link          https://cakephp.org CakePHP(tm) Project
 * @since         2.0.0
 * @license       https://opensource.org/licenses/mit-license.php MIT License
 */
namespace Cake\Test\TestCase\Mailer\Transport;

use Cake\Core\Exception\CakeException;
use Cake\Error\Debugger;
use Cake\Mailer\Message;
use Cake\Mailer\Transport\SmtpTransport;
use Cake\Network\Exception\SocketException;
use Cake\Network\Socket;
use Cake\TestSuite\TestCase;
use TestApp\Mailer\Transport\SmtpTestTransport;

/**
 * Test case
 */
class SmtpTransportTest extends TestCase
{
    /**
     * @var \TestApp\Mailer\Transport\SmtpTestTransport
     */
    protected $SmtpTransport;

    /**
     * @var \Cake\Network\Socket&\PHPUnit\Framework\MockObject\MockObject
     */
    protected $socket;

    /**
     * @var array<string, string>
     */
    protected $credentials = [
        'username' => 'mark',
        'password' => 'story',
    ];

    /**
     * @var string
     */
    protected $credentialsEncoded;

    /**
     * Setup
     */
    public function setUp(): void
    {
        parent::setUp();
        $this->socket = $this->getMockBuilder(Socket::class)
            ->onlyMethods(['read', 'write', 'connect', 'disconnect', 'enableCrypto', 'isConnected'])
            ->getMock();

        $this->SmtpTransport = new SmtpTestTransport();
        $this->SmtpTransport->setSocket($this->socket);
        $this->SmtpTransport->setConfig(['client' => 'localhost']);

        $this->credentialsEncoded = base64_encode(chr(0) . 'mark' . chr(0) . 'story');
    }

    /**
     * testConnectEhlo method
     */
    public function testConnectEhlo(): void
    {
        $this->socket->expects($this->any())->method('connect')->willReturn(true);
        $this->socket->expects($this->any())
            ->method('read')
            ->willReturn("220 Welcome message\r\n", "250 Accepted\r\n");
        $this->socket->expects($this->once())->method('write')->with("EHLO localhost\r\n");
        $this->SmtpTransport->connect();
    }

    /**
     * testConnectEhloTls method
     */
    public function testConnectEhloTls(): void
    {
        $this->SmtpTransport->setConfig(['tls' => true]);
        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->exactly(4))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n",
                "220 Server ready\r\n",
                "250 Accepted\r\n"
            );
        $this->socket->expects($this->exactly(3))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["STARTTLS\r\n"],
                    ["EHLO localhost\r\n"]
                )
            );
        $this->socket->expects($this->once())->method('enableCrypto')
            ->with('tls');

        $this->SmtpTransport->connect();
    }

    /**
     * testConnectEhloTlsOnNonTlsServer method
     */
    public function testConnectEhloTlsOnNonTlsServer(): void
    {
        $this->SmtpTransport->setConfig(['tls' => true]);
        $this->socket->expects($this->any())->method('connect')->willReturn(true);

        $this->socket->expects($this->exactly(3))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n",
                "500 5.3.3 Unrecognized command\r\n"
            );
        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["STARTTLS\r\n"]
                )
            );

        $e = null;
        try {
            $this->SmtpTransport->connect();
        } catch (SocketException $e) {
        }

        $this->assertNotNull($e);
        $this->assertSame('SMTP server did not accept the connection or trying to connect to non TLS SMTP server using TLS.', $e->getMessage());
        $this->assertInstanceOf(SocketException::class, $e->getPrevious());
        $this->assertStringContainsString('500 5.3.3 Unrecognized command', $e->getPrevious()->getMessage());
    }

    /**
     * testConnectEhloNoTlsOnRequiredTlsServer method
     */
    public function testConnectEhloNoTlsOnRequiredTlsServer(): void
    {
        $this->expectException(SocketException::class);
        $this->expectExceptionMessage('SMTP authentication method not allowed, check if SMTP server requires TLS.');
        $this->SmtpTransport->setConfig(['tls' => false] + $this->credentials);

        $this->socket->expects($this->exactly(4))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n",
                "504 5.7.4 Unrecognized Authentication Type\r\n",
                "504 5.7.4 Unrecognized authentication type\r\n"
            );
        $this->socket->expects($this->exactly(3))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["AUTH PLAIN {$this->credentialsEncoded}\r\n"],
                    ["AUTH LOGIN\r\n"]
                )
            );

        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->SmtpTransport->connect();
    }

    public function testConnectEhloWithAuthPlain(): void
    {
        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->exactly(3))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n250 AUTH PLAIN LOGIN\r\n",
                "235 OK\r\n",
            );
        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["AUTH PLAIN {$this->credentialsEncoded}\r\n"]
                )
            );

        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->connect();
        $this->assertEquals($this->SmtpTransport->getAuthType(), 'PLAIN');
    }

    public function testConnectEhloWithAuthLogin(): void
    {
        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->any())
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n250 AUTH LOGIN\r\n",
                "334 Login\r\n",
                "334 Pass\r\n",
                "235 OK\r\n"
            );
        $this->socket->expects($this->exactly(4))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["AUTH LOGIN\r\n"],
                    ["bWFyaw==\r\n"],
                    ["c3Rvcnk=\r\n"]
                )
            );

        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->connect();
        $this->assertEquals($this->SmtpTransport->getAuthType(), 'LOGIN');
    }

    /**
     * testConnectHelo method
     */
    public function testConnectHelo(): void
    {
        $this->socket->expects($this->exactly(3))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "200 Not Accepted\r\n",
                "250 Accepted\r\n"
            );
        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["HELO localhost\r\n"]
                )
            );

        $this->socket->expects($this->once())->method('connect')->willReturn(true);
        $this->SmtpTransport->connect();
    }

    /**
     * testConnectFail method
     */
    public function testConnectFail(): void
    {
        $this->socket->expects($this->exactly(3))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "200 Not Accepted\r\n",
                "200 Not Accepted\r\n"
            );
        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["HELO localhost\r\n"]
                )
            );
        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $e = null;
        try {
            $this->SmtpTransport->connect();
        } catch (SocketException $e) {
        }

        $this->assertNotNull($e);
        $this->assertSame('SMTP server did not accept the connection.', $e->getMessage());
        $this->assertInstanceOf(SocketException::class, $e->getPrevious());
        $this->assertStringContainsString('200 Not Accepted', $e->getPrevious()->getMessage());
    }

    /**
     * Test that when "authType" is specified that's that one used instead of the
     * 1st one supported by the server
     *
     * @return void
     */
    public function testAuthTypeSet(): void
    {
        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n250 AUTH PLAIN LOGIN\r\n",
            );
        $this->socket->expects($this->exactly(1))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                )
            );

        $this->SmtpTransport->setConfig(['authType' => SmtpTransport::AUTH_XOAUTH2]);
        $this->SmtpTransport->connect();
        $this->assertEquals($this->SmtpTransport->getAuthType(), SmtpTransport::AUTH_XOAUTH2);
    }

    public function testExceptionInvalidAuthType(): void
    {
        $this->expectException(CakeException::class);
        $this->expectExceptionMessage('Unsupported auth type. Available types are: PLAIN, LOGIN, XOAUTH2');

        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n250 AUTH PLAIN LOGIN\r\n",
            );
        $this->socket->expects($this->exactly(1))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                )
            );

        $this->SmtpTransport->setConfig(['authType' => 'invalid']);
        $this->SmtpTransport->connect();
    }

    public function testAuthTypeUnsupported(): void
    {
        $this->expectException(CakeException::class);
        $this->expectExceptionMessage('Unsupported auth type: CRAM-MD5');

        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n250 AUTH CRAM-MD5\r\n",
            );
        $this->socket->expects($this->exactly(1))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                )
            );

        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->connect();
    }

    public function testAuthTypeParsingIsSkippedIfNoCredentialsProvided(): void
    {
        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n250 AUTH CRAM-MD5\r\n",
            );
        $this->socket->expects($this->exactly(1))
            ->method('write')
            ->with(...self::withConsecutive(
                ["EHLO localhost\r\n"],
            ));

        $this->SmtpTransport->connect();
        $this->assertNull($this->SmtpTransport->getAuthType());
    }

    public function testAuthPlain(): void
    {
        $this->socket->expects($this->once())->method('write')->with("AUTH PLAIN {$this->credentialsEncoded}\r\n");
        $this->socket->expects($this->once())->method('read')->willReturn("235 OK\r\n");
        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->auth();
    }

    /**
     * testAuth method
     */
    public function testAuthLogin(): void
    {
        $this->socket->expects($this->exactly(4))
            ->method('read')
            ->willReturn(
                "504 5.7.4 Unrecognized Authentication Type\r\n",
                "334 Login\r\n",
                "334 Pass\r\n",
                "235 OK\r\n"
            );
        $this->socket->expects($this->exactly(4))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["AUTH PLAIN {$this->credentialsEncoded}\r\n"],
                    ["AUTH LOGIN\r\n"],
                    ["bWFyaw==\r\n"],
                    ["c3Rvcnk=\r\n"]
                )
            );

        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->auth();
    }

    /**
     * testAuth method
     */
    public function testAuthXoauth2(): void
    {
        $authString = base64_encode(sprintf(
            "user=%s\1auth=Bearer %s\1\1",
            $this->credentials['username'],
            $this->credentials['password']
        ));

        $this->socket->expects($this->exactly(1))
            ->method('read')
            ->willReturn(
                "235 OK\r\n"
            );
        $this->socket->expects($this->exactly(1))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["AUTH XOAUTH2 {$authString}\r\n"],
                )
            );

        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->setAuthType('XOAUTH2');
        $this->SmtpTransport->auth();
    }

    /**
     * testAuthNotRecognized method
     */
    public function testAuthNotRecognized(): void
    {
        $this->expectException(SocketException::class);
        $this->expectExceptionMessage('AUTH command not recognized or not implemented, SMTP server may not require authentication.');

        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "504 5.7.4 Unrecognized Authentication Type\r\n",
                "500 5.3.3 Unrecognized command\r\n"
            );
        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["AUTH PLAIN {$this->credentialsEncoded}\r\n"],
                    ["AUTH LOGIN\r\n"]
                )
            );

        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->auth();
    }

    /**
     * testAuthNotImplemented method
     */
    public function testAuthNotImplemented(): void
    {
        $this->expectException(SocketException::class);
        $this->expectExceptionMessage('AUTH command not recognized or not implemented, SMTP server may not require authentication.');

        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "504 5.7.4 Unrecognized Authentication Type\r\n",
                "502 5.3.3 Command not implemented\r\n"
            );
        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["AUTH PLAIN {$this->credentialsEncoded}\r\n"],
                    ["AUTH LOGIN\r\n"]
                )
            );
        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->auth();
    }

    /**
     * testAuthBadSequence method
     */
    public function testAuthBadSequence(): void
    {
        $this->expectException(SocketException::class);
        $this->expectExceptionMessage('SMTP Error: 503 5.5.1 Already authenticated');

        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "504 5.7.4 Unrecognized Authentication Type\r\n",
                "503 5.5.1 Already authenticated\r\n"
            );
        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["AUTH PLAIN {$this->credentialsEncoded}\r\n"],
                    ["AUTH LOGIN\r\n"]
                )
            );
        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->auth();
    }

    /**
     * testAuthBadUsername method
     */
    public function testAuthBadUsername(): void
    {
        $this->socket->expects($this->exactly(3))
            ->method('read')
            ->willReturn(
                "504 5.7.4 Unrecognized Authentication Type\r\n",
                "334 Login\r\n",
                "535 5.7.8 Authentication failed\r\n"
            );
        $this->socket->expects($this->exactly(3))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["AUTH PLAIN {$this->credentialsEncoded}\r\n"],
                    ["AUTH LOGIN\r\n"],
                    ["bWFyaw==\r\n"]
                )
            );

        $this->SmtpTransport->setConfig($this->credentials);

        $e = null;
        try {
            $this->SmtpTransport->auth();
        } catch (SocketException $e) {
        }

        $this->assertNotNull($e);
        $this->assertSame('SMTP server did not accept the username.', $e->getMessage());
        $this->assertInstanceOf(SocketException::class, $e->getPrevious());
        $this->assertStringContainsString('535 5.7.8 Authentication failed', $e->getPrevious()->getMessage());
    }

    /**
     * testAuthBadPassword method
     */
    public function testAuthBadPassword(): void
    {
        $this->socket->expects($this->exactly(4))
            ->method('read')
            ->willReturn(
                "504 5.7.4 Unrecognized Authentication Type\r\n",
                "334 Login\r\n",
                "334 Pass\r\n",
                "535 5.7.8 Authentication failed\r\n"
            );
        $this->socket->expects($this->exactly(4))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["AUTH PLAIN {$this->credentialsEncoded}\r\n"],
                    ["AUTH LOGIN\r\n"],
                    ["bWFyaw==\r\n"],
                    ["c3Rvcnk=\r\n"]
                )
            );

        $this->SmtpTransport->setConfig($this->credentials);

        $e = null;
        try {
            $this->SmtpTransport->auth();
        } catch (SocketException $e) {
        }

        $this->assertNotNull($e);
        $this->assertSame('SMTP server did not accept the password.', $e->getMessage());
        $this->assertInstanceOf(SocketException::class, $e->getPrevious());
        $this->assertStringContainsString('535 5.7.8 Authentication failed', $e->getPrevious()->getMessage());
    }

    /**
     * testRcpt method
     */
    public function testRcpt(): void
    {
        $message = new Message();
        $message->setFrom('[email protected]', 'CakePHP Test');
        $message->setTo('[email protected]', 'CakePHP');
        $message->setBcc('[email protected]');
        $message->setCc(['[email protected]' => 'Mark Story', '[email protected]' => 'Juan Basso']);

        $this->socket->expects($this->any())->method('read')->willReturn("250 OK\r\n");

        $this->socket->expects($this->exactly(5))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["MAIL FROM:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"]
                )
            );

        $this->SmtpTransport->sendRcpt($message);
    }

    /**
     * testRcptWithReturnPath method
     */
    public function testRcptWithReturnPath(): void
    {
        $message = new Message();
        $message->setFrom('[email protected]', 'CakePHP Test');
        $message->setTo('[email protected]', 'CakePHP');
        $message->setReturnPath('[email protected]', 'CakePHP Return');

        $this->socket->expects($this->exactly(2))->method('read')->willReturn("250 OK\r\n");

        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["MAIL FROM:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"]
                )
            );
        $this->SmtpTransport->sendRcpt($message);
    }

    /**
     * testSendData method
     */
    public function testSendData(): void
    {
        $message = new Message();
        $message->setFrom('[email protected]', 'CakePHP Test');
        $message->setReturnPath('[email protected]', 'CakePHP Return');
        $message->setTo('[email protected]', 'CakePHP');
        $message->setReplyTo(['[email protected]' => 'Mark Story', '[email protected]' => 'Juan Basso']);
        $message->setCc(['[email protected]' => 'Mark Story', '[email protected]' => 'Juan Basso']);
        $message->setBcc('[email protected]');
        $message->setMessageId('<4d9946cf-0a44-4907-88fe-1d0ccbdd56cb@localhost>');
        $message->setSubject('Testing SMTP');
        $date = date(DATE_RFC2822);
        $message->setHeaders(['Date' => $date]);
        $message->setBody(['text' => "First Line\nSecond Line\n.Third Line"]);

        $data = "From: CakePHP Test <[email protected]>\r\n";
        $data .= "Reply-To: Mark Story <[email protected]>, Juan Basso <[email protected]>\r\n";
        $data .= "Return-Path: CakePHP Return <[email protected]>\r\n";
        $data .= "To: CakePHP <[email protected]>\r\n";
        $data .= "Cc: Mark Story <[email protected]>, Juan Basso <[email protected]>\r\n";
        $data .= 'Date: ' . $date . "\r\n";
        $data .= "Message-ID: <4d9946cf-0a44-4907-88fe-1d0ccbdd56cb@localhost>\r\n";
        $data .= "Subject: Testing SMTP\r\n";
        $data .= "MIME-Version: 1.0\r\n";
        $data .= "Content-Type: text/plain; charset=UTF-8\r\n";
        $data .= "Content-Transfer-Encoding: 8bit\r\n";
        $data .= "\r\n";
        $data .= "First Line\r\n";
        $data .= "Second Line\r\n";
        $data .= "..Third Line\r\n\r\n"; // RFC5321 4.5.2.Transparency
        $data .= "\r\n";
        $data .= "\r\n\r\n.\r\n";

        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "354 OK\r\n",
                "250 OK\r\n"
            );

        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["DATA\r\n"],
                    [$data]
                )
            );

        $this->SmtpTransport->sendData($message);
    }

    /**
     * testQuit method
     */
    public function testQuit(): void
    {
        $this->socket->expects($this->once())->method('write')->with("QUIT\r\n");
        $this->socket->expects($this->once())->method('isConnected')->willReturn(true);

        $this->SmtpTransport->disconnect();
    }

    /**
     * Tests using empty client name
     */
    public function testEmptyClientName(): void
    {
        $this->socket->expects($this->any())->method('connect')->willReturn(true);
        $this->socket->expects($this->any())
           ->method('read')
           ->willReturn("220 Welcome message\r\n", "250 Accepted\r\n");

        $this->SmtpTransport->setConfig(['client' => '']);

        $this->expectException(SocketException::class);
        $this->expectExceptionMessage('Cannot use an empty client name');
        $this->SmtpTransport->connect();
    }

    /**
     * testGetLastResponse method
     */
    public function testGetLastResponse(): void
    {
        $this->assertEmpty($this->SmtpTransport->getLastResponse());

        $this->socket->expects($this->any())->method('connect')->willReturn(true);
        $this->socket->expects($this->any())
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250-PIPELINING\r\n",
                "250-SIZE 102400000\r\n",
                "250-VRFY\r\n",
                "250-ETRN\r\n",
                "250-STARTTLS\r\n",
                "250-AUTH PLAIN LOGIN\r\n",
                "250-AUTH=PLAIN LOGIN\r\n",
                "250-ENHANCEDSTATUSCODES\r\n",
                "250-8BITMIME\r\n",
                "250 DSN\r\n"
            );
        $this->socket->expects($this->once())->method('write')->with("EHLO localhost\r\n");
        $this->SmtpTransport->connect();

        $expected = [
            ['code' => '250', 'message' => 'PIPELINING'],
            ['code' => '250', 'message' => 'SIZE 102400000'],
            ['code' => '250', 'message' => 'VRFY'],
            ['code' => '250', 'message' => 'ETRN'],
            ['code' => '250', 'message' => 'STARTTLS'],
            ['code' => '250', 'message' => 'AUTH PLAIN LOGIN'],
            ['code' => '250', 'message' => 'AUTH=PLAIN LOGIN'],
            ['code' => '250', 'message' => 'ENHANCEDSTATUSCODES'],
            ['code' => '250', 'message' => '8BITMIME'],
            ['code' => '250', 'message' => 'DSN'],
        ];
        $result = $this->SmtpTransport->getLastResponse();
        $this->assertEquals($expected, $result);
    }

    /**
     * Test getLastResponse() with multiple operations
     */
    public function testGetLastResponseMultipleOperations(): void
    {
        $message = new Message();
        $message->setFrom('[email protected]', 'CakePHP Test');
        $message->setTo('[email protected]', 'CakePHP');

        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["MAIL FROM:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"]
                )
            );
        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn("250 OK\r\n");

        $this->SmtpTransport->sendRcpt($message);

        $expected = [
            ['code' => '250', 'message' => 'OK'],
        ];
        $result = $this->SmtpTransport->getLastResponse();
        $this->assertEquals($expected, $result);
    }

    /**
     * testBufferResponseLines method
     */
    public function testBufferResponseLines(): void
    {
        $responseLines = [
            '123',
            "456\tFOO",
            'FOOBAR',
            '250-PIPELINING',
            '250-ENHANCEDSTATUSCODES',
            '250-8BITMIME',
            '250 DSN',
        ];
        $this->SmtpTransport->bufferResponseLines($responseLines);

        $expected = [
            ['code' => '123', 'message' => null],
            ['code' => '250', 'message' => 'PIPELINING'],
            ['code' => '250', 'message' => 'ENHANCEDSTATUSCODES'],
            ['code' => '250', 'message' => '8BITMIME'],
            ['code' => '250', 'message' => 'DSN'],
        ];
        $result = $this->SmtpTransport->getLastResponse();
        $this->assertEquals($expected, $result);
    }

    /**
     * testExplicitConnectAlreadyConnected method
     */
    public function testExplicitConnectAlreadyConnected(): void
    {
        $this->socket->expects($this->never())->method('connect');
        $this->socket->expects($this->once())->method('isConnected')->willReturn(true);

        $this->SmtpTransport->connect();
    }

    /**
     * testConnected method
     */
    public function testConnected(): void
    {
        $this->socket->expects($this->exactly(2))
            ->method('isConnected')
            ->willReturn(
                true,
                false
            );

        $this->assertTrue($this->SmtpTransport->connected());
        $this->assertFalse($this->SmtpTransport->connected());
    }

    /**
     * testAutoDisconnect method
     */
    public function testAutoDisconnect(): void
    {
        $this->socket->expects($this->once())->method('write')->with("QUIT\r\n");
        $this->socket->expects($this->once())->method('disconnect');
        $this->socket->expects($this->once())->method('isConnected')->willReturn(true);
        unset($this->SmtpTransport);
    }

    /**
     * testExplicitDisconnect method
     */
    public function testExplicitDisconnect(): void
    {
        $this->socket->expects($this->once())->method('write')->with("QUIT\r\n");
        $this->socket->expects($this->once())->method('disconnect');
        $this->socket->expects($this->once())->method('isConnected')->willReturn(true);
        $this->SmtpTransport->disconnect();
    }

    /**
     * testExplicitDisconnectNotConnected method
     */
    public function testExplicitDisconnectNotConnected(): void
    {
        $callback = function ($arg): void {
            $this->assertNotEquals("QUIT\r\n", $arg);
        };
        $this->socket->expects($this->any())->method('write')->willReturnCallback($callback);
        $this->socket->expects($this->never())->method('disconnect');
        $this->SmtpTransport->disconnect();
    }

    /**
     * testKeepAlive method
     */
    public function testKeepAlive(): void
    {
        $this->SmtpTransport->setConfig(['keepAlive' => true]);

        /** @var \Cake\Mailer\Message $message */
        $message = $this->getMockBuilder(Message::class)
            ->onlyMethods(['getBody'])
            ->getMock();
        $message->setFrom('[email protected]', 'CakePHP Test');
        $message->setTo('[email protected]', 'CakePHP');
        $message->expects($this->exactly(2))->method('getBody')->willReturn(['First Line']);

        $callback = function ($arg) {
            $this->assertNotEquals("QUIT\r\n", $arg);

            return 1;
        };
        $this->socket->expects($this->any())->method('write')->willReturnCallback($callback);
        $this->socket->expects($this->never())->method('disconnect');

        $this->socket->expects($this->exactly(11))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 OK\r\n",
                "250 OK\r\n",
                "250 OK\r\n",
                "354 OK\r\n",
                "250 OK\r\n",
                "250 OK\r\n",
                // Second email
                "250 OK\r\n",
                "250 OK\r\n",
                "354 OK\r\n",
                "250 OK\r\n"
            );
        $this->socket->expects($this->exactly(10))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["MAIL FROM:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"],
                    ["DATA\r\n"],
                    [$this->stringContains('First Line')],
                    ["RSET\r\n"],
                    // Second email
                    ["MAIL FROM:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"],
                    ["DATA\r\n"],
                    [$this->stringContains('First Line')]
                )
            );

        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->SmtpTransport->send($message);
        $this->socket->expects($this->once())->method('isConnected')->willReturn(true);
        $this->SmtpTransport->send($message);
    }

    /**
     * testSendDefaults method
     */
    public function testSendDefaults(): void
    {
        /** @var \Cake\Mailer\Message $message */
        $message = $this->getMockBuilder(Message::class)
            ->onlyMethods(['getBody'])
            ->getMock();
        $message->setFrom('[email protected]', 'CakePHP Test');
        $message->setTo('[email protected]', 'CakePHP');
        $message->expects($this->once())->method('getBody')->willReturn(['First Line']);

        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->atLeast(6))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 OK\r\n",
                "250 OK\r\n",
                "250 OK\r\n",
                "354 OK\r\n",
                "250 OK\r\n"
            );

        $this->socket->expects($this->atLeast(6))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["MAIL FROM:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"],
                    ["DATA\r\n"],
                    [$this->stringContains('First Line')],
                    ["QUIT\r\n"]
                )
            );

        $this->socket->expects($this->once())->method('disconnect');

        $this->SmtpTransport->send($message);
    }

    /**
     * testSendDefaults method
     */
    public function testSendMessageTooBigOnWindows(): void
    {
        /** @var \Cake\Mailer\Message $message */
        $message = $this->getMockBuilder(Message::class)
            ->onlyMethods(['getBody'])
            ->getMock();
        $message->setFrom('[email protected]', 'CakePHP Test');
        $message->setTo('[email protected]', 'CakePHP');
        $message->expects($this->once())->method('getBody')->willReturn(['First Line']);

        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->atLeast(6))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 OK\r\n",
                "250 OK\r\n",
                "250 OK\r\n",
                "354 OK\r\n",
                'Message size too large'
            );

        $this->socket->expects($this->exactly(5))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["MAIL FROM:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"],
                    ["DATA\r\n"],
                    [$this->stringContains('First Line')]
                )
            );

        $this->expectException(SocketException::class);
        $this->expectExceptionMessage('Message size too large');

        $this->SmtpTransport->send($message);
    }

    /**
     * Ensure that unserialized transports have no connection.
     */
    public function testSerializeCleanupSocket(): void
    {
        $this->socket->expects($this->once())->method('connect')->willReturn(true);
        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 OK\r\n"
            );
        $this->socket->expects($this->once())
            ->method('write')
            ->with("EHLO localhost\r\n");

        $smtpTransport = new SmtpTestTransport();
        $smtpTransport->setSocket($this->socket);
        $smtpTransport->connect();

        $result = unserialize(serialize($smtpTransport));
        $this->assertStringContainsString('[protected] _socket => [uninitialized]', Debugger::exportVar($result));
        $this->assertFalse($result->connected());
    }
}
 ?>

Did this file decode correctly?

Original Code

<?php
declare(strict_types=1);

/**
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link          https://cakephp.org CakePHP(tm) Project
 * @since         2.0.0
 * @license       https://opensource.org/licenses/mit-license.php MIT License
 */
namespace Cake\Test\TestCase\Mailer\Transport;

use Cake\Core\Exception\CakeException;
use Cake\Error\Debugger;
use Cake\Mailer\Message;
use Cake\Mailer\Transport\SmtpTransport;
use Cake\Network\Exception\SocketException;
use Cake\Network\Socket;
use Cake\TestSuite\TestCase;
use TestApp\Mailer\Transport\SmtpTestTransport;

/**
 * Test case
 */
class SmtpTransportTest extends TestCase
{
    /**
     * @var \TestApp\Mailer\Transport\SmtpTestTransport
     */
    protected $SmtpTransport;

    /**
     * @var \Cake\Network\Socket&\PHPUnit\Framework\MockObject\MockObject
     */
    protected $socket;

    /**
     * @var array<string, string>
     */
    protected $credentials = [
        'username' => 'mark',
        'password' => 'story',
    ];

    /**
     * @var string
     */
    protected $credentialsEncoded;

    /**
     * Setup
     */
    public function setUp(): void
    {
        parent::setUp();
        $this->socket = $this->getMockBuilder(Socket::class)
            ->onlyMethods(['read', 'write', 'connect', 'disconnect', 'enableCrypto', 'isConnected'])
            ->getMock();

        $this->SmtpTransport = new SmtpTestTransport();
        $this->SmtpTransport->setSocket($this->socket);
        $this->SmtpTransport->setConfig(['client' => 'localhost']);

        $this->credentialsEncoded = base64_encode(chr(0) . 'mark' . chr(0) . 'story');
    }

    /**
     * testConnectEhlo method
     */
    public function testConnectEhlo(): void
    {
        $this->socket->expects($this->any())->method('connect')->willReturn(true);
        $this->socket->expects($this->any())
            ->method('read')
            ->willReturn("220 Welcome message\r\n", "250 Accepted\r\n");
        $this->socket->expects($this->once())->method('write')->with("EHLO localhost\r\n");
        $this->SmtpTransport->connect();
    }

    /**
     * testConnectEhloTls method
     */
    public function testConnectEhloTls(): void
    {
        $this->SmtpTransport->setConfig(['tls' => true]);
        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->exactly(4))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n",
                "220 Server ready\r\n",
                "250 Accepted\r\n"
            );
        $this->socket->expects($this->exactly(3))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["STARTTLS\r\n"],
                    ["EHLO localhost\r\n"]
                )
            );
        $this->socket->expects($this->once())->method('enableCrypto')
            ->with('tls');

        $this->SmtpTransport->connect();
    }

    /**
     * testConnectEhloTlsOnNonTlsServer method
     */
    public function testConnectEhloTlsOnNonTlsServer(): void
    {
        $this->SmtpTransport->setConfig(['tls' => true]);
        $this->socket->expects($this->any())->method('connect')->willReturn(true);

        $this->socket->expects($this->exactly(3))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n",
                "500 5.3.3 Unrecognized command\r\n"
            );
        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["STARTTLS\r\n"]
                )
            );

        $e = null;
        try {
            $this->SmtpTransport->connect();
        } catch (SocketException $e) {
        }

        $this->assertNotNull($e);
        $this->assertSame('SMTP server did not accept the connection or trying to connect to non TLS SMTP server using TLS.', $e->getMessage());
        $this->assertInstanceOf(SocketException::class, $e->getPrevious());
        $this->assertStringContainsString('500 5.3.3 Unrecognized command', $e->getPrevious()->getMessage());
    }

    /**
     * testConnectEhloNoTlsOnRequiredTlsServer method
     */
    public function testConnectEhloNoTlsOnRequiredTlsServer(): void
    {
        $this->expectException(SocketException::class);
        $this->expectExceptionMessage('SMTP authentication method not allowed, check if SMTP server requires TLS.');
        $this->SmtpTransport->setConfig(['tls' => false] + $this->credentials);

        $this->socket->expects($this->exactly(4))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n",
                "504 5.7.4 Unrecognized Authentication Type\r\n",
                "504 5.7.4 Unrecognized authentication type\r\n"
            );
        $this->socket->expects($this->exactly(3))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["AUTH PLAIN {$this->credentialsEncoded}\r\n"],
                    ["AUTH LOGIN\r\n"]
                )
            );

        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->SmtpTransport->connect();
    }

    public function testConnectEhloWithAuthPlain(): void
    {
        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->exactly(3))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n250 AUTH PLAIN LOGIN\r\n",
                "235 OK\r\n",
            );
        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["AUTH PLAIN {$this->credentialsEncoded}\r\n"]
                )
            );

        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->connect();
        $this->assertEquals($this->SmtpTransport->getAuthType(), 'PLAIN');
    }

    public function testConnectEhloWithAuthLogin(): void
    {
        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->any())
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n250 AUTH LOGIN\r\n",
                "334 Login\r\n",
                "334 Pass\r\n",
                "235 OK\r\n"
            );
        $this->socket->expects($this->exactly(4))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["AUTH LOGIN\r\n"],
                    ["bWFyaw==\r\n"],
                    ["c3Rvcnk=\r\n"]
                )
            );

        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->connect();
        $this->assertEquals($this->SmtpTransport->getAuthType(), 'LOGIN');
    }

    /**
     * testConnectHelo method
     */
    public function testConnectHelo(): void
    {
        $this->socket->expects($this->exactly(3))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "200 Not Accepted\r\n",
                "250 Accepted\r\n"
            );
        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["HELO localhost\r\n"]
                )
            );

        $this->socket->expects($this->once())->method('connect')->willReturn(true);
        $this->SmtpTransport->connect();
    }

    /**
     * testConnectFail method
     */
    public function testConnectFail(): void
    {
        $this->socket->expects($this->exactly(3))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "200 Not Accepted\r\n",
                "200 Not Accepted\r\n"
            );
        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["HELO localhost\r\n"]
                )
            );
        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $e = null;
        try {
            $this->SmtpTransport->connect();
        } catch (SocketException $e) {
        }

        $this->assertNotNull($e);
        $this->assertSame('SMTP server did not accept the connection.', $e->getMessage());
        $this->assertInstanceOf(SocketException::class, $e->getPrevious());
        $this->assertStringContainsString('200 Not Accepted', $e->getPrevious()->getMessage());
    }

    /**
     * Test that when "authType" is specified that's that one used instead of the
     * 1st one supported by the server
     *
     * @return void
     */
    public function testAuthTypeSet(): void
    {
        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n250 AUTH PLAIN LOGIN\r\n",
            );
        $this->socket->expects($this->exactly(1))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                )
            );

        $this->SmtpTransport->setConfig(['authType' => SmtpTransport::AUTH_XOAUTH2]);
        $this->SmtpTransport->connect();
        $this->assertEquals($this->SmtpTransport->getAuthType(), SmtpTransport::AUTH_XOAUTH2);
    }

    public function testExceptionInvalidAuthType(): void
    {
        $this->expectException(CakeException::class);
        $this->expectExceptionMessage('Unsupported auth type. Available types are: PLAIN, LOGIN, XOAUTH2');

        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n250 AUTH PLAIN LOGIN\r\n",
            );
        $this->socket->expects($this->exactly(1))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                )
            );

        $this->SmtpTransport->setConfig(['authType' => 'invalid']);
        $this->SmtpTransport->connect();
    }

    public function testAuthTypeUnsupported(): void
    {
        $this->expectException(CakeException::class);
        $this->expectExceptionMessage('Unsupported auth type: CRAM-MD5');

        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n250 AUTH CRAM-MD5\r\n",
            );
        $this->socket->expects($this->exactly(1))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                )
            );

        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->connect();
    }

    public function testAuthTypeParsingIsSkippedIfNoCredentialsProvided(): void
    {
        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 Accepted\r\n250 AUTH CRAM-MD5\r\n",
            );
        $this->socket->expects($this->exactly(1))
            ->method('write')
            ->with(...self::withConsecutive(
                ["EHLO localhost\r\n"],
            ));

        $this->SmtpTransport->connect();
        $this->assertNull($this->SmtpTransport->getAuthType());
    }

    public function testAuthPlain(): void
    {
        $this->socket->expects($this->once())->method('write')->with("AUTH PLAIN {$this->credentialsEncoded}\r\n");
        $this->socket->expects($this->once())->method('read')->willReturn("235 OK\r\n");
        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->auth();
    }

    /**
     * testAuth method
     */
    public function testAuthLogin(): void
    {
        $this->socket->expects($this->exactly(4))
            ->method('read')
            ->willReturn(
                "504 5.7.4 Unrecognized Authentication Type\r\n",
                "334 Login\r\n",
                "334 Pass\r\n",
                "235 OK\r\n"
            );
        $this->socket->expects($this->exactly(4))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["AUTH PLAIN {$this->credentialsEncoded}\r\n"],
                    ["AUTH LOGIN\r\n"],
                    ["bWFyaw==\r\n"],
                    ["c3Rvcnk=\r\n"]
                )
            );

        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->auth();
    }

    /**
     * testAuth method
     */
    public function testAuthXoauth2(): void
    {
        $authString = base64_encode(sprintf(
            "user=%s\1auth=Bearer %s\1\1",
            $this->credentials['username'],
            $this->credentials['password']
        ));

        $this->socket->expects($this->exactly(1))
            ->method('read')
            ->willReturn(
                "235 OK\r\n"
            );
        $this->socket->expects($this->exactly(1))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["AUTH XOAUTH2 {$authString}\r\n"],
                )
            );

        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->setAuthType('XOAUTH2');
        $this->SmtpTransport->auth();
    }

    /**
     * testAuthNotRecognized method
     */
    public function testAuthNotRecognized(): void
    {
        $this->expectException(SocketException::class);
        $this->expectExceptionMessage('AUTH command not recognized or not implemented, SMTP server may not require authentication.');

        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "504 5.7.4 Unrecognized Authentication Type\r\n",
                "500 5.3.3 Unrecognized command\r\n"
            );
        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["AUTH PLAIN {$this->credentialsEncoded}\r\n"],
                    ["AUTH LOGIN\r\n"]
                )
            );

        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->auth();
    }

    /**
     * testAuthNotImplemented method
     */
    public function testAuthNotImplemented(): void
    {
        $this->expectException(SocketException::class);
        $this->expectExceptionMessage('AUTH command not recognized or not implemented, SMTP server may not require authentication.');

        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "504 5.7.4 Unrecognized Authentication Type\r\n",
                "502 5.3.3 Command not implemented\r\n"
            );
        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["AUTH PLAIN {$this->credentialsEncoded}\r\n"],
                    ["AUTH LOGIN\r\n"]
                )
            );
        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->auth();
    }

    /**
     * testAuthBadSequence method
     */
    public function testAuthBadSequence(): void
    {
        $this->expectException(SocketException::class);
        $this->expectExceptionMessage('SMTP Error: 503 5.5.1 Already authenticated');

        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "504 5.7.4 Unrecognized Authentication Type\r\n",
                "503 5.5.1 Already authenticated\r\n"
            );
        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["AUTH PLAIN {$this->credentialsEncoded}\r\n"],
                    ["AUTH LOGIN\r\n"]
                )
            );
        $this->SmtpTransport->setConfig($this->credentials);
        $this->SmtpTransport->auth();
    }

    /**
     * testAuthBadUsername method
     */
    public function testAuthBadUsername(): void
    {
        $this->socket->expects($this->exactly(3))
            ->method('read')
            ->willReturn(
                "504 5.7.4 Unrecognized Authentication Type\r\n",
                "334 Login\r\n",
                "535 5.7.8 Authentication failed\r\n"
            );
        $this->socket->expects($this->exactly(3))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["AUTH PLAIN {$this->credentialsEncoded}\r\n"],
                    ["AUTH LOGIN\r\n"],
                    ["bWFyaw==\r\n"]
                )
            );

        $this->SmtpTransport->setConfig($this->credentials);

        $e = null;
        try {
            $this->SmtpTransport->auth();
        } catch (SocketException $e) {
        }

        $this->assertNotNull($e);
        $this->assertSame('SMTP server did not accept the username.', $e->getMessage());
        $this->assertInstanceOf(SocketException::class, $e->getPrevious());
        $this->assertStringContainsString('535 5.7.8 Authentication failed', $e->getPrevious()->getMessage());
    }

    /**
     * testAuthBadPassword method
     */
    public function testAuthBadPassword(): void
    {
        $this->socket->expects($this->exactly(4))
            ->method('read')
            ->willReturn(
                "504 5.7.4 Unrecognized Authentication Type\r\n",
                "334 Login\r\n",
                "334 Pass\r\n",
                "535 5.7.8 Authentication failed\r\n"
            );
        $this->socket->expects($this->exactly(4))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["AUTH PLAIN {$this->credentialsEncoded}\r\n"],
                    ["AUTH LOGIN\r\n"],
                    ["bWFyaw==\r\n"],
                    ["c3Rvcnk=\r\n"]
                )
            );

        $this->SmtpTransport->setConfig($this->credentials);

        $e = null;
        try {
            $this->SmtpTransport->auth();
        } catch (SocketException $e) {
        }

        $this->assertNotNull($e);
        $this->assertSame('SMTP server did not accept the password.', $e->getMessage());
        $this->assertInstanceOf(SocketException::class, $e->getPrevious());
        $this->assertStringContainsString('535 5.7.8 Authentication failed', $e->getPrevious()->getMessage());
    }

    /**
     * testRcpt method
     */
    public function testRcpt(): void
    {
        $message = new Message();
        $message->setFrom('[email protected]', 'CakePHP Test');
        $message->setTo('[email protected]', 'CakePHP');
        $message->setBcc('[email protected]');
        $message->setCc(['[email protected]' => 'Mark Story', '[email protected]' => 'Juan Basso']);

        $this->socket->expects($this->any())->method('read')->willReturn("250 OK\r\n");

        $this->socket->expects($this->exactly(5))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["MAIL FROM:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"]
                )
            );

        $this->SmtpTransport->sendRcpt($message);
    }

    /**
     * testRcptWithReturnPath method
     */
    public function testRcptWithReturnPath(): void
    {
        $message = new Message();
        $message->setFrom('[email protected]', 'CakePHP Test');
        $message->setTo('[email protected]', 'CakePHP');
        $message->setReturnPath('[email protected]', 'CakePHP Return');

        $this->socket->expects($this->exactly(2))->method('read')->willReturn("250 OK\r\n");

        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["MAIL FROM:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"]
                )
            );
        $this->SmtpTransport->sendRcpt($message);
    }

    /**
     * testSendData method
     */
    public function testSendData(): void
    {
        $message = new Message();
        $message->setFrom('[email protected]', 'CakePHP Test');
        $message->setReturnPath('[email protected]', 'CakePHP Return');
        $message->setTo('[email protected]', 'CakePHP');
        $message->setReplyTo(['[email protected]' => 'Mark Story', '[email protected]' => 'Juan Basso']);
        $message->setCc(['[email protected]' => 'Mark Story', '[email protected]' => 'Juan Basso']);
        $message->setBcc('[email protected]');
        $message->setMessageId('<4d9946cf-0a44-4907-88fe-1d0ccbdd56cb@localhost>');
        $message->setSubject('Testing SMTP');
        $date = date(DATE_RFC2822);
        $message->setHeaders(['Date' => $date]);
        $message->setBody(['text' => "First Line\nSecond Line\n.Third Line"]);

        $data = "From: CakePHP Test <[email protected]>\r\n";
        $data .= "Reply-To: Mark Story <[email protected]>, Juan Basso <[email protected]>\r\n";
        $data .= "Return-Path: CakePHP Return <[email protected]>\r\n";
        $data .= "To: CakePHP <[email protected]>\r\n";
        $data .= "Cc: Mark Story <[email protected]>, Juan Basso <[email protected]>\r\n";
        $data .= 'Date: ' . $date . "\r\n";
        $data .= "Message-ID: <4d9946cf-0a44-4907-88fe-1d0ccbdd56cb@localhost>\r\n";
        $data .= "Subject: Testing SMTP\r\n";
        $data .= "MIME-Version: 1.0\r\n";
        $data .= "Content-Type: text/plain; charset=UTF-8\r\n";
        $data .= "Content-Transfer-Encoding: 8bit\r\n";
        $data .= "\r\n";
        $data .= "First Line\r\n";
        $data .= "Second Line\r\n";
        $data .= "..Third Line\r\n\r\n"; // RFC5321 4.5.2.Transparency
        $data .= "\r\n";
        $data .= "\r\n\r\n.\r\n";

        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "354 OK\r\n",
                "250 OK\r\n"
            );

        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["DATA\r\n"],
                    [$data]
                )
            );

        $this->SmtpTransport->sendData($message);
    }

    /**
     * testQuit method
     */
    public function testQuit(): void
    {
        $this->socket->expects($this->once())->method('write')->with("QUIT\r\n");
        $this->socket->expects($this->once())->method('isConnected')->willReturn(true);

        $this->SmtpTransport->disconnect();
    }

    /**
     * Tests using empty client name
     */
    public function testEmptyClientName(): void
    {
        $this->socket->expects($this->any())->method('connect')->willReturn(true);
        $this->socket->expects($this->any())
           ->method('read')
           ->willReturn("220 Welcome message\r\n", "250 Accepted\r\n");

        $this->SmtpTransport->setConfig(['client' => '']);

        $this->expectException(SocketException::class);
        $this->expectExceptionMessage('Cannot use an empty client name');
        $this->SmtpTransport->connect();
    }

    /**
     * testGetLastResponse method
     */
    public function testGetLastResponse(): void
    {
        $this->assertEmpty($this->SmtpTransport->getLastResponse());

        $this->socket->expects($this->any())->method('connect')->willReturn(true);
        $this->socket->expects($this->any())
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250-PIPELINING\r\n",
                "250-SIZE 102400000\r\n",
                "250-VRFY\r\n",
                "250-ETRN\r\n",
                "250-STARTTLS\r\n",
                "250-AUTH PLAIN LOGIN\r\n",
                "250-AUTH=PLAIN LOGIN\r\n",
                "250-ENHANCEDSTATUSCODES\r\n",
                "250-8BITMIME\r\n",
                "250 DSN\r\n"
            );
        $this->socket->expects($this->once())->method('write')->with("EHLO localhost\r\n");
        $this->SmtpTransport->connect();

        $expected = [
            ['code' => '250', 'message' => 'PIPELINING'],
            ['code' => '250', 'message' => 'SIZE 102400000'],
            ['code' => '250', 'message' => 'VRFY'],
            ['code' => '250', 'message' => 'ETRN'],
            ['code' => '250', 'message' => 'STARTTLS'],
            ['code' => '250', 'message' => 'AUTH PLAIN LOGIN'],
            ['code' => '250', 'message' => 'AUTH=PLAIN LOGIN'],
            ['code' => '250', 'message' => 'ENHANCEDSTATUSCODES'],
            ['code' => '250', 'message' => '8BITMIME'],
            ['code' => '250', 'message' => 'DSN'],
        ];
        $result = $this->SmtpTransport->getLastResponse();
        $this->assertEquals($expected, $result);
    }

    /**
     * Test getLastResponse() with multiple operations
     */
    public function testGetLastResponseMultipleOperations(): void
    {
        $message = new Message();
        $message->setFrom('[email protected]', 'CakePHP Test');
        $message->setTo('[email protected]', 'CakePHP');

        $this->socket->expects($this->exactly(2))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["MAIL FROM:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"]
                )
            );
        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn("250 OK\r\n");

        $this->SmtpTransport->sendRcpt($message);

        $expected = [
            ['code' => '250', 'message' => 'OK'],
        ];
        $result = $this->SmtpTransport->getLastResponse();
        $this->assertEquals($expected, $result);
    }

    /**
     * testBufferResponseLines method
     */
    public function testBufferResponseLines(): void
    {
        $responseLines = [
            '123',
            "456\tFOO",
            'FOOBAR',
            '250-PIPELINING',
            '250-ENHANCEDSTATUSCODES',
            '250-8BITMIME',
            '250 DSN',
        ];
        $this->SmtpTransport->bufferResponseLines($responseLines);

        $expected = [
            ['code' => '123', 'message' => null],
            ['code' => '250', 'message' => 'PIPELINING'],
            ['code' => '250', 'message' => 'ENHANCEDSTATUSCODES'],
            ['code' => '250', 'message' => '8BITMIME'],
            ['code' => '250', 'message' => 'DSN'],
        ];
        $result = $this->SmtpTransport->getLastResponse();
        $this->assertEquals($expected, $result);
    }

    /**
     * testExplicitConnectAlreadyConnected method
     */
    public function testExplicitConnectAlreadyConnected(): void
    {
        $this->socket->expects($this->never())->method('connect');
        $this->socket->expects($this->once())->method('isConnected')->willReturn(true);

        $this->SmtpTransport->connect();
    }

    /**
     * testConnected method
     */
    public function testConnected(): void
    {
        $this->socket->expects($this->exactly(2))
            ->method('isConnected')
            ->willReturn(
                true,
                false
            );

        $this->assertTrue($this->SmtpTransport->connected());
        $this->assertFalse($this->SmtpTransport->connected());
    }

    /**
     * testAutoDisconnect method
     */
    public function testAutoDisconnect(): void
    {
        $this->socket->expects($this->once())->method('write')->with("QUIT\r\n");
        $this->socket->expects($this->once())->method('disconnect');
        $this->socket->expects($this->once())->method('isConnected')->willReturn(true);
        unset($this->SmtpTransport);
    }

    /**
     * testExplicitDisconnect method
     */
    public function testExplicitDisconnect(): void
    {
        $this->socket->expects($this->once())->method('write')->with("QUIT\r\n");
        $this->socket->expects($this->once())->method('disconnect');
        $this->socket->expects($this->once())->method('isConnected')->willReturn(true);
        $this->SmtpTransport->disconnect();
    }

    /**
     * testExplicitDisconnectNotConnected method
     */
    public function testExplicitDisconnectNotConnected(): void
    {
        $callback = function ($arg): void {
            $this->assertNotEquals("QUIT\r\n", $arg);
        };
        $this->socket->expects($this->any())->method('write')->willReturnCallback($callback);
        $this->socket->expects($this->never())->method('disconnect');
        $this->SmtpTransport->disconnect();
    }

    /**
     * testKeepAlive method
     */
    public function testKeepAlive(): void
    {
        $this->SmtpTransport->setConfig(['keepAlive' => true]);

        /** @var \Cake\Mailer\Message $message */
        $message = $this->getMockBuilder(Message::class)
            ->onlyMethods(['getBody'])
            ->getMock();
        $message->setFrom('[email protected]', 'CakePHP Test');
        $message->setTo('[email protected]', 'CakePHP');
        $message->expects($this->exactly(2))->method('getBody')->willReturn(['First Line']);

        $callback = function ($arg) {
            $this->assertNotEquals("QUIT\r\n", $arg);

            return 1;
        };
        $this->socket->expects($this->any())->method('write')->willReturnCallback($callback);
        $this->socket->expects($this->never())->method('disconnect');

        $this->socket->expects($this->exactly(11))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 OK\r\n",
                "250 OK\r\n",
                "250 OK\r\n",
                "354 OK\r\n",
                "250 OK\r\n",
                "250 OK\r\n",
                // Second email
                "250 OK\r\n",
                "250 OK\r\n",
                "354 OK\r\n",
                "250 OK\r\n"
            );
        $this->socket->expects($this->exactly(10))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["MAIL FROM:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"],
                    ["DATA\r\n"],
                    [$this->stringContains('First Line')],
                    ["RSET\r\n"],
                    // Second email
                    ["MAIL FROM:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"],
                    ["DATA\r\n"],
                    [$this->stringContains('First Line')]
                )
            );

        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->SmtpTransport->send($message);
        $this->socket->expects($this->once())->method('isConnected')->willReturn(true);
        $this->SmtpTransport->send($message);
    }

    /**
     * testSendDefaults method
     */
    public function testSendDefaults(): void
    {
        /** @var \Cake\Mailer\Message $message */
        $message = $this->getMockBuilder(Message::class)
            ->onlyMethods(['getBody'])
            ->getMock();
        $message->setFrom('[email protected]', 'CakePHP Test');
        $message->setTo('[email protected]', 'CakePHP');
        $message->expects($this->once())->method('getBody')->willReturn(['First Line']);

        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->atLeast(6))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 OK\r\n",
                "250 OK\r\n",
                "250 OK\r\n",
                "354 OK\r\n",
                "250 OK\r\n"
            );

        $this->socket->expects($this->atLeast(6))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["MAIL FROM:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"],
                    ["DATA\r\n"],
                    [$this->stringContains('First Line')],
                    ["QUIT\r\n"]
                )
            );

        $this->socket->expects($this->once())->method('disconnect');

        $this->SmtpTransport->send($message);
    }

    /**
     * testSendDefaults method
     */
    public function testSendMessageTooBigOnWindows(): void
    {
        /** @var \Cake\Mailer\Message $message */
        $message = $this->getMockBuilder(Message::class)
            ->onlyMethods(['getBody'])
            ->getMock();
        $message->setFrom('[email protected]', 'CakePHP Test');
        $message->setTo('[email protected]', 'CakePHP');
        $message->expects($this->once())->method('getBody')->willReturn(['First Line']);

        $this->socket->expects($this->once())->method('connect')->willReturn(true);

        $this->socket->expects($this->atLeast(6))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 OK\r\n",
                "250 OK\r\n",
                "250 OK\r\n",
                "354 OK\r\n",
                'Message size too large'
            );

        $this->socket->expects($this->exactly(5))
            ->method('write')
            ->with(
                ...self::withConsecutive(
                    ["EHLO localhost\r\n"],
                    ["MAIL FROM:<[email protected]>\r\n"],
                    ["RCPT TO:<[email protected]>\r\n"],
                    ["DATA\r\n"],
                    [$this->stringContains('First Line')]
                )
            );

        $this->expectException(SocketException::class);
        $this->expectExceptionMessage('Message size too large');

        $this->SmtpTransport->send($message);
    }

    /**
     * Ensure that unserialized transports have no connection.
     */
    public function testSerializeCleanupSocket(): void
    {
        $this->socket->expects($this->once())->method('connect')->willReturn(true);
        $this->socket->expects($this->exactly(2))
            ->method('read')
            ->willReturn(
                "220 Welcome message\r\n",
                "250 OK\r\n"
            );
        $this->socket->expects($this->once())
            ->method('write')
            ->with("EHLO localhost\r\n");

        $smtpTransport = new SmtpTestTransport();
        $smtpTransport->setSocket($this->socket);
        $smtpTransport->connect();

        $result = unserialize(serialize($smtpTransport));
        $this->assertStringContainsString('[protected] _socket => [uninitialized]', Debugger::exportVar($result));
        $this->assertFalse($result->connected());
    }
}

Function Calls

None

Variables

None

Stats

MD5 58ed1cd0d002de53c59b671892b32eab
Eval Count 0
Decode Time 99 ms