Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

link checking #9684

Closed
wants to merge 15 commits into from
Closed
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"bytestream/horde-util": "^2.7.0",
"cerdic/css-tidy": "v2.1.0",
"ezyang/htmlpurifier": "4.17.0",
"glenscott/url-normalizer": "^1.4",
"gravatarphp/gravatar": "dev-master#6b9f6a45477ce48285738d9d0c3f0dbf97abe263",
"hamza221/html2text": "^1.0",
"nextcloud/horde-managesieve": "^1.0",
Expand Down
43 changes: 42 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions lib/Address.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,27 @@ public function getLabel(): ?string {
// Fallback
return $this->getEmail();
}
$personal = trim(explode('<', $personal)[0]); // Remove the email part if present
return $personal;
}

/**
* @return string|null
*/
public function getCustomEmail(): ?string {
$personal = $this->wrapped->personal;
if ($personal === null) {
// Fallback
return null;
}
$parts = explode('<', $personal);
if (count($parts) === 1) {
return null;
}
$customEmail = trim($parts[1], '>');
return $customEmail;
}

/**
* @return string|null
*/
Expand Down
19 changes: 15 additions & 4 deletions lib/IMAP/ImapMessageFetcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
use OCA\Mail\IMAP\Charset\Converter;
use OCA\Mail\Model\IMAPMessage;
use OCA\Mail\Service\Html;
use OCA\Mail\Service\PhishingDetection\PhishingDetectionService;
use OCA\Mail\Service\SmimeService;
use OCP\AppFramework\Db\DoesNotExistException;
use function str_starts_with;
Expand All @@ -53,6 +54,7 @@ class ImapMessageFetcher {

private Html $htmlService;
private SmimeService $smimeService;
private PhishingDetectionService $phishingDetectionService;
private string $userId;

// Conditional fetching/parsing
Expand All @@ -71,6 +73,7 @@ class ImapMessageFetcher {
private string $rawReferences = '';
private string $dispositionNotificationTo = '';
private bool $hasDkimSignature = false;
private array $phishingDetails = [];
private ?string $unsubscribeUrl = null;
private bool $isOneClickUnsubscribe = false;
private ?string $unsubscribeMailto = null;
Expand All @@ -81,13 +84,16 @@ public function __construct(int $uid,
string $userId,
Html $htmlService,
SmimeService $smimeService,
private Converter $converter) {
private Converter $converter,
PhishingDetectionService $phishingDetectionService,
) {
$this->uid = $uid;
$this->mailbox = $mailbox;
$this->client = $client;
$this->userId = $userId;
$this->htmlService = $htmlService;
$this->smimeService = $smimeService;
$this->phishingDetectionService = $phishingDetectionService;
}


Expand Down Expand Up @@ -115,7 +121,7 @@ public function withBody(bool $value): ImapMessageFetcher {
* @throws Horde_Mime_Exception
* @throws ServiceException
*/
public function fetchMessage(?Horde_Imap_Client_Data_Fetch $fetch = null): IMAPMessage {
public function fetchMessage(?Horde_Imap_Client_Data_Fetch $fetch = null, bool $runPhishingCheck = false): IMAPMessage {
$ids = new Horde_Imap_Client_Ids($this->uid);

$isSigned = false;
Expand Down Expand Up @@ -231,7 +237,7 @@ public function fetchMessage(?Horde_Imap_Client_Data_Fetch $fetch = null): IMAPM
}
}

$this->parseHeaders($fetch);
$this->parseHeaders($fetch, $runPhishingCheck);

$envelope = $fetch->getEnvelope();
return new IMAPMessage(
Expand All @@ -255,6 +261,7 @@ public function fetchMessage(?Horde_Imap_Client_Data_Fetch $fetch = null): IMAPM
$this->rawReferences,
$this->dispositionNotificationTo,
$this->hasDkimSignature,
$this->phishingDetails,
$this->unsubscribeUrl,
$this->isOneClickUnsubscribe,
$this->unsubscribeMailto,
Expand Down Expand Up @@ -493,7 +500,7 @@ private function decodeSubject(Horde_Imap_Client_Data_Envelope $envelope): strin
return iconv("UTF-8", "UTF-8//IGNORE", $subject);
}

private function parseHeaders(Horde_Imap_Client_Data_Fetch $fetch): void {
private function parseHeaders(Horde_Imap_Client_Data_Fetch $fetch, bool $runPhishingCheck = false): void {
/** @var resource $headersStream */
$headersStream = $fetch->getHeaderText('0', Horde_Imap_Client_Data_Fetch::HEADER_STREAM);
$parsedHeaders = Horde_Mime_Headers::parseHeaders($headersStream);
Expand All @@ -512,6 +519,10 @@ private function parseHeaders(Horde_Imap_Client_Data_Fetch $fetch): void {
$dkimSignatureHeader = $parsedHeaders->getHeader('dkim-signature');
$this->hasDkimSignature = $dkimSignatureHeader !== null;

if($runPhishingCheck) {
$this->phishingDetails = $this->phishingDetectionService->checkHeadersForPhishing($parsedHeaders, $this->userId, $this->hasHtmlMessage, $this->htmlMessage);
}

$listUnsubscribeHeader = $parsedHeaders->getHeader('list-unsubscribe');
if ($listUnsubscribeHeader !== null) {
$listHeaders = new Horde_ListHeaders();
Expand Down
7 changes: 6 additions & 1 deletion lib/IMAP/ImapMessageFetcherFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,23 @@
use Horde_Imap_Client_Base;
use OCA\Mail\IMAP\Charset\Converter;
use OCA\Mail\Service\Html;
use OCA\Mail\Service\PhishingDetection\PhishingDetectionService;
use OCA\Mail\Service\SmimeService;

class ImapMessageFetcherFactory {
private Html $htmlService;
private SmimeService $smimeService;
private Converter $charsetConverter;
private PhishingDetectionService $phishingDetectionService;

public function __construct(Html $htmlService,
SmimeService $smimeService,
Converter $charsetConverter) {
Converter $charsetConverter,
PhishingDetectionService $phishingDetectionService) {
$this->htmlService = $htmlService;
$this->smimeService = $smimeService;
$this->charsetConverter = $charsetConverter;
$this->phishingDetectionService = $phishingDetectionService;
}

public function build(int $uid,
Expand All @@ -56,6 +60,7 @@ public function build(int $uid,
$this->htmlService,
$this->smimeService,
$this->charsetConverter,
$this->phishingDetectionService,
);
}
}
9 changes: 5 additions & 4 deletions lib/IMAP/MessageMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public function find(Horde_Imap_Client_Base $client,
int $id,
string $userId,
bool $loadBody = false): IMAPMessage {
$result = $this->findByIds($client, $mailbox, new Horde_Imap_Client_Ids([$id]), $userId, $loadBody);
$result = $this->findByIds($client, $mailbox, new Horde_Imap_Client_Ids([$id]), $userId, $loadBody, true);

if (count($result) === 0) {
throw new DoesNotExistException("Message does not exist");
Expand Down Expand Up @@ -264,7 +264,8 @@ public function findByIds(Horde_Imap_Client_Base $client,
string $mailbox,
$ids,
string $userId,
bool $loadBody = false): array {
bool $loadBody = false,
bool $runPhishingCheck = false): array {
$query = new Horde_Imap_Client_Fetch_Query();
$query->envelope();
$query->flags();
Expand Down Expand Up @@ -309,7 +310,7 @@ public function findByIds(Horde_Imap_Client_Base $client,
$this->logger->debug("findByIds in $mailbox got " . count($ids) . " UIDs ($range) and found " . count($fetchResults) . ". minFetched=$minFetched maxFetched=$maxFetched");
}

return array_map(function (Horde_Imap_Client_Data_Fetch $fetchResult) use ($client, $mailbox, $loadBody, $userId) {
return array_map(function (Horde_Imap_Client_Data_Fetch $fetchResult) use ($client, $mailbox, $loadBody, $userId, $runPhishingCheck) {
return $this->imapMessageFactory
->build(
$fetchResult->getUid(),
Expand All @@ -318,7 +319,7 @@ public function findByIds(Horde_Imap_Client_Base $client,
$userId,
)
->withBody($loadBody)
->fetchMessage($fetchResult);
->fetchMessage($fetchResult, $runPhishingCheck);
}, $fetchResults);
}

Expand Down
4 changes: 4 additions & 0 deletions lib/Model/IMAPMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class IMAPMessage implements IMessage, JsonSerializable {
private string $rawReferences;
private string $dispositionNotificationTo;
private bool $hasDkimSignature;
private array $phishingDetails;
private ?string $unsubscribeUrl;
private bool $isOneClickUnsubscribe;
private ?string $unsubscribeMailto;
Expand Down Expand Up @@ -105,6 +106,7 @@ public function __construct(int $uid,
string $rawReferences,
string $dispositionNotificationTo,
bool $hasDkimSignature,
array $phishingDetails,
?string $unsubscribeUrl,
bool $isOneClickUnsubscribe,
?string $unsubscribeMailto,
Expand Down Expand Up @@ -133,6 +135,7 @@ public function __construct(int $uid,
$this->rawReferences = $rawReferences;
$this->dispositionNotificationTo = $dispositionNotificationTo;
$this->hasDkimSignature = $hasDkimSignature;
$this->phishingDetails = $phishingDetails;
$this->unsubscribeUrl = $unsubscribeUrl;
$this->isOneClickUnsubscribe = $isOneClickUnsubscribe;
$this->unsubscribeMailto = $unsubscribeMailto;
Expand Down Expand Up @@ -319,6 +322,7 @@ public function jsonSerialize() {
'hasHtmlBody' => $this->hasHtmlMessage,
'dispositionNotificationTo' => $this->getDispositionNotificationTo(),
'hasDkimSignature' => $this->hasDkimSignature,
'phishingDetails' => $this->phishingDetails,
'unsubscribeUrl' => $this->unsubscribeUrl,
'isOneClickUnsubscribe' => $this->isOneClickUnsubscribe,
'unsubscribeMailto' => $this->unsubscribeMailto,
Expand Down
69 changes: 69 additions & 0 deletions lib/PhishingDetectionList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

/**
* @copyright 2024 Hamza Mahjoubi <hamza.mahjoubi221@proton.me>
*
* @author 2024 Hamza Mahjoubi <hamza.mahjoubi221@proton.me>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Mail;

use JsonSerializable;
use ReturnTypeWillChange;

/**
* @psalm-immutable
*/
class PhishingDetectionList implements JsonSerializable {

/** @var PhishingDetectionResult[] */
private $checks;

private bool $warning = false;

public function __construct(array $checks = []) {
$this->checks = $checks;
}

public function addCheck(PhishingDetectionResult $check) {
$this->checks[] = $check;

Check failure on line 47 in lib/PhishingDetectionList.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-master

InaccessibleProperty

lib/PhishingDetectionList.php:47:3: InaccessibleProperty: OCA\Mail\PhishingDetectionList::$checks is marked readonly (see https://psalm.dev/054)

Check failure on line 47 in lib/PhishingDetectionList.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable28

InaccessibleProperty

lib/PhishingDetectionList.php:47:3: InaccessibleProperty: OCA\Mail\PhishingDetectionList::$checks is marked readonly (see https://psalm.dev/054)

Check failure on line 47 in lib/PhishingDetectionList.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable27

InaccessibleProperty

lib/PhishingDetectionList.php:47:3: InaccessibleProperty: OCA\Mail\PhishingDetectionList::$checks is marked readonly (see https://psalm.dev/054)

Check failure on line 47 in lib/PhishingDetectionList.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable29

InaccessibleProperty

lib/PhishingDetectionList.php:47:3: InaccessibleProperty: OCA\Mail\PhishingDetectionList::$checks is marked readonly (see https://psalm.dev/054)
}
private function isWarning() {
foreach ($this->checks as $check) {
if (in_array($check->getType(), [PhishingDetectionResult::DATE_CHECK, PhishingDetectionResult::LINK_CHECK, PhishingDetectionResult::CUSTOM_EMAIL_CHECK, PhishingDetectionResult::CONTACTS_CHECK]) && $check->isPhishing()) {
return true;
}
}
return false;
}

#[ReturnTypeWillChange]
public function jsonSerialize() {
$result = array_map(static function (PhishingDetectionResult $check) {
return $check->jsonSerialize();
}, $this->checks);
return [
'checks' => $result,
'warning' => $this->isWarning(),
];
}

}
Loading
Loading