blob: 41a953d9fcc8099b45c936cc7585f656298d5adb [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001<?php
2
3declare(strict_types=1);
4
5namespace Ddeboer\Imap;
6
7use Ddeboer\Imap\Exception\AuthenticationFailedException;
8use Ddeboer\Imap\Exception\ResourceCheckFailureException;
9
10/**
11 * An IMAP server.
12 */
13final class Server implements ServerInterface
14{
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020015 private string $hostname;
16 private string $port;
17 private string $flags;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010018 /**
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019 * @var mixed[]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010020 */
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020021 private array $parameters;
22 private int $options;
23 private int $retries;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010024
25 /**
26 * Constructor.
27 *
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020028 * @param string $hostname Internet domain name or bracketed IP address
29 * of server
30 * @param string $port TCP port number
31 * @param string $flags Optional flags
32 * @param mixed[] $parameters Connection parameters
33 * @param int $options Connection options
34 * @param int $retries Retries number
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010035 */
36 public function __construct(
37 string $hostname,
38 string $port = '993',
39 string $flags = '/imap/ssl/validate-cert',
40 array $parameters = [],
41 int $options = 0,
42 int $retries = 1
43 ) {
44 if (!\function_exists('imap_open')) {
45 throw new \RuntimeException('IMAP extension must be enabled');
46 }
47
48 $this->hostname = $hostname;
49 $this->port = $port;
50 $this->flags = '' !== $flags ? '/' . \ltrim($flags, '/') : '';
51 $this->parameters = $parameters;
52 $this->options = $options;
53 $this->retries = $retries;
54 }
55
56 /**
57 * Authenticate connection.
58 *
59 * @param string $username Username
60 * @param string $password Password
61 *
62 * @throws AuthenticationFailedException
63 */
64 public function authenticate(string $username, string $password): ConnectionInterface
65 {
66 $errorMessage = null;
67 $errorNumber = 0;
68 \set_error_handler(static function ($nr, $message) use (&$errorMessage, &$errorNumber): bool {
69 $errorMessage = $message;
70 $errorNumber = $nr;
71
72 return true;
73 });
74
75 $resource = \imap_open(
76 $this->getServerString(),
77 $username,
78 $password,
79 $this->options,
80 $this->retries,
81 $this->parameters
82 );
83
84 \restore_error_handler();
85
86 if (false === $resource || null !== $errorMessage) {
87 throw new AuthenticationFailedException(\sprintf(
88 'Authentication failed for user "%s"%s',
89 $username,
90 null !== $errorMessage ? ': ' . $errorMessage : ''
91 ), $errorNumber);
92 }
93
94 $check = \imap_check($resource);
95
96 if (false === $check) {
97 throw new ResourceCheckFailureException('Resource check failure');
98 }
99
100 $mailbox = $check->Mailbox;
101 $connection = $mailbox;
102 $curlyPosition = \strpos($mailbox, '}');
103 if (false !== $curlyPosition) {
104 $connection = \substr($mailbox, 0, $curlyPosition + 1);
105 }
106
107 // These are necessary to get rid of PHP throwing IMAP errors
108 \imap_errors();
109 \imap_alerts();
110
111 return new Connection(new ImapResource($resource), $connection);
112 }
113
114 /**
115 * Glues hostname, port and flags and returns result.
116 */
117 private function getServerString(): string
118 {
119 return \sprintf(
120 '{%s%s%s}',
121 $this->hostname,
122 '' !== $this->port ? ':' . $this->port : '',
123 $this->flags
124 );
125 }
126}