Skip to content

Commit dd4460e

Browse files
committed
fix: normalize mail subjects to ASCII
Signed-off-by: Vitor Mattos <[email protected]>
1 parent 02cb032 commit dd4460e

2 files changed

Lines changed: 86 additions & 5 deletions

File tree

lib/Service/MailService.php

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
class MailService {
2222
/** @var array */
2323
private $files = [];
24+
private const FALLBACK_MAIL_SUBJECT = 'LibreSign: Code to sign file';
2425

2526
public function __construct(
2627
private LoggerInterface $logger,
@@ -42,13 +43,34 @@ private function getFileById(int $fileId): File {
4243
return $this->files[$fileId];
4344
}
4445

46+
private function normalizeMailSubject(string $subject): string {
47+
if (preg_match('/^[\x20-\x7E]+$/', $subject) === 1) {
48+
return $subject;
49+
}
50+
51+
$normalized = $subject;
52+
if (class_exists(\Normalizer::class)) {
53+
$normalized = \Normalizer::normalize($normalized, \Normalizer::FORM_KD) ?: $normalized;
54+
$normalized = preg_replace('/\p{Mn}+/u', '', $normalized) ?: $normalized;
55+
}
56+
57+
$ascii = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $normalized);
58+
if (!is_string($ascii) || trim($ascii) === '') {
59+
return self::FALLBACK_MAIL_SUBJECT;
60+
}
61+
62+
$ascii = preg_replace('/[^\x20-\x7E]/', '', $ascii) ?: '';
63+
$ascii = trim(preg_replace('/\s+/', ' ', $ascii) ?: '');
64+
return $ascii !== '' ? $ascii : self::FALLBACK_MAIL_SUBJECT;
65+
}
66+
4567
/**
4668
* @psalm-suppress MixedMethodCall
4769
*/
4870
public function notifySignDataUpdated(SignRequest $data, string $email, ?string $description = null): void {
4971
$emailTemplate = $this->mailer->createEMailTemplate('settings.TestEmail');
5072
// TRANSLATORS The subject of the email that is sent after changes are made to the signature request that may affect something for the signer who will sign the document. Some possible reasons: URL for signature changed (when the URL expires), the person who requested the signature sent a notification
51-
$emailTemplate->setSubject($this->l10n->t('LibreSign: Changes into a file for you to sign'));
73+
$emailTemplate->setSubject($this->normalizeMailSubject($this->l10n->t('LibreSign: Changes into a file for you to sign')));
5274
$emailTemplate->addHeader();
5375
$emailTemplate->addHeading($this->l10n->t('File to sign'), false);
5476

@@ -84,7 +106,7 @@ public function notifySignDataUpdated(SignRequest $data, string $email, ?string
84106
*/
85107
public function notifyUnsignedUser(SignRequest $data, string $email, ?string $description = null): void {
86108
$emailTemplate = $this->mailer->createEMailTemplate('settings.TestEmail');
87-
$emailTemplate->setSubject($this->l10n->t('LibreSign: There is a file for you to sign'));
109+
$emailTemplate->setSubject($this->normalizeMailSubject($this->l10n->t('LibreSign: There is a file for you to sign')));
88110
$emailTemplate->addHeader();
89111
$emailTemplate->addHeading($this->l10n->t('File to sign'), false);
90112

@@ -118,7 +140,7 @@ public function notifyUnsignedUser(SignRequest $data, string $email, ?string $de
118140
public function notifySignedUser(SignRequest $signRequest, string $email, File $libreSignFile, string $displayName): void {
119141
$emailTemplate = $this->mailer->createEMailTemplate('settings.TestEmail');
120142
// TRANSLATORS The subject of the email that is sent after a document has been signed by a user. This email is sent to the person who requested the signature.
121-
$emailTemplate->setSubject($this->l10n->t('LibreSign: A file has been signed'));
143+
$emailTemplate->setSubject($this->normalizeMailSubject($this->l10n->t('LibreSign: A file has been signed')));
122144
$emailTemplate->addHeader();
123145
$emailTemplate->addHeading($this->l10n->t('File signed'), false);
124146
// TRANSLATORS The text in the email that is sent after a document has been signed by a user. %s will be replaced with the name of the user who signed the document.
@@ -147,7 +169,7 @@ public function notifySignedUser(SignRequest $signRequest, string $email, File $
147169
public function notifyCanceledRequest(SignRequest $signRequest, string $email, File $libreSignFile): void {
148170
$emailTemplate = $this->mailer->createEMailTemplate('settings.TestEmail');
149171
// TRANSLATORS The subject of the email that is sent when a signature request has been canceled.
150-
$emailTemplate->setSubject($this->l10n->t('LibreSign: A signature request has been canceled'));
172+
$emailTemplate->setSubject($this->normalizeMailSubject($this->l10n->t('LibreSign: A signature request has been canceled')));
151173
$emailTemplate->addHeader();
152174
$emailTemplate->addHeading($this->l10n->t('Signature request canceled'), false);
153175
// TRANSLATORS The text in the email that is sent when a signature request has been canceled. %s will be replaced with the name of the file.
@@ -169,7 +191,7 @@ public function notifyCanceledRequest(SignRequest $signRequest, string $email, F
169191

170192
public function sendCodeToSign(string $email, string $name, string $code): void {
171193
$emailTemplate = $this->mailer->createEMailTemplate('settings.TestEmail');
172-
$emailTemplate->setSubject($this->l10n->t('LibreSign: Code to sign file'));
194+
$emailTemplate->setSubject($this->normalizeMailSubject($this->l10n->t('LibreSign: Code to sign file')));
173195
$emailTemplate->addHeader();
174196
$emailTemplate->addBodyText($this->l10n->t('Use this code to sign the document:'));
175197
$emailTemplate->addBodyText($code);

tests/php/Unit/Service/MailServiceTest.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
use OCP\IAppConfig;
1616
use OCP\IL10N;
1717
use OCP\IURLGenerator;
18+
use OCP\Mail\IEMailTemplate;
1819
use OCP\Mail\IMailer;
20+
use OCP\Mail\IMessage;
1921
use PHPUnit\Framework\MockObject\MockObject;
2022
use Psr\Log\LoggerInterface;
2123

@@ -113,4 +115,61 @@ public function testFailToSendMailToUnsignedUser():void {
113115
$actual = $this->service->notifyUnsignedUser($signRequest, '[email protected]');
114116
$this->assertNull($actual);
115117
}
118+
119+
public function testSendCodeToSignNormalizesAccentedSubjectToAscii(): void {
120+
$l10n = $this->createMock(IL10N::class);
121+
$l10n
122+
->method('t')
123+
->willReturnCallback(static fn (string $text): string
124+
=> $text === 'LibreSign: Code to sign file'
125+
? 'LibreSign : Code nécessaire à la signature du fichier'
126+
: $text
127+
);
128+
129+
$service = new MailService(
130+
$this->logger,
131+
$this->mailer,
132+
$this->fileMapper,
133+
$l10n,
134+
$this->urlGenerator,
135+
$this->appConfig
136+
);
137+
138+
$emailTemplate = $this->createMock(IEMailTemplate::class);
139+
$emailTemplate
140+
->expects($this->once())
141+
->method('setSubject')
142+
->with('LibreSign : Code necessaire a la signature du fichier');
143+
$emailTemplate
144+
->expects($this->once())
145+
->method('addHeader');
146+
$emailTemplate
147+
->expects($this->exactly(2))
148+
->method('addBodyText');
149+
150+
$message = $this->createMock(IMessage::class);
151+
$message
152+
->method('setTo')
153+
->willReturnSelf();
154+
$message
155+
->method('useTemplate')
156+
->willReturnSelf();
157+
158+
$this->mailer
159+
->expects($this->once())
160+
->method('createEMailTemplate')
161+
->with('settings.TestEmail')
162+
->willReturn($emailTemplate);
163+
$this->mailer
164+
->expects($this->once())
165+
->method('createMessage')
166+
->willReturn($message);
167+
$this->mailer
168+
->expects($this->once())
169+
->method('send')
170+
->with($message)
171+
->willReturn([]);
172+
173+
$service->sendCodeToSign('[email protected]', 'John Doe', '123456');
174+
}
116175
}

0 commit comments

Comments
 (0)