Issues (78)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/mailbox.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * @author Christoph Wurst <[email protected]>
5
 * @author Clement Wong <[email protected]>
6
 * @author Jan-Christoph Borchardt <[email protected]>
7
 * @author Lukas Reschke <[email protected]>
8
 * @author matiasdelellis <[email protected]>
9
 * @author Robin McCorkell <[email protected]>
10
 * @author Thomas Imbreckx <[email protected]>
11
 * @author Thomas I <[email protected]>
12
 * @author Thomas Mueller <[email protected]>
13
 * @author Thomas Müller <[email protected]>
14
 *
15
 * Mail
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
31
namespace OCA\Mail;
32
33
use Horde_Imap_Client;
34
use Horde_Imap_Client_Exception;
35
use Horde_Imap_Client_Fetch_Query;
36
use Horde_Imap_Client_Ids;
37
use Horde_Imap_Client_Mailbox;
38
use Horde_Imap_Client_Search_Query;
39
use Horde_Imap_Client_Socket;
40
use OCA\Mail\Model\IMAPMessage;
41
use OCA\Mail\Service\IMailBox;
42
43
class Mailbox implements IMailBox {
44
45
	/**
46
	 * @var Horde_Imap_Client_Socket
47
	 */
48
	protected $conn;
49
50
	/**
51
	 * @var array
52
	 */
53
	private $attributes;
54
55
	/**
56
	 * @var string
57
	 */
58
	private $specialRole;
59
60
	/**
61
	 * @var string
62
	 */
63
	private $displayName;
64
65
	/**
66
	 * @var string
67
	 */
68
	private $delimiter;
69
70
	/**
71
	 * @var Horde_Imap_Client_Mailbox
72
	 */
73
	protected $mailBox;
74
75
	/**
76
	 * @param Horde_Imap_Client_Socket $conn
77
	 * @param Horde_Imap_Client_Mailbox $mailBox
78
	 * @param array $attributes
79
	 * @param string $delimiter
80
	 */
81 12
	public function __construct($conn, $mailBox, $attributes, $delimiter='/') {
82 12
		$this->conn = $conn;
83 12
		$this->mailBox = $mailBox;
84 12
		$this->attributes = $attributes;
85 12
		$this->delimiter = $delimiter;
86 12
		$this->getSpecialRoleFromAttributes();
87 12
		if ($this->specialRole === null) {
88 12
			$this->guessSpecialRole();
89 12
		}
90 12
		$this->makeDisplayName();
91 12
	}
92
93 6
	private function getSearchIds($from, $count, $filter) {
94 6
		if ($filter instanceof Horde_Imap_Client_Search_Query) {
0 ignored issues
show
The class Horde_Imap_Client_Search_Query does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
95 3
			$query = $filter;
96 3
		} else {
97 3
			$query = new Horde_Imap_Client_Search_Query();
98 3
			if ($filter) {
99
				$query->text($filter, false);
100
			}
101
		}
102 6
		if ($this->getSpecialRole() !== 'trash') {
103 6
			$query->flag(Horde_Imap_Client::FLAG_DELETED, false);
104 6
		}
105 6
		$result = $this->conn->search($this->mailBox, $query, ['sort' => [Horde_Imap_Client::SORT_DATE]]);
106 6
		$ids = array_reverse($result['match']->ids);
107 6
		if ($from >= 0 && $count >= 0) {
108 3
			$ids = array_slice($ids, $from, $count);
109 3
		}
110 6
		return new \Horde_Imap_Client_Ids($ids, false);
111
	}
112
113
	private function getFetchIds($from, $count) {
114
		$q = new Horde_Imap_Client_Fetch_Query();
115
		$q->uid();
116
		$q->imapDate();
117
118
		$result = $this->conn->fetch($this->mailBox, $q);
119
		$uidMap = [];
120
		foreach ($result as $r) {
121
			$uidMap[$r->getUid()] = $r->getImapDate()->getTimeStamp();
122
		}
123
		// sort by time
124
		uasort($uidMap, function($a, $b) {
125
			return $a < $b;
126
		});
127
		if ($from >= 0 && $count >= 0) {
128
			$uidMap = array_slice($uidMap, $from, $count, true);
129
		}
130
		return new \Horde_Imap_Client_Ids(array_keys($uidMap), false);
131
	}
132
133 6
	public function getMessages($from = 0, $count = 2, $filter = '') {
134 6
		if (!$this->conn->capability->query('SORT') && (is_null($filter) || $filter === '')) {
135
			$ids = $this->getFetchIds($from, $count);
136
		} else {
137 6
			$ids = $this->getSearchIds($from, $count, $filter);
138
		}
139
140 6
		$headers = [];
141
142 6
		$fetch_query = new Horde_Imap_Client_Fetch_Query();
143 6
		$fetch_query->envelope();
144 6
		$fetch_query->flags();
145 6
		$fetch_query->size();
146 6
		$fetch_query->uid();
147 6
		$fetch_query->imapDate();
148 6
		$fetch_query->structure();
149
150 6
		$headers = array_merge($headers, [
151 6
			'importance',
152 6
			'list-post',
153
			'x-priority'
154 6
		]);
155 6
		$headers[] = 'content-type';
156
157 6
		$fetch_query->headers('imp', $headers, [
158 6
			'cache' => true,
159
			'peek'  => true
160 6
		]);
161
162 6
		$options = ['ids' => $ids];
163
		// $list is an array of Horde_Imap_Client_Data_Fetch objects.
164 6
		$headers = $this->conn->fetch($this->mailBox, $fetch_query, $options);
165
166 6
		ob_start(); // fix for Horde warnings
167 6
		$messages = [];
168 6
		foreach ($headers->ids() as $message_id) {
169 6
			$header = $headers[$message_id];
170 6
			$message = new IMAPMessage($this->conn, $this->mailBox, $message_id, $header);
171 6
			$messages[] = $message->getListArray();
172 6
		}
173 6
		ob_get_clean();
174
175
		// sort by time
176
		usort($messages, function($a, $b) {
177
			return $a['dateInt'] < $b['dateInt'];
178 6
		});
179
180 6
		return $messages;
181
	}
182
183
	/**
184
	 * @return array
185
	 */
186 3
	public function attributes() {
187 3
		return $this->attributes;
188
	}
189
190
	/**
191
	 * @param string $messageId
192
	 * @param bool $loadHtmlMessageBody
193
	 * @return IMAPMessage
194
	 */
195
	public function getMessage($messageId, $loadHtmlMessageBody = false) {
196
		return new IMAPMessage($this->conn, $this->mailBox, $messageId, null, $loadHtmlMessageBody);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \OCA\Mail\Mod... $loadHtmlMessageBody); (OCA\Mail\Model\IMAPMessage) is incompatible with the return type declared by the interface OCA\Mail\Service\IMailBox::getMessage of type OCA\Mail\Message.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
197
	}
198
199
	/**
200
	 * @param int $flags
201
	 * @return array
202
	 */
203 12
	public function getStatus($flags = \Horde_Imap_Client::STATUS_ALL) {
204 12
		return $this->conn->status($this->mailBox, $flags);
205
	}
206
207
	/**
208
	 * @return int
209
	 */
210 3
	public function getTotalMessages() {
211 3
		$status = $this->getStatus(\Horde_Imap_Client::STATUS_MESSAGES);
212 3
		return (int) $status['messages'];
213
	}
214
215 12
	protected function makeDisplayName() {
216 12
		$parts = explode($this->delimiter, $this->mailBox->utf8, 2);
217
218 12
		if (count($parts) > 1) {
219
			$displayName = $parts[1];
220 12
		} elseif (strtolower($this->mailBox->utf8) === 'inbox') {
221 6
			$displayName = 'Inbox';
222 6
		} else {
223 12
			$displayName = $this->mailBox->utf8;
224
		}
225
226 12
		$this->displayName = $displayName;
227 12
	}
228
229 13
	public function getFolderId() {
230 13
		return $this->mailBox->utf8;
231
	}
232
233
	/**
234
	 * @return string
235
	 */
236 6
	public function getParent() {
237 6
		$folderId = $this->getFolderId();
238 6
		$parts = explode($this->delimiter, $folderId, 2);
239
240 6
		if (count($parts) > 1) {
241
			return $parts[0];
242
		}
243
244 6
		return null;
245
	}
246
247
	/**
248
	 * @return string
249
	 */
250 9
	public function getSpecialRole() {
251 9
		return $this->specialRole;
252
	}
253
254
	/**
255
	 * @return string
256
	 */
257 6
	public function getDisplayName() {
258 6
		return $this->displayName;
259
	}
260
261
	/**
262
	 * @param string $displayName
263
	 */
264 6
	public function setDisplayName($displayName) {
265 6
		$this->displayName = $displayName;
266 6
	}
267
268
	/**
269
	 * @param integer $accountId
270
	 * @return array
271
	 */
272 6
	public function getListArray($accountId, $status = null) {
273 6
		$displayName = $this->getDisplayName();
274
		try {
275 6
			if (is_null($status)) {
276 3
				$status = $this->getStatus();
277 3
			}
278 6
			$total = $status['messages'];
279 6
			$specialRole = $this->getSpecialRole();
280 6
			$unseen = ($specialRole === 'trash') ? 0 : $status['unseen'];
281 6
			$isEmpty = ($total === 0);
282 6
			$noSelect = in_array('\\noselect', $this->attributes);
283 6
			$parentId = $this->getParent();
284 6
			$parentId = ($parentId !== null) ? base64_encode($parentId) : null;
285
			return [
286 6
				'id' => base64_encode($this->getFolderId()),
287 6
				'parent' => $parentId,
288 6
				'name' => $displayName,
289 6
				'specialRole' => $specialRole,
290 6
				'unseen' => $unseen,
291 6
				'total' => $total,
292 6
				'isEmpty' => $isEmpty,
293 6
				'accountId' => $accountId,
294 6
				'noSelect' => $noSelect,
295 6
				'uidvalidity' => $status['uidvalidity'],
296 6
				'uidnext' => $status['uidnext'],
297 6
				'delimiter' => $this->delimiter
298 6
			];
299
		} catch (Horde_Imap_Client_Exception $e) {
0 ignored issues
show
The class Horde_Imap_Client_Exception does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
300
			return [
301
				'id' => base64_encode($this->getFolderId()),
302
				'parent' => null,
303
				'name' => $displayName,
304
				'specialRole' => null,
305
				'unseen' => 0,
306
				'total' => 0,
307
				'error' => $e->getMessage(),
308
				'isEmpty' => true,
309
				'accountId' => $accountId,
310
				'noSelect' => true
311
			];
312
		}
313
	}
314
	/**
315
	 * Get the special use role of the mailbox
316
	 *
317
	 * This method reads the attributes sent by the server
318
	 *
319
	 */
320 12
	protected function getSpecialRoleFromAttributes() {
321
		/*
322
		 * @todo: support multiple attributes on same folder
323
		 * "any given server or  message store may support
324
		 *  any combination of the attributes"
325
		 *  https://tools.ietf.org/html/rfc6154
326
		 */
327 12
		$result = null;
328 12
		if (is_array($this->attributes)) {
329
			/* Convert attributes to lowercase, because gmail
330
			 * returns them as lowercase (eg. \trash and not \Trash)
331
			 */
332
			$specialUseAttributes = [
333 12
				strtolower(Horde_Imap_Client::SPECIALUSE_ALL),
334 12
				strtolower(Horde_Imap_Client::SPECIALUSE_ARCHIVE),
335 12
				strtolower(Horde_Imap_Client::SPECIALUSE_DRAFTS),
336 12
				strtolower(Horde_Imap_Client::SPECIALUSE_FLAGGED),
337 12
				strtolower(Horde_Imap_Client::SPECIALUSE_JUNK),
338 12
				strtolower(Horde_Imap_Client::SPECIALUSE_SENT),
339 12
				strtolower(Horde_Imap_Client::SPECIALUSE_TRASH)
340 12
			];
341
342 12
			$attributes = array_map(function($n) {
343 6
				return strtolower($n);
344 12
			}, $this->attributes);
345
346 12
			foreach ($specialUseAttributes as $attr)  {
347 12
				if (in_array($attr, $attributes)) {
348 6
					$result = ltrim($attr, '\\');
349 6
					break;
350
				}
351 12
			}
352
353 12
		}
354
355 12
		$this->specialRole = $result;
356 12
	}
357
358
	/**
359
	 * Assign a special role to this mailbox based on its name
360
	 */
361 12
	protected function guessSpecialRole() {
362
363
		$specialFoldersDict = [
364 12
			'inbox'   => ['inbox'],
365 12
			'sent'    => ['sent', 'sent items', 'sent messages', 'sent-mail', 'sentmail'],
366 12
			'drafts'  => ['draft', 'drafts'],
367 12
			'archive' => ['archive', 'archives'],
368 12
			'trash'   => ['deleted messages', 'trash'],
369 12
			'junk'    => ['junk', 'spam', 'bulk mail'],
370 12
		];
371
372 12
		$lowercaseExplode = explode($this->delimiter, $this->getFolderId(), 2);
373 12
		$lowercaseId = strtolower(array_pop($lowercaseExplode));
374 12
		$result = null;
375 12
		foreach ($specialFoldersDict as $specialRole => $specialNames) {
376 12
			if (in_array($lowercaseId, $specialNames)) {
377 6
				$result = $specialRole;
378 6
				break;
379
			}
380 12
		}
381
382 12
		$this->specialRole = $result;
383 12
	}
384
385
	/**
386
	 * @param int $messageId
387
	 * @param string $attachmentId
388
	 * @return Attachment
389
	 */
390
	public function getAttachment($messageId, $attachmentId) {
391
		return new Attachment($this->conn, $this->mailBox, $messageId, $attachmentId);
392
	}
393
394
	/**
395
	 * @param string $rawBody
396
	 * @param array $flags
397
	 */
398 6
	public function saveMessage($rawBody, $flags = []) {
399
400 6
		$this->conn->append($this->mailBox, [
401
			[
402 6
				'data' => $rawBody,
403
				'flags' => $flags
404 6
			]
405 6
		]);
406 6
	}
407
408
	/**
409
	 * Save draft
410
	 *
411
	 * @param string $rawBody
412
	 * @return int UID of the saved draft
413
	 */
414
	public function saveDraft($rawBody) {
415
416
		$uids = $this->conn->append($this->mailBox, [
417
			[
418
				'data' => $rawBody,
419
				'flags' => [
420
					Horde_Imap_Client::FLAG_DRAFT,
421
					Horde_Imap_Client::FLAG_SEEN
422
				]
423
			]
424
		]);
425
		return $uids->current();
426
	}
427
428
	/**
429
	 * @param int $uid
430
	 * @param string $flag
431
	 * @param boolean $add
432
	 */
433
	public function setMessageFlag($uid, $flag, $add) {
434
		$options = [
435
			'ids' => new Horde_Imap_Client_Ids($uid)
436
		];
437
		if ($add) {
438
			$options['add'] = [$flag];
439
		} else {
440
			$options['remove'] = [$flag];
441
		}
442
		$this->conn->store($this->mailBox, $options);
443
	}
444
445
	/**
446
	 * @param $fromUid
447
	 * @param $toUid
448
	 * @return array
449
	 */
450 3
	public function getMessagesSince($fromUid, $toUid) {
451 3
		$query = new Horde_Imap_Client_Search_Query();
452 3
		$query->ids(new Horde_Imap_Client_Ids("$fromUid:$toUid"));
453 3
		return $this->getMessages(-1, -1, $query);
454
	}
455
456
	/**
457
	 * @return Horde_Imap_Client_Mailbox
458
	 */
459
	public function getHordeMailBox() {
460
		return $this->mailBox;
461
	}
462
463
}
464