Skip to content

Commit

Permalink
Reduced queries in ReceiveEmail
Browse files Browse the repository at this point in the history
  • Loading branch information
willbrowningme committed Nov 17, 2022
1 parent 24b8286 commit 26b4704
Show file tree
Hide file tree
Showing 10 changed files with 401 additions and 218 deletions.
116 changes: 58 additions & 58 deletions app/Console/Commands/ReceiveEmail.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,13 @@ public function handle()
// Check whether this email is a reply/send from or a new email to be forwarded.
$destination = Str::replaceLast('=', '@', $recipient['extension']);
$validEmailDestination = filter_var($destination, FILTER_VALIDATE_EMAIL);
$verifiedRecipient = $user->getVerifiedRecipientByEmail($this->senderFrom);
if ($validEmailDestination) {
$verifiedRecipient = $user->getVerifiedRecipientByEmail($this->senderFrom);
} else {
$verifiedRecipient = null;
}

if ($validEmailDestination && $verifiedRecipient?->can_reply_send) {
if ($verifiedRecipient?->can_reply_send) {
// Check if the Dmarc allow or spam headers are present from Rspamd
if (! $this->parser->getHeader('X-AnonAddy-Dmarc-Allow') || $this->parser->getHeader('X-AnonAddy-Spam')) {
// Notify user and exit
Expand All @@ -152,18 +156,18 @@ public function handle()
exit(0);
}

if ($this->parser->getHeader('In-Reply-To')) {
$this->handleReply($user, $recipient);
if ($this->parser->getHeader('In-Reply-To') && $alias) {
$this->handleReply($user, $recipient, $alias);
} else {
$this->handleSendFrom($user, $recipient, $aliasable ?? null);
$this->handleSendFrom($user, $recipient, $alias ?? null, $aliasable ?? null);
}
} elseif ($validEmailDestination && $verifiedRecipient?->can_reply_send === false) {
} elseif ($verifiedRecipient?->can_reply_send === false) {
// Notify user that they have not allowed this recipient to reply and send from aliases
$verifiedRecipient->notify(new DisallowedReplySendAttempt($recipient, $this->senderFrom, $this->parser->getHeader('X-AnonAddy-Authentication-Results')));

exit(0);
} else {
$this->handleForward($user, $recipient, $aliasable ?? null);
$this->handleForward($user, $recipient, $alias ?? null, $aliasable ?? null);
}
}
} catch (\Exception $e) {
Expand All @@ -184,60 +188,53 @@ protected function handleUnsubscribe($recipient)
}
}

protected function handleReply($user, $recipient)
protected function handleReply($user, $recipient, $alias)
{
$alias = $user->aliases()->where('email', $recipient['local_part'] . '@' . $recipient['domain'])->first();

if ($alias) {
$sendTo = Str::replaceLast('=', '@', $recipient['extension']);
$sendTo = Str::replaceLast('=', '@', $recipient['extension']);

$emailData = new EmailData($this->parser, $this->size);
$emailData = new EmailData($this->parser, $this->option('sender'), $this->size);

$message = new ReplyToEmail($user, $alias, $emailData);
$message = new ReplyToEmail($user, $alias, $emailData);

Mail::to($sendTo)->queue($message);
}
Mail::to($sendTo)->queue($message);
}

protected function handleSendFrom($user, $recipient, $aliasable)
protected function handleSendFrom($user, $recipient, $alias, $aliasable)
{
$alias = $user->aliases()->withTrashed()->firstOrNew([
'email' => $recipient['local_part'] . '@' . $recipient['domain'],
'local_part' => $recipient['local_part'],
'domain' => $recipient['domain'],
'aliasable_id' => $aliasable->id ?? null,
'aliasable_type' => $aliasable ? 'App\\Models\\' . class_basename($aliasable) : null
]);

// This is a new alias but at a shared domain or the sender is not a verified recipient.
if (!isset($alias->id) && in_array($recipient['domain'], config('anonaddy.all_domains'))) {
exit(0);
if (is_null($alias)) {
$alias = $user->aliases()->create([
'email' => $recipient['local_part'] . '@' . $recipient['domain'],
'local_part' => $recipient['local_part'],
'domain' => $recipient['domain'],
'aliasable_id' => $aliasable?->id,
'aliasable_type' => $aliasable ? 'App\\Models\\' . class_basename($aliasable) : null
]);

// Hydrate all alias fields
$alias->refresh();
}

$alias->save();
$alias->refresh();

$sendTo = Str::replaceLast('=', '@', $recipient['extension']);

$emailData = new EmailData($this->parser, $this->size);
$emailData = new EmailData($this->parser, $this->option('sender'), $this->size);

$message = new SendFromEmail($user, $alias, $emailData);

Mail::to($sendTo)->queue($message);
}

protected function handleForward($user, $recipient, $aliasable)
protected function handleForward($user, $recipient, $alias, $aliasable)
{
$alias = $user->aliases()->withTrashed()->firstOrNew([
'email' => $recipient['local_part'] . '@' . $recipient['domain'],
'local_part' => $recipient['local_part'],
'domain' => $recipient['domain'],
'aliasable_id' => $aliasable->id ?? null,
'aliasable_type' => $aliasable ? 'App\\Models\\' . class_basename($aliasable) : null
]);

if (!isset($alias->id)) {
// This is a new alias.
if (is_null($alias)) {
// This is a new alias
$alias = new Alias([
'email' => $recipient['local_part'] . '@' . $recipient['domain'],
'local_part' => $recipient['local_part'],
'domain' => $recipient['domain'],
'aliasable_id' => $aliasable?->id,
'aliasable_type' => $aliasable ? 'App\\Models\\' . class_basename($aliasable) : null
]);

if ($user->hasExceededNewAliasLimit()) {
$this->error('4.2.1 New aliases per hour limit exceeded for user.');

Expand All @@ -250,26 +247,29 @@ protected function handleForward($user, $recipient, $aliasable)
$keys = explode('.', $recipient['extension']);

$recipientIds = $user
->recipients()
->oldest()
->get()
->filter(function ($item, $key) use ($keys) {
return in_array($key+1, $keys) && !is_null($item['email_verified_at']);
})
->pluck('id')
->take(10)
->toArray();
->recipients()
->select(['id','email_verified_at'])
->oldest()
->get()
->filter(function ($item, $key) use ($keys) {
return in_array($key + 1, $keys) && !is_null($item['email_verified_at']);
})
->pluck('id')
->take(10)
->toArray();
}
}

$alias->save();
$alias->refresh();
$user->aliases()->save($alias);

// Hydrate all alias fields
$alias->refresh();

if (isset($recipientIds)) {
$alias->recipients()->sync($recipientIds);
if (isset($recipientIds)) {
$alias->recipients()->sync($recipientIds);
}
}

$emailData = new EmailData($this->parser, $this->size);
$emailData = new EmailData($this->parser, $this->option('sender'), $this->size);

$alias->verifiedRecipientsOrDefault()->each(function ($recipient) use ($alias, $emailData) {
$message = new ForwardEmail($alias, $emailData, $recipient);
Expand Down
10 changes: 10 additions & 0 deletions app/Mail/ForwardEmail.php
Original file line number Diff line number Diff line change
Expand Up @@ -319,4 +319,14 @@ private function needsDkimSignature()
{
return $this->alias->isCustomDomain() ? $this->alias->aliasable->isVerifiedForSending() : false;
}

/**
* Override default buildSubject method that does not allow an empty subject.
*/
protected function buildSubject($message)
{
$message->subject($this->subject);

return $this;
}
}
10 changes: 10 additions & 0 deletions app/Mail/ReplyToEmail.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,14 @@ private function removeRealEmailAndHtmlBanner($html)
->replaceMatches('/(?s)((<|&lt;)!--banner-info--(&gt;|>)).*?((<|&lt;)!--banner-info--(&gt;|>))/mi', '')
->replaceMatches("/(?s)(<tr((?!<tr).)*?" . preg_quote(Str::of(config('app.url'))->after('://')->rtrim('/'), '/') . "(\/|%2F)deactivate(\/|%2F).*?\/tr>)/mi", '');
}

/**
* Override default buildSubject method that does not allow an empty subject.
*/
protected function buildSubject($message)
{
$message->subject($this->subject);

return $this;
}
}
10 changes: 10 additions & 0 deletions app/Mail/SendFromEmail.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,4 +204,14 @@ private function removeRealEmailAndHtmlBanner($html)
->replaceMatches('/(?s)((<|&lt;)!--banner-info--(&gt;|>)).*?((<|&lt;)!--banner-info--(&gt;|>))/mi', '')
->replaceMatches("/(?s)(<tr((?!<tr).)*?" . preg_quote(Str::of(config('app.url'))->after('://')->rtrim('/'), '/') . "(\/|%2F)deactivate(\/|%2F).*?\/tr>)/mi", '');
}

/**
* Override default buildSubject method that does not allow an empty subject.
*/
protected function buildSubject($message)
{
$message->subject($this->subject);

return $this;
}
}
18 changes: 11 additions & 7 deletions app/Models/Alias.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,18 +132,22 @@ public function verifiedRecipients()
*/
public function verifiedRecipientsOrDefault()
{
if ($this->verifiedRecipients()->count() === 0) {
$verifiedRecipients = $this
->verifiedRecipients()
->get();

if ($verifiedRecipients->count() === 0) {
// If the alias is for a custom domain or username that has a default recipient set.
if (isset($this->aliasable->defaultRecipient)) {
return $this->aliasable->defaultRecipient();
if ($this->aliasable_id) {
if (isset($this->aliasable->defaultRecipient)) {
return $this->aliasable->defaultRecipient();
}
}

return $this->user->defaultRecipient();
return $this->user->hasVerifiedDefaultRecipient() ? $this->user->defaultRecipient() : collect();
}

return $this
->verifiedRecipients()
->get();
return $verifiedRecipients;
}

/**
Expand Down
16 changes: 13 additions & 3 deletions app/Models/EmailData.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,19 @@ class EmailData
{
private static $mimeTypes;

public function __construct(Parser $parser, $size)
public function __construct(Parser $parser, $sender, $size)
{
$this->sender = $parser->getAddresses('from')[0]['address'];
if (isset($parser->getAddresses('from')[0]['address'])) {
if (filter_var($parser->getAddresses('from')[0]['address'], FILTER_VALIDATE_EMAIL)) {
$this->sender = $parser->getAddresses('from')[0]['address'];
}
}

// If we can't get a From header address then use the envelope from
if (! isset($this->sender)) {
$this->sender = $sender;
}

$this->display_from = base64_encode($parser->getAddresses('from')[0]['display']);
if (isset($parser->getAddresses('reply-to')[0])) {
$this->reply_to_address = $parser->getAddresses('reply-to')[0]['address'];
Expand All @@ -36,7 +46,7 @@ public function __construct(Parser $parser, $size)
$this->listUnsubscribe = base64_encode($parser->getHeader('List-Unsubscribe'));
$this->inReplyTo = base64_encode($parser->getHeader('In-Reply-To'));
$this->references = base64_encode($parser->getHeader('References'));
$this->originalEnvelopeFrom = $this->sender;
$this->originalEnvelopeFrom = $sender;
$this->originalFromHeader = base64_encode($parser->getHeader('From'));
$this->originalReplyToHeader = base64_encode($parser->getHeader('Reply-To'));
$this->originalSenderHeader = base64_encode($parser->getHeader('Sender'));
Expand Down
6 changes: 2 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,9 @@
"laravel/ui": "^4.0",
"maatwebsite/excel": "^3.1",
"mews/captcha": "^3.0.0",
"php-mime-mail-parser/php-mime-mail-parser": "^7.0",
"php-mime-mail-parser/php-mime-mail-parser": "^8.0",
"pragmarx/google2fa-laravel": "^2.0.0",
"ramsey/uuid": "^4.0",
"web-auth/cose-lib": "v4.0.5",
"web-auth/webauthn-lib": "v4.0.5"
"ramsey/uuid": "^4.0"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",
Expand Down
Loading

0 comments on commit 26b4704

Please sign in to comment.