2021-05-21 08:49:41 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace Xentral\Components\MailClient\Data;
|
|
|
|
|
|
|
|
use Xentral\Components\MailClient\Exception\InvalidArgumentException;
|
|
|
|
|
|
|
|
class MailAttachmentData implements MailAttachmentInterface
|
|
|
|
{
|
|
|
|
/** @var string $filename */
|
|
|
|
private $filename;
|
|
|
|
|
|
|
|
/** @var string $content */
|
|
|
|
private $content;
|
|
|
|
|
|
|
|
/** @var string $contentType */
|
|
|
|
private $contentType;
|
|
|
|
|
|
|
|
/** @var string $encoding */
|
|
|
|
private $encoding;
|
|
|
|
|
|
|
|
/** @var bool $isInlineAttachment*/
|
|
|
|
private $isInlineAttachment;
|
|
|
|
|
|
|
|
/** @var string|null $cid */
|
|
|
|
private $cid;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $filename
|
|
|
|
* @param string $content
|
|
|
|
* @param string $contentType
|
|
|
|
* @param string $encoding
|
|
|
|
* @param bool $isInlineAttachment
|
|
|
|
* @param string|null $cid
|
|
|
|
*/
|
|
|
|
public function __construct(
|
|
|
|
string $filename,
|
|
|
|
string $content,
|
|
|
|
string $contentType,
|
|
|
|
string $encoding,
|
|
|
|
bool $isInlineAttachment = false,
|
|
|
|
string $cid = null
|
|
|
|
)
|
|
|
|
{
|
|
|
|
$this->filename = $filename;
|
|
|
|
$this->content = $content;
|
|
|
|
$this->contentType = $contentType;
|
|
|
|
$this->encoding = $encoding;
|
|
|
|
$this->isInlineAttachment = $isInlineAttachment;
|
|
|
|
$this->cid = $cid;
|
|
|
|
}
|
|
|
|
|
2023-02-02 16:39:00 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
Check the type of Attachment
|
|
|
|
Possible results: application/octet-stream, attachment, inline
|
|
|
|
*/
|
|
|
|
public static function getAttachmentPartType(MailMessagePartInterface $part): ?string {
|
|
|
|
|
|
|
|
if (!$part->isMultipart()) {
|
|
|
|
$header = $part->getHeader('content-disposition');
|
|
|
|
if ($header !== null) {
|
|
|
|
$split = explode(';', $header->getValue());
|
|
|
|
if ($split[0] === 'attachment') {
|
|
|
|
return ('attachment');
|
|
|
|
} else if ($split[0] === 'inline') {
|
|
|
|
return ('inline');
|
|
|
|
}
|
2024-04-11 16:12:47 +02:00
|
|
|
} else {
|
|
|
|
// No disposition given (that is a bad thing)
|
|
|
|
// Check for application/octet-stream
|
2023-02-02 16:39:00 +01:00
|
|
|
$content_type = $part->getContentType();
|
|
|
|
if ($content_type == 'application/octet-stream') {
|
|
|
|
return('application/octet-stream');
|
2024-04-11 16:12:47 +02:00
|
|
|
}
|
|
|
|
// Check for Content-id
|
|
|
|
$contentIdHeader = $part->getHeader('content-id');
|
|
|
|
if ($contentIdHeader !== null) {
|
|
|
|
return ('inline');
|
|
|
|
}
|
2023-02-02 16:39:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return(null);
|
|
|
|
}
|
|
|
|
|
2021-05-21 08:49:41 +02:00
|
|
|
/**
|
|
|
|
* @param MailMessagePartInterface $part
|
|
|
|
* @throws InvalidArgumentException
|
|
|
|
*
|
|
|
|
* @return MailAttachmentData
|
|
|
|
*/
|
|
|
|
public static function fromMailMessagePart(MailMessagePartInterface $part): MailAttachmentData
|
|
|
|
{
|
2023-02-01 11:24:20 +01:00
|
|
|
|
2023-02-02 16:39:00 +01:00
|
|
|
$attachmenttype = MailAttachmentData::getAttachmentPartType($part);
|
2023-02-01 11:24:20 +01:00
|
|
|
|
2023-02-02 16:39:00 +01:00
|
|
|
if ($attachmenttype == null) {
|
|
|
|
throw new InvalidArgumentException('object is no attachment');
|
2021-05-21 08:49:41 +02:00
|
|
|
}
|
2023-02-02 16:39:00 +01:00
|
|
|
|
|
|
|
$disposition = $part->getHeaderValue('content-disposition');
|
|
|
|
if ($disposition == null) {
|
|
|
|
$disposition = '';
|
2021-05-21 08:49:41 +02:00
|
|
|
}
|
2022-07-27 18:05:24 +02:00
|
|
|
|
2023-02-02 16:39:00 +01:00
|
|
|
$disposition = str_replace(["\n\r", "\n", "\r"], '', $disposition);
|
|
|
|
|
|
|
|
// file_put_contents('debug.txt',date("HH:mm:ss")."\nDispo: ".$disposition); // FILE_APPEND
|
|
|
|
|
|
|
|
// Determine filename
|
2023-01-04 12:27:34 +01:00
|
|
|
/*
|
|
|
|
Content-Disposition: inline
|
|
|
|
Content-Disposition: attachment
|
|
|
|
Content-Disposition: attachment; filename="filename.jpg"
|
|
|
|
|
|
|
|
This is not correctly implemented -> only the first string is evaluated
|
|
|
|
Content-Disposition: attachment; filename*0="filename_that_is_"
|
|
|
|
Content-Disposition: attachment; filename*1="very_long.jpg"
|
|
|
|
*/
|
2023-02-02 16:39:00 +01:00
|
|
|
$filename = 'OpenXE_file.unknown';
|
|
|
|
if (preg_match('/(.+);\s*filename(?:\*[0-9]){0,1}="*([^"]+)"*.*$/m', $disposition, $matches)) { // Filename in disposition
|
2023-01-04 12:27:34 +01:00
|
|
|
$filename = $matches[2];
|
2023-02-02 16:39:00 +01:00
|
|
|
} else {
|
|
|
|
$contenttype = $part->getHeaderValue('content-type');
|
|
|
|
|
|
|
|
$contenttype = str_replace(["\n\r", "\n", "\r"], '', $contenttype);
|
|
|
|
|
|
|
|
// file_put_contents('debug.txt',date("HH:mm:ss")."\nConttype: ".$contenttype,FILE_APPEND); // FILE_APPEND
|
2023-01-04 12:27:34 +01:00
|
|
|
|
2023-02-02 16:39:00 +01:00
|
|
|
if (preg_match('/(.+);\s*name(?:\*[0-9]){0,1}="*([^"]+)"*.*$/m', $contenttype, $matches)) { // Name in content-type
|
2023-01-04 12:27:34 +01:00
|
|
|
$filename = $matches[2];
|
2023-02-02 16:39:00 +01:00
|
|
|
} else if ($contenttype == 'message/rfc822') { // RFC822 message
|
|
|
|
$filename = 'ForwardedMessage.eml';
|
2023-01-12 00:01:20 +01:00
|
|
|
}
|
|
|
|
}
|
2023-02-02 16:39:00 +01:00
|
|
|
|
|
|
|
$encodingHeader = $part->getHeader('content-transfer-encoding');
|
|
|
|
if ($encodingHeader === null) {
|
|
|
|
$content_transfer_encoding = '';
|
|
|
|
} else {
|
|
|
|
$content_transfer_encoding = $encodingHeader->getValue();
|
2021-05-21 08:49:41 +02:00
|
|
|
}
|
2022-07-27 18:05:24 +02:00
|
|
|
|
|
|
|
// Thunderbird UTF URL-Format
|
|
|
|
$UTF_pos = strpos($filename,'UTF-8\'\'');
|
2023-01-04 12:27:34 +01:00
|
|
|
if ($UTF_pos !== false) {
|
2022-07-27 18:05:24 +02:00
|
|
|
$wasUTF = "JA";
|
|
|
|
$filename = substr($filename,$UTF_pos);
|
|
|
|
$filename = rawurldecode($filename);
|
|
|
|
}
|
2023-02-02 16:39:00 +01:00
|
|
|
|
2021-05-21 08:49:41 +02:00
|
|
|
$cid = null;
|
|
|
|
$contentIdHeader = $part->getHeader('content-id');
|
|
|
|
if ($contentIdHeader !== null) {
|
|
|
|
$cid = $contentIdHeader->getValue();
|
|
|
|
if (preg_match('/[<]?([^<>]+)[>]?$/', $cid, $cidMatches)) {
|
|
|
|
$cid = $cidMatches[1];
|
|
|
|
}
|
|
|
|
}
|
2023-02-02 16:39:00 +01:00
|
|
|
if ($attachmenttype == 'inline' && $cid != null) {
|
|
|
|
$filename = "cid:".$cid;
|
|
|
|
}
|
2021-05-21 08:49:41 +02:00
|
|
|
|
2023-02-01 11:24:20 +01:00
|
|
|
$content = $part->getContent();
|
|
|
|
if ($content === null) { // This should not be
|
2023-02-02 16:39:00 +01:00
|
|
|
// file_put_contents('debug.txt',date("HH:mm:ss")."\n".print_r($part,true)); // FILE_APPEND
|
2023-02-01 11:24:20 +01:00
|
|
|
throw new InvalidArgumentException(
|
2023-02-01 19:02:16 +01:00
|
|
|
sprintf('content is null "%s"', substr(print_r($part,true),0,1000))
|
2023-02-01 11:24:20 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-05-21 08:49:41 +02:00
|
|
|
return new self(
|
|
|
|
$filename,
|
2023-02-01 11:24:20 +01:00
|
|
|
$content,
|
2021-05-21 08:49:41 +02:00
|
|
|
$part->getContentType(),
|
2023-02-02 16:39:00 +01:00
|
|
|
$content_transfer_encoding,
|
|
|
|
$attachmenttype == 'inline',
|
2021-05-21 08:49:41 +02:00
|
|
|
$cid
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getFileName(): string
|
|
|
|
{
|
|
|
|
return $this->filename;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getContent(): string
|
|
|
|
{
|
|
|
|
switch ($this->encoding) {
|
|
|
|
case 'base64':
|
|
|
|
return base64_decode($this->content);
|
|
|
|
|
|
|
|
default:
|
|
|
|
return $this->content;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getContentType(): string
|
|
|
|
{
|
|
|
|
return $this->contentType;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getTransferEncoding(): string
|
|
|
|
{
|
|
|
|
return $this->encoding;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function isInlineAttachment(): bool
|
|
|
|
{
|
|
|
|
return $this->isInlineAttachment;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string|null
|
|
|
|
*/
|
|
|
|
public function getCid(): ?string
|
|
|
|
{
|
|
|
|
return $this->cid;
|
|
|
|
}
|
|
|
|
}
|