diff --git a/src/PhpImap/Mailbox.php b/src/PhpImap/Mailbox.php index 011b497d..76beb8e8 100644 --- a/src/PhpImap/Mailbox.php +++ b/src/PhpImap/Mailbox.php @@ -1440,6 +1440,14 @@ protected function hasAttachmentDisposition(object $partStructure): bool return \is_string($disposition) && 'attachment' === \mb_strtolower($disposition); } + protected function sanitizeAttachmentFileSystemName(string $fileName): string + { + return \strtr($fileName, [ + '\\' => '_', + '/' => '_', + ]); + } + /** * Download attachment. * @@ -1519,7 +1527,7 @@ public function downloadAttachment(DataPartInfo $dataInfo, array $params, object if (null != $attachmentsDir) { if (true == $this->getAttachmentFilenameMode()) { - $fileSysName = $attachment->name; + $fileSysName = $this->sanitizeAttachmentFileSystemName($attachment->name); } else { $fileSysName = \bin2hex(\random_bytes(16)).'.bin'; } diff --git a/tests/unit/MailboxTest.php b/tests/unit/MailboxTest.php index 30f39b7f..29b6b002 100644 --- a/tests/unit/MailboxTest.php +++ b/tests/unit/MailboxTest.php @@ -453,6 +453,62 @@ public function testDownloadAttachmentTreatsMixedCaseRfc822DispositionAsEmlAttac $this->assertFalse($attachment->emlOrigin); } + /** + * @return array + */ + public function unsafeAttachmentFilenameProvider(): array + { + return [ + 'forward slash' => ['foo/bar.txt', 'foo_bar.txt'], + 'backslash' => ['foo\\bar.txt', 'foo_bar.txt'], + ]; + } + + /** + * @dataProvider unsafeAttachmentFilenameProvider + */ + public function testDownloadAttachmentSanitizesFilePathWhenUsingOriginalFilenameMode(string $unsafeName, string $expectedFileName): void + { + $attachmentsDir = \sys_get_temp_dir().DIRECTORY_SEPARATOR.'php-imap-attachment-name-'.\bin2hex(\random_bytes(8)); + $mailbox = new class ($this->imapPath, $this->login, $this->password, $attachmentsDir, $this->serverEncoding, true, true) extends Fixtures\Mailbox { + public function decodeMimeStr(string $string): string + { + return $string; + } + }; + $dataInfo = new Fixtures\DataPartInfo($mailbox, 1, '2', 0, 0); + $dataInfo->setData('attachment body'); + $partStructure = (object) [ + 'type' => 3, + 'subtype' => 'OCTET-STREAM', + 'bytes' => 15, + 'encoding' => 0, + 'ifid' => 0, + 'ifsubtype' => 1, + 'ifdescription' => 0, + ]; + $attachmentPath = null; + + \mkdir($attachmentsDir); + + try { + $attachment = $mailbox->downloadAttachment($dataInfo, ['filename' => $unsafeName], $partStructure); + $attachmentPath = $attachment->filePath; + + $this->assertSame($unsafeName, $attachment->name); + $this->assertSame($attachmentsDir.DIRECTORY_SEPARATOR.$expectedFileName, $attachmentPath); + $this->assertFileExists($attachmentPath); + } finally { + if (\is_string($attachmentPath) && \file_exists($attachmentPath)) { + \unlink($attachmentPath); + } + + if (\is_dir($attachmentsDir)) { + \rmdir($attachmentsDir); + } + } + } + /** * Provides test data for testing encoding. *