Completed
Push — master ( ca9814...748fd4 )
by
unknown
41:44
created

MessagesController::attachmentIsImage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
ccs 0
cts 6
cp 0
rs 9.4285
cc 1
eloc 6
nc 1
nop 1
crap 2
1
<?php
2
/**
3
 * @author Alexander Weidinger <[email protected]>
4
 * @author Christoph Wurst <[email protected]>
5
 * @author Christoph Wurst <[email protected]>
6
 * @author Jakob Sack <[email protected]>
7
 * @author Jan-Christoph Borchardt <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Scrutinizer Auto-Fixer <[email protected]>
10
 * @author Thomas Imbreckx <[email protected]>
11
 * @author Thomas Müller <[email protected]>
12
 *
13
 * ownCloud - Mail
14
 *
15
 * This code is free software: you can redistribute it and/or modify
16
 * it under the terms of the GNU Affero General Public License, version 3,
17
 * as published by the Free Software Foundation.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License, version 3,
25
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
26
 *
27
 */
28
29
namespace OCA\Mail\Controller;
30
31
use OCA\Mail\Http\AttachmentDownloadResponse;
32
use OCA\Mail\Http\HtmlResponse;
33
use OCA\Mail\Service\AccountService;
34
use OCA\Mail\Service\ContactsIntegration;
35
use OCA\Mail\Service\IAccount;
36
use OCA\Mail\Service\IMailBox;
37
use OCA\Mail\Service\Logger;
38
use OCA\Mail\Service\UnifiedAccount;
39
use OCP\AppFramework\Controller;
40
use OCP\AppFramework\Db\DoesNotExistException;
41
use OCP\AppFramework\Http;
42
use OCP\AppFramework\Http\ContentSecurityPolicy;
43
use OCP\AppFramework\Http\JSONResponse;
44
use OCP\AppFramework\Http\TemplateResponse;
45
use OCP\IL10N;
46
use OCP\IRequest;
47
use OCP\Util;
48
49
class MessagesController extends Controller {
50
51
	/** @var AccountService */
52
	private $accountService;
53
54
	/**
55
	 * @var string
56
	 */
57
	private $currentUserId;
58
59
	/**
60
	 * @var ContactsIntegration
61
	 */
62
	private $contactsIntegration;
63
64
	/**
65
	 * @var \OCA\Mail\Service\Logger
66
	 */
67
	private $logger;
68
69
	/**
70
	 * @var \OCP\Files\Folder
71
	 */
72
	private $userFolder;
73
74
	/**
75
	 * @var IL10N
76
	 */
77
	private $l10n;
78
79
	/**
80
	 * @var IAccount[]
81
	 */
82
	private $accounts = [];
83
84
	/**
85
	 * @param string $appName
86
	 * @param IRequest $request
87
	 * @param AccountService $accountService
88
	 * @param string $UserId
89
	 * @param $userFolder
90
	 * @param ContactsIntegration $contactsIntegration
91
	 * @param Logger $logger
92
	 * @param IL10N $l10n
93
	 */
94 12
	public function __construct($appName,
95
								IRequest $request,
96
								AccountService $accountService,
97
								$UserId,
98
								$userFolder,
99
								ContactsIntegration $contactsIntegration,
100
								Logger $logger,
101
								IL10N $l10n) {
102 12
		parent::__construct($appName, $request);
103 12
		$this->accountService = $accountService;
104 12
		$this->currentUserId = $UserId;
105 12
		$this->userFolder = $userFolder;
106 12
		$this->contactsIntegration = $contactsIntegration;
107 12
		$this->logger = $logger;
108 12
		$this->l10n = $l10n;
109 12
	}
110
111
	/**
112
	 * @NoAdminRequired
113
	 * @NoCSRFRequired
114
	 *
115
	 * @param int $accountId
116
	 * @param string $folderId
117
	 * @param int $from
118
	 * @param int $to
119
	 * @param string $filter
120
	 * @param array $ids
121
	 * @return JSONResponse
122
	 */
123
	public function index($accountId, $folderId, $from=0, $to=20, $filter=null, $ids=null) {
124
		if (!is_null($ids)) {
125
			$ids = explode(',', $ids);
126
127
			return $this->loadMultiple($accountId, $folderId, $ids);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->loadMultip...ntId, $folderId, $ids); (array) is incompatible with the return type documented by OCA\Mail\Controller\MessagesController::index of type OCP\AppFramework\Http\JSONResponse.

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...
128
		}
129
		$mailBox = $this->getFolder($accountId, $folderId);
130
131
		$this->logger->debug("loading messages $from to $to of folder <$folderId>");
132
133
		$json = $mailBox->getMessages($from, $to-$from+1, $filter);
134
135
		$ci = $this->contactsIntegration;
136
		$json = array_map(function($j) use ($ci, $mailBox) {
137
			if ($mailBox->getSpecialRole() === 'trash') {
138
				$j['delete'] = (string)$this->l10n->t('Delete permanently');
139
			}
140
141
			if ($mailBox->getSpecialRole() === 'sent') {
142
				$j['fromEmail'] = $j['toEmail'];
143
				$j['from'] = $j['to'];
144
				if((count($j['toList']) > 1) || (count($j['ccList']) > 0)) {
145
					$j['from'] .= ' ' . $this->l10n->t('& others');
146
				}
147
			}
148
149
			$j['senderImage'] = $ci->getPhoto($j['fromEmail']);
150
			return $j;
151
		}, $json);
152
153
		return new JSONResponse($json);
154
	}
155
156
	private function loadMessage($accountId, $folderId, $id) {
157
		$account = $this->getAccount($accountId);
158
		$mailBox = $account->getMailbox(base64_decode($folderId));
159
		$m = $mailBox->getMessage($id);
160
161
		$json = $this->enhanceMessage($accountId, $folderId, $id, $m, $account, $mailBox);
162
163
		// Unified inbox hack
164
		$messageId = $id;
165
		if ($accountId === UnifiedAccount::ID) {
166
			// Add accountId, folderId for unified inbox replies
167
			list($accountId, $messageId) = json_decode(base64_decode($id));
168
			$account = $this->getAccount($accountId);
169
			$inbox = $account->getInbox();
170
			$folderId = base64_encode($inbox->getFolderId());
171
		}
172
		$json['messageId'] = $messageId;
173
		$json['accountId'] = $accountId;
174
		$json['folderId'] = $folderId;
175
		// End unified inbox hack
176
177
		return $json;
178
	}
179
180
	/**
181
	 * @NoAdminRequired
182
	 * @NoCSRFRequired
183
	 *
184
	 * @param int $accountId
185
	 * @param string $folderId
186
	 * @param mixed $id
187
	 * @return JSONResponse
188
	 */
189
	public function show($accountId, $folderId, $id) {
190
		try {
191
			$json = $this->loadMessage($accountId, $folderId, $id);
192
		} catch (DoesNotExistException $ex) {
0 ignored issues
show
Bug introduced by
The class OCP\AppFramework\Db\DoesNotExistException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
193
			return new JSONResponse([], 404);
194
		}
195
		return new JSONResponse($json);
196
	}
197
198
	/**
199
	 * @NoAdminRequired
200
	 * @NoCSRFRequired
201
	 *
202
	 * @param int $accountId
203
	 * @param string $folderId
204
	 * @param string $messageId
205
	 * @return \OCA\Mail\Http\HtmlResponse
206
	 */
207 1
	public function getHtmlBody($accountId, $folderId, $messageId) {
208
		try {
209 1
			$mailBox = $this->getFolder($accountId, $folderId);
210
211 1
			$m = $mailBox->getMessage($messageId, true);
0 ignored issues
show
Unused Code introduced by
The call to IMailBox::getMessage() has too many arguments starting with true.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
212
			$html = $m->getHtmlBody($accountId, $folderId, $messageId, function($cid) use ($m){
213
				$match = array_filter($m->attachments, function($a) use($cid){
214
					return $a['cid'] === $cid;
215
				});
216
				$match = array_shift($match);
217
				if (is_null($match)) {
218
					return null;
219
				}
220
				return $match['id'];
221 1
			});
222
223 1
			$htmlResponse = new HtmlResponse($html);
224
225
			// Harden the default security policy
226
			// FIXME: Remove once ownCloud 8.1 is a requirement for the mail app
227 1 View Code Duplication
			if(class_exists('\OCP\AppFramework\Http\ContentSecurityPolicy')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
228 1
				$policy = new ContentSecurityPolicy();
229 1
				$policy->allowEvalScript(false);
230 1
				$policy->disallowScriptDomain('\'self\'');
231 1
				$policy->disallowConnectDomain('\'self\'');
232 1
				$policy->disallowFontDomain('\'self\'');
233 1
				$policy->disallowMediaDomain('\'self\'');
234 1
				$htmlResponse->setContentSecurityPolicy($policy);
235 1
			}
236
237
			// Enable caching
238 1
			$htmlResponse->cacheFor(60 * 60);
239 1
			$htmlResponse->addHeader('Pragma', 'cache');
240
241 1
			return $htmlResponse;
242
		} catch(\Exception $ex) {
243
			return new TemplateResponse($this->appName, 'error', ['message' => $ex->getMessage()], 'none');
244
		}
245
	}
246
247
	/**
248
	 * @NoAdminRequired
249
	 * @NoCSRFRequired
250
	 *
251
	 * @param int $accountId
252
	 * @param string $folderId
253
	 * @param string $messageId
254
	 * @param string $attachmentId
255
	 * @return AttachmentDownloadResponse
256
	 */
257 1
	public function downloadAttachment($accountId, $folderId, $messageId, $attachmentId) {
258 1
		$mailBox = $this->getFolder($accountId, $folderId);
259
260 1
		$attachment = $mailBox->getAttachment($messageId, $attachmentId);
261
262 1
		return new AttachmentDownloadResponse(
263 1
			$attachment->getContents(),
264 1
			$attachment->getName(),
265 1
			$attachment->getType());
266
	}
267
268
	/**
269
	 * @NoAdminRequired
270
	 * @NoCSRFRequired
271
	 *
272
	 * @param int $accountId
273
	 * @param string $folderId
274
	 * @param string $messageId
275
	 * @param int $attachmentId
276
	 * @param string $targetPath
277
	 * @return JSONResponse
278
	 */
279 2
	public function saveAttachment($accountId, $folderId, $messageId, $attachmentId, $targetPath) {
280 2
		$mailBox = $this->getFolder($accountId, $folderId);
281
282 2
		$attachmentIds = [];
0 ignored issues
show
Unused Code introduced by
$attachmentIds is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
283 2
		if($attachmentId === 0) {
284
			// Save all attachments
285 1
			$m = $mailBox->getMessage($messageId);
286
			$attachmentIds = array_map(function($a){
287 1
				return $a['id'];
288 1
			}, $m->attachments);
289 1
		} else {
290 1
			$attachmentIds = [$attachmentId];
291
		}
292
293 2
		foreach($attachmentIds as $attachmentId) {
294 2
			$attachment = $mailBox->getAttachment($messageId, $attachmentId);
295
296 2
			$fileName = $attachment->getName();
297 2
			$fileParts = pathinfo($fileName);
298 2
			$fileName = $fileParts['filename'];
299 2
			$fileExtension = $fileParts['extension'];
300 2
			$fullPath = "$targetPath/$fileName.$fileExtension";
301 2
			$counter = 2;
302 2
			while($this->userFolder->nodeExists($fullPath)) {
303
				$fullPath = "$targetPath/$fileName ($counter).$fileExtension";
304
				$counter++;
305
			}
306
307 2
			$newFile = $this->userFolder->newFile($fullPath);
308 2
			$newFile->putContent($attachment->getContents());
309 2
		}
310
311 2
		return new JSONResponse();
312
	}
313
314
	/**
315
	 * @NoAdminRequired
316
	 *
317
	 * @param int $accountId
318
	 * @param string $folderId
319
	 * @param string $messageId
320
	 * @param array $flags
321
	 * @return JSONResponse
322
	 */
323 2
	public function setFlags($accountId, $folderId, $messageId, $flags) {
324 2
		$mailBox = $this->getFolder($accountId, $folderId);
325
326 2
		foreach($flags as $flag => $value) {
327 2
			$value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
328 2
			if ($flag === 'unseen') {
329 1
				$flag = 'seen';
330 1
				$value = !$value;
331 1
			}
332 2
			$mailBox->setMessageFlag($messageId, '\\'.$flag, $value);
333 2
		}
334
335 2
		return new JSONResponse();
336
	}
337
338
	/**
339
	 * @NoAdminRequired
340
	 *
341
	 * @param int $accountId
342
	 * @param string $folderId
343
	 * @param string $id
344
	 * @return JSONResponse
345
	 */
346 3
	public function destroy($accountId, $folderId, $id) {
347 3
		$this->logger->debug("deleting message <$id> of folder <$folderId>, account <$accountId>");
348
		try {
349 3
			$account = $this->getAccount($accountId);
350 2
			$account->deleteMessage(base64_decode($folderId), $id);
351 1
			return new JSONResponse();
352
353 2
		} catch (DoesNotExistException $e) {
0 ignored issues
show
Bug introduced by
The class OCP\AppFramework\Db\DoesNotExistException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
354 2
			$this->logger->error("could not delete message <$id> of folder <$folderId>, "
355 2
				. "account <$accountId> because it does not exist");
356 2
			return new JSONResponse([], Http::STATUS_NOT_FOUND);
357
		}
358
	}
359
360
	/**
361
	 * @param int $accountId
362
	 * @return \OCA\Mail\Service\IAccount
363
	 */
364 9
	private function getAccount($accountId) {
365 9
		if (!array_key_exists($accountId, $this->accounts)) {
366 9
			$this->accounts[$accountId] = $this->accountService->find($this->currentUserId, $accountId);
367 8
		}
368 8
		return $this->accounts[$accountId];
369
	}
370
371
	/**
372
	 * @param int $accountId
373
	 * @param string $folderId
374
	 * @return IMailBox
375
	 */
376 6
	private function getFolder($accountId, $folderId) {
377 6
		$account = $this->getAccount($accountId);
378 6
		return $account->getMailbox(base64_decode($folderId));
379
	}
380
381
	/**
382
	 * @param string $messageId
383
	 * @param $accountId
384
	 * @param $folderId
385
	 * @return callable
386
	 */
387
	private function enrichDownloadUrl($accountId, $folderId, $messageId, $attachment) {
388
		$downloadUrl = \OCP\Util::linkToRoute('mail.messages.downloadAttachment', [
389
			'accountId' => $accountId,
390
			'folderId' => $folderId,
391
			'messageId' => $messageId,
392
			'attachmentId' => $attachment['id'],
393
		]);
394
		$downloadUrl = \OC::$server->getURLGenerator()->getAbsoluteURL($downloadUrl);
395
		$attachment['downloadUrl'] = $downloadUrl;
396
		$attachment['mimeUrl'] = $this->mimeTypeIcon($attachment['mime']);
397
398
		if ($this->attachmentIsImage($attachment)) {
399
			$attachment['isImage'] = true;
400
		} else if ($this->attachmentIsCalendarEvent($attachment)) {
401
			$attachment['isCalendarEvent'] = true;
402
		}
403
		return $attachment;
404
	}
405
406
	/**
407
	 * @param $attachment
408
	 *
409
	 * Determines if the content of this attachment is an image
410
	 *
411
	 * @return boolean
412
	 */
413
	private function attachmentIsImage($attachment) {
414
		return in_array(
415
			$attachment['mime'], [
416
			'image/jpeg',
417
			'image/png',
418
			'image/gif'
419
		]);
420
	}
421
422
	/**
423
	 * @param type $attachment
424
	 * @return boolean
425
	 */
426
	private function attachmentIsCalendarEvent($attachment) {
427
		return $attachment['mime'] === 'text/calendar';
428
	}
429
430
	/**
431
	 * @param string $accountId
432
	 * @param string $folderId
433
	 * @param string $messageId
434
	 * @return string
435
	 */
436
	private function buildHtmlBodyUrl($accountId, $folderId, $messageId) {
437
		$htmlBodyUrl = \OC::$server->getURLGenerator()->linkToRoute('mail.messages.getHtmlBody', [
438
			'accountId' => $accountId,
439
			'folderId' => $folderId,
440
			'messageId' => $messageId,
441
		]);
442
		return \OC::$server->getURLGenerator()->getAbsoluteURL($htmlBodyUrl);
443
	}
444
445
	/**
446
	 * @param integer $accountId
447
	 * @param string $folderId
448
	 */
449
	private function loadMultiple($accountId, $folderId, $ids) {
450
		$messages = array_map(function($id) use ($accountId, $folderId){
451
			try {
452
				return $this->loadMessage($accountId, $folderId, $id);
453
			} catch (DoesNotExistException $ex) {
0 ignored issues
show
Bug introduced by
The class OCP\AppFramework\Db\DoesNotExistException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
454
				return null;
455
			}
456
		}, $ids);
457
458
		return $messages;
459
	}
460
461
	/**
462
	 * @param $accountId
463
	 * @param $folderId
464
	 * @param $id
465
	 * @param $m
466
	 * @param IAccount $account
467
	 * @param IMailBox $mailBox
468
	 * @return mixed
469
	 */
470
	private function enhanceMessage($accountId, $folderId, $id, $m, IAccount $account, $mailBox) {
471
		$json = $m->getFullMessage($account->getEmail(), $mailBox->getSpecialRole());
472
		$json['senderImage'] = $this->contactsIntegration->getPhoto($m->getFromEmail());
473
		if (isset($json['hasHtmlBody'])) {
474
			$json['htmlBodyUrl'] = $this->buildHtmlBodyUrl($accountId, $folderId, $id);
475
		}
476
477
		if (isset($json['attachments'])) {
478
			$json['attachments'] = array_map(function ($a) use ($accountId, $folderId, $id) {
479
				return $this->enrichDownloadUrl($accountId, $folderId, $id, $a);
480
			}, $json['attachments']);
481
482
			// show images first
483
			usort($json['attachments'], function($a, $b) {
484
				if (isset($a['isImage']) && !isset($b['isImage'])) {
485
					return -1;
486
				} elseif (!isset($a['isImage']) && isset($b['isImage'])) {
487
					return 1;
488
				} else {
489
					Util::naturalSortCompare($a['fileName'], $b['fileName']);
490
				}
491
			});
492
			return $json;
493
		}
494
		return $json;
495
	}
496
497
	/**
498
	 * Get path to the icon of a file type
499
	 *
500
	 * @todo Inject IMimeTypeDetector once core 8.2+ is supported
501
	 *
502
	 * @param string $mimeType the MIME type
503
	 */
504
	private function mimeTypeIcon($mimeType) {
505
		$ocVersion = \OC::$server->getConfig()->getSystemValue('version', '0.0.0');
506
		if (version_compare($ocVersion, '8.2.0', '<')) {
507
			// Version-hack for 8.1 and lower
508
			return \OC_Helper::mimetypeIcon($mimeType);
509
		}
510
		/* @var IMimeTypeDetector */
511
		$mimeTypeDetector = \OC::$server->getMimeTypeDetector();
512
		return $mimeTypeDetector->mimeTypeIcon($mimeType);
513
	}
514
515
}
516