1 | <?php |
||||
2 | namespace Mezon\Pop3; |
||||
3 | |||||
4 | /** |
||||
5 | * Class Client |
||||
6 | * |
||||
7 | * @package Mezon |
||||
8 | * @subpackage Pop3Client |
||||
9 | * @author Dodonov A.A. |
||||
10 | * @version v.1.0 (2019/08/13) |
||||
11 | * @copyright Copyright (c) 2019, aeon.org |
||||
12 | */ |
||||
13 | |||||
14 | /** |
||||
15 | * POP3 protocol client. |
||||
16 | */ |
||||
17 | class Client |
||||
18 | { |
||||
19 | |||||
20 | /** |
||||
21 | * Connection |
||||
22 | */ |
||||
23 | private $connection = false; |
||||
24 | |||||
25 | /** |
||||
26 | * Method connects to server |
||||
27 | * |
||||
28 | * @param string $server |
||||
29 | * Server domain |
||||
30 | * @param string $login |
||||
31 | * Login |
||||
32 | * @param string $password |
||||
33 | * Password |
||||
34 | * @param int $timeOut |
||||
35 | * Timeout |
||||
36 | * @param int $port |
||||
37 | * Port number |
||||
38 | */ |
||||
39 | public function connect(string $server, string $login, string $password, int $timeOut = 5, int $port = 110) |
||||
40 | { |
||||
41 | try { |
||||
42 | $errorCode = $errorMessage = ''; |
||||
43 | |||||
44 | $context = stream_context_create([ |
||||
45 | 'ssl' => [ |
||||
46 | 'verify_peer' => false |
||||
47 | ] |
||||
48 | ]); |
||||
49 | |||||
50 | $this->connection = stream_socket_client( |
||||
51 | $server . ":$port", |
||||
52 | $errorCode, |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
53 | $errorMessage, |
||||
54 | $timeOut, |
||||
55 | STREAM_CLIENT_CONNECT, |
||||
56 | $context); |
||||
57 | |||||
58 | if ($this->connection === false) { |
||||
59 | throw (new \Exception('Connection was not established', - 1)); |
||||
60 | } |
||||
61 | |||||
62 | $result = fgets($this->connection, 1024); |
||||
63 | |||||
64 | if (substr($result, 0, 3) !== '+OK') { |
||||
65 | throw (new \Exception('Connection. ' . $result, 0)); |
||||
66 | } |
||||
67 | |||||
68 | fputs($this->connection, "USER $login\r\n"); |
||||
69 | |||||
70 | $result = fgets($this->connection, 1024); |
||||
71 | |||||
72 | if (substr($result, 0, 3) !== '+OK') { |
||||
73 | throw (new \Exception("USER $login " . $result, 0)); |
||||
74 | } |
||||
75 | |||||
76 | fputs($this->connection, "PASS $password\r\n"); |
||||
77 | |||||
78 | $result = fgets($this->connection, 1024); |
||||
79 | |||||
80 | if (substr($result, 0, 3) !== '+OK') { |
||||
81 | throw (new \Exception("PASS " . $result . $login, 0)); |
||||
82 | } |
||||
83 | } catch (\Exception $e) { |
||||
84 | throw ($e); |
||||
85 | } |
||||
86 | } |
||||
87 | |||||
88 | /** |
||||
89 | * Constructor |
||||
90 | * |
||||
91 | * @param string $server |
||||
92 | * Server domain |
||||
93 | * @param string $login |
||||
94 | * Login |
||||
95 | * @param string $password |
||||
96 | * Password |
||||
97 | * @param int $timeOut |
||||
98 | * Timeout |
||||
99 | * @param int $port |
||||
100 | * Port number |
||||
101 | */ |
||||
102 | public function __construct(string $server, string $login, string $password, int $timeOut = 5, int $port = 110) |
||||
103 | { |
||||
104 | $this->connect($server, $login, $password, $timeOut, $port); |
||||
105 | } |
||||
106 | |||||
107 | /** |
||||
108 | * Method returns emails count. |
||||
109 | */ |
||||
110 | public function getCount(): int |
||||
111 | { |
||||
112 | fputs($this->connection, "STAT\r\n"); |
||||
0 ignored issues
–
show
$this->connection of type boolean is incompatible with the type resource expected by parameter $handle of fputs() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
113 | |||||
114 | $result = fgets($this->connection, 1024); |
||||
0 ignored issues
–
show
$this->connection of type boolean is incompatible with the type resource expected by parameter $handle of fgets() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
115 | |||||
116 | if (substr($result, 0, 3) !== '+OK') { |
||||
117 | throw (new \Exception("STAT " . $result, 0)); |
||||
118 | } |
||||
119 | |||||
120 | $result = explode(' ', $result); |
||||
121 | |||||
122 | return intval($result[1]); |
||||
123 | } |
||||
124 | |||||
125 | /** |
||||
126 | * Method returns data from connection |
||||
127 | * |
||||
128 | * @return string Fetched data |
||||
129 | */ |
||||
130 | protected function getData(): string |
||||
131 | { |
||||
132 | $data = ''; |
||||
133 | |||||
134 | while (! feof($this->connection)) { |
||||
0 ignored issues
–
show
$this->connection of type boolean is incompatible with the type resource expected by parameter $handle of feof() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
135 | $buffer = chop(fgets($this->connection, 1024)); |
||||
0 ignored issues
–
show
$this->connection of type boolean is incompatible with the type resource expected by parameter $handle of fgets() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
136 | |||||
137 | if (strpos($buffer, '-ERR') === 0) { |
||||
138 | throw (new \Exception(str_replace('-ERR ', '', $buffer), 0)); |
||||
139 | } |
||||
140 | |||||
141 | $data .= "$buffer\r\n"; |
||||
142 | |||||
143 | if (trim($buffer) == '.') { |
||||
144 | break; |
||||
145 | } |
||||
146 | } |
||||
147 | |||||
148 | return $data; |
||||
149 | } |
||||
150 | |||||
151 | /** |
||||
152 | * Method returns email's headers |
||||
153 | * |
||||
154 | * @param int $i |
||||
155 | * Number of the message |
||||
156 | * @return string Headers |
||||
157 | */ |
||||
158 | public function getMessageHeaders(int $i): string |
||||
159 | { |
||||
160 | fputs($this->connection, "TOP $i 3\r\n"); |
||||
0 ignored issues
–
show
$this->connection of type boolean is incompatible with the type resource expected by parameter $handle of fputs() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
161 | |||||
162 | return $this->getData(); |
||||
163 | } |
||||
164 | |||||
165 | /** |
||||
166 | * Method deletes email |
||||
167 | * |
||||
168 | * @param int $i |
||||
169 | * Number of the message |
||||
170 | * @return string Result of the deletion |
||||
171 | */ |
||||
172 | public function deleteMessage($i): string |
||||
173 | { |
||||
174 | fputs($this->connection, "DELE $i\r\n"); |
||||
0 ignored issues
–
show
$this->connection of type boolean is incompatible with the type resource expected by parameter $handle of fputs() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
175 | |||||
176 | return fgets($this->connection); |
||||
0 ignored issues
–
show
$this->connection of type boolean is incompatible with the type resource expected by parameter $handle of fgets() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
177 | } |
||||
178 | |||||
179 | /** |
||||
180 | * Method terminates session |
||||
181 | */ |
||||
182 | public function quit() |
||||
183 | { |
||||
184 | fputs($this->connection, "QUIT\r\n"); |
||||
0 ignored issues
–
show
$this->connection of type boolean is incompatible with the type resource expected by parameter $handle of fputs() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
185 | } |
||||
186 | |||||
187 | /** |
||||
188 | * Method parses subject with any prefix |
||||
189 | * |
||||
190 | * @param string $line |
||||
191 | * Line of the email |
||||
192 | * @param int $i |
||||
193 | * Line cursor |
||||
194 | * @param array $headers |
||||
195 | * Email headers |
||||
196 | * @param string $type |
||||
197 | * Mime type |
||||
198 | * @return string Decoded data |
||||
199 | */ |
||||
200 | protected function parseAnyType(string $line, int $i, array $headers, string $type): string |
||||
201 | { |
||||
202 | $subject = substr($line, 0, strlen($line) - 2); |
||||
203 | |||||
204 | for ($j = $i + 1; $j < count($headers); $j ++) { |
||||
0 ignored issues
–
show
It seems like you are calling the size function
count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}
// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
![]() |
|||||
205 | if (substr($headers[$j], 0, 1) == ' ') { |
||||
206 | $subject .= str_replace([ |
||||
207 | ' ' . $type, |
||||
208 | '?=' |
||||
209 | ], [ |
||||
210 | '', |
||||
211 | '' |
||||
212 | ], $headers[$j]); |
||||
213 | } else { |
||||
214 | return str_replace('Subject: ', '', iconv_mime_decode($subject . "?=\r\n", 0, "UTF-8")); |
||||
215 | } |
||||
216 | } |
||||
0 ignored issues
–
show
In this branch, the function will implicitly return
null which is incompatible with the type-hinted return string . Consider adding a return statement or allowing null as return value.
For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example: interface ReturnsInt {
public function returnsIntHinted(): int;
}
class MyClass implements ReturnsInt {
public function returnsIntHinted(): int
{
if (foo()) {
return 123;
}
// here: null is implicitly returned
}
}
![]() |
|||||
217 | } |
||||
218 | |||||
219 | /** |
||||
220 | * Method returns message's subject |
||||
221 | * |
||||
222 | * @param int $i |
||||
223 | * Line number |
||||
224 | * @return string Decoded data |
||||
225 | */ |
||||
226 | public function getMessageSubject(int $i): string |
||||
227 | { |
||||
228 | $headers = $this->getMessageHeaders($i); |
||||
229 | |||||
230 | $headers = explode("\r\n", $headers); |
||||
231 | |||||
232 | foreach ($headers as $i => $line) { |
||||
233 | if (strpos($line, 'Subject: ') === 0) { |
||||
234 | if (strpos($line, '=?UTF-8?Q?') !== false) { |
||||
235 | return $this->parseAnyType($line, $i, $headers, '=?UTF-8?Q?'); |
||||
236 | } elseif (strpos($line, '=?UTF-8?B?') !== false) { |
||||
237 | return $this->parseAnyType($line, $i, $headers, '=?UTF-8?B?'); |
||||
238 | } |
||||
239 | } |
||||
240 | } |
||||
0 ignored issues
–
show
In this branch, the function will implicitly return
null which is incompatible with the type-hinted return string . Consider adding a return statement or allowing null as return value.
For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example: interface ReturnsInt {
public function returnsIntHinted(): int;
}
class MyClass implements ReturnsInt {
public function returnsIntHinted(): int
{
if (foo()) {
return 123;
}
// here: null is implicitly returned
}
}
![]() |
|||||
241 | } |
||||
242 | |||||
243 | /** |
||||
244 | * Method returns true if the mail with the specified subject exists |
||||
245 | * |
||||
246 | * @param string $subject |
||||
247 | * Searching subject |
||||
248 | * @return bool Email exists |
||||
249 | */ |
||||
250 | public function messageWithSubjectExists(string $subject): bool |
||||
251 | { |
||||
252 | $count = $this->getCount(); |
||||
253 | |||||
254 | for ($i = 1; $i <= $count; $i ++) { |
||||
255 | $mailSubject = $this->getMessageSubject($i); |
||||
256 | |||||
257 | if ($subject == $mailSubject) { |
||||
258 | return true; |
||||
259 | } |
||||
260 | } |
||||
261 | |||||
262 | return false; |
||||
263 | } |
||||
264 | |||||
265 | /** |
||||
266 | * Method removes all the mails with the specified subject |
||||
267 | * |
||||
268 | * @param string $subject |
||||
269 | * subject of emails to be deleted |
||||
270 | */ |
||||
271 | public function deleteMessagesWithSubject(string $subject) |
||||
272 | { |
||||
273 | $count = $this->getCount(); |
||||
274 | |||||
275 | for ($i = 1; $i <= $count; $i ++) { |
||||
276 | $mailSubject = $this->getMessageSubject($i); |
||||
277 | |||||
278 | if ($subject == $mailSubject) { |
||||
279 | $this->deleteMessage($i); |
||||
280 | } |
||||
281 | } |
||||
282 | } |
||||
283 | |||||
284 | /** |
||||
285 | * Method returns Message-ID |
||||
286 | * |
||||
287 | * @param string $headers |
||||
288 | * email headers |
||||
289 | * @return string Message-ID |
||||
290 | */ |
||||
291 | public static function getMessageId(string $headers): string |
||||
292 | { |
||||
293 | $matches = []; |
||||
294 | |||||
295 | preg_match('/Message-ID: <([0-9a-zA-Z\.@\-]*)>/mi', $headers, $matches); |
||||
296 | |||||
297 | return $matches[1]; |
||||
298 | } |
||||
299 | } |
||||
300 |