diff --git a/src/Protocol/Pop3.php b/src/Protocol/Pop3.php index a097823c..5c6ee46a 100644 --- a/src/Protocol/Pop3.php +++ b/src/Protocol/Pop3.php @@ -2,6 +2,7 @@ namespace Laminas\Mail\Protocol; +use Laminas\Mail\Protocol\Pop3\Response; use Laminas\Stdlib\ErrorHandler; use function explode; @@ -151,25 +152,14 @@ public function sendRequest($request) */ public function readResponse($multiline = false) { - ErrorHandler::start(); - $result = fgets($this->socket); - $error = ErrorHandler::stop(); - if (! is_string($result)) { - throw new Exception\RuntimeException('read failed - connection closed?', 0, $error); - } + $response = $this->readRemoteResponse(); - $result = trim($result); - if (strpos($result, ' ')) { - [$status, $message] = explode(' ', $result, 2); - } else { - $status = $result; - $message = ''; - } - - if ($status != '+OK') { + if ($response->status() != '+OK') { throw new Exception\RuntimeException('last request failed'); } + $message = $response->message(); + if ($multiline) { $message = ''; $line = fgets($this->socket); @@ -185,6 +175,32 @@ public function readResponse($multiline = false) return $message; } + /** + * read a response + * return extracted status / message from response + + * @throws Exception\RuntimeException + */ + protected function readRemoteResponse(): Response + { + ErrorHandler::start(); + $result = fgets($this->socket); + $error = ErrorHandler::stop(); + if (! is_string($result)) { + throw new Exception\RuntimeException('read failed - connection closed?', 0, $error); + } + + $result = trim($result); + if (strpos($result, ' ')) { + [$status, $message] = explode(' ', $result, 2); + } else { + $status = $result; + $message = ''; + } + + return new Response($status, $message); + } + /** * Send request and get response * diff --git a/src/Protocol/Pop3/Response.php b/src/Protocol/Pop3/Response.php new file mode 100644 index 00000000..41c0eb5b --- /dev/null +++ b/src/Protocol/Pop3/Response.php @@ -0,0 +1,35 @@ +status = $status; + $this->message = $message; + } + + public function status(): string + { + return $this->status; + } + + public function message(): string + { + return $this->message; + } +} diff --git a/src/Protocol/Pop3/Xoauth2/Microsoft.php b/src/Protocol/Pop3/Xoauth2/Microsoft.php new file mode 100644 index 00000000..74d093dc --- /dev/null +++ b/src/Protocol/Pop3/Xoauth2/Microsoft.php @@ -0,0 +1,34 @@ +sendRequest(self::AUTH_INITIALIZE_REQUEST); + + $response = $this->readRemoteResponse(); + + if ($response->status() != self::AUTH_RESPONSE_INITIALIZED_OK) { + throw new RuntimeException($response->message()); + } + + $this->request(Xoauth2::encodeXoauth2Sasl($user, $password)); + } +} diff --git a/src/Protocol/Xoauth2/Xoauth2.php b/src/Protocol/Xoauth2/Xoauth2.php new file mode 100644 index 00000000..fa3d5b88 --- /dev/null +++ b/src/Protocol/Xoauth2/Xoauth2.php @@ -0,0 +1,32 @@ +assertEquals('+OK', $response->status()); + + /** @psalm-suppress InternalMethod */ + $this->assertEquals('Auth', $response->message()); + } +} diff --git a/test/Protocol/Pop3/Xoauth2/MicrosoftTest.php b/test/Protocol/Pop3/Xoauth2/MicrosoftTest.php new file mode 100644 index 00000000..883b2b08 --- /dev/null +++ b/test/Protocol/Pop3/Xoauth2/MicrosoftTest.php @@ -0,0 +1,102 @@ +step === self::AUTH_INITIALIZE_REQUEST) { + /** @psalm-suppress InternalMethod */ + return new Response(self::AUTH_RESPONSE_INITIALIZED_OK, 'Auth initialized'); + } + + /** @psalm-suppress InternalMethod */ + return new Response('+OK', 'Authenticated'); + } + + /** + * Send a request + * + * @param string $request your request without newline + * @throws RuntimeException + */ + public function sendRequest($request): void + { + $this->step = $request; + parent::sendRequest($request); + } + + /** + * Open connection to POP3 server + * + * @param string $host hostname or IP address of POP3 server + * @param int|null $port of POP3 server, default is 110 (995 for ssl) + * @param string|bool $ssl use 'SSL', 'TLS' or false + * @throws RuntimeException + * @return string welcome message + */ + public function connect($host, $port = null, $ssl = false) + { + $this->socket = fopen("php://memory", 'rw+'); + return ''; + } + + /** + * @return null|resource + */ + public function getSocket() + { + return $this->socket; + } + }; + + $protocol->connect('localhost', 0, false); + + $protocol->login('test@example.com', '123'); + + $this->assertInstanceOf(Microsoft::class, $protocol); + + $streamContents = ''; + if ($socket = $protocol->getSocket()) { + rewind($socket); + $streamContents = stream_get_contents($socket); + $streamContents = str_replace("\r\n", "\n", $streamContents); + } + + /** @psalm-suppress InternalMethod */ + $xoauth2Sasl = Xoauth2::encodeXoauth2Sasl('test@example.com', '123'); + + $this->assertEquals( + 'AUTH XOAUTH2' . "\n" . $xoauth2Sasl . "\n", + $streamContents + ); + } +} diff --git a/test/Protocol/Xoauth2/Xoauth2Test.php b/test/Protocol/Xoauth2/Xoauth2Test.php new file mode 100644 index 00000000..f0c0eb63 --- /dev/null +++ b/test/Protocol/Xoauth2/Xoauth2Test.php @@ -0,0 +1,32 @@ +assertEquals( + $accessToken, + Xoauth2::encodeXoauth2Sasl( + 'test@contoso.onmicrosoft.com', + 'EwBAAl3BAAUFFpUAo7J3Ve0bjLBWZWCclRC3EoAA' + ) + ); + } +}