Ticket system rewrite of ticket import attachment handling, now with application/octet-stream and cid

This commit is contained in:
OpenXE 2023-02-02 16:39:00 +01:00
parent 9a45653eaf
commit 6bb54be5a3
4 changed files with 85 additions and 75 deletions

View File

@ -51,9 +51,35 @@ class MailAttachmentData implements MailAttachmentInterface
$this->cid = $cid; $this->cid = $cid;
} }
/*
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');
}
} else { // Check for application/octet-stream
$content_type = $part->getContentType();
if ($content_type == 'application/octet-stream') {
return('application/octet-stream');
}
}
}
return(null);
}
/** /**
* @param MailMessagePartInterface $part * @param MailMessagePartInterface $part
*
* @throws InvalidArgumentException * @throws InvalidArgumentException
* *
* @return MailAttachmentData * @return MailAttachmentData
@ -61,21 +87,22 @@ class MailAttachmentData implements MailAttachmentInterface
public static function fromMailMessagePart(MailMessagePartInterface $part): MailAttachmentData public static function fromMailMessagePart(MailMessagePartInterface $part): MailAttachmentData
{ {
$isInline = false; $attachmenttype = MailAttachmentData::getAttachmentPartType($part);
$encodingHeader = $part->getHeader('content-transfer-encoding'); if ($attachmenttype == null) {
if ($encodingHeader === null) { throw new InvalidArgumentException('object is no attachment');
// Assume this is no error (?) throw new InvalidArgumentException('missing header: "Content-Transfer-Encoding"');
$encoding = '';
} else {
$encoding = $encodingHeader->getValue();
} }
$dispositionHeader = $part->getHeader('content-disposition');
if ($dispositionHeader === null) {
throw new InvalidArgumentException('missing header: "Content-Disposition"');
}
$disposition = $dispositionHeader->getValue();
$disposition = $part->getHeaderValue('content-disposition');
if ($disposition == null) {
$disposition = '';
}
$disposition = str_replace(["\n\r", "\n", "\r"], '', $disposition);
// file_put_contents('debug.txt',date("HH:mm:ss")."\nDispo: ".$disposition); // FILE_APPEND
// Determine filename
/* /*
Content-Disposition: inline Content-Disposition: inline
Content-Disposition: attachment Content-Disposition: attachment
@ -84,60 +111,29 @@ class MailAttachmentData implements MailAttachmentInterface
This is not correctly implemented -> only the first string is evaluated This is not correctly implemented -> only the first string is evaluated
Content-Disposition: attachment; filename*0="filename_that_is_" Content-Disposition: attachment; filename*0="filename_that_is_"
Content-Disposition: attachment; filename*1="very_long.jpg" Content-Disposition: attachment; filename*1="very_long.jpg"
*/ */
$filename = 'OpenXE_file.unknown';
if (preg_match('/(.+);\s*filename(?:\*[0-9]){0,1}="([^"]+)".*$/m', $disposition, $matches)) { if (preg_match('/(.+);\s*filename(?:\*[0-9]){0,1}="*([^"]+)"*.*$/m', $disposition, $matches)) { // Filename in disposition
$isInline = strtolower($matches[1]) === 'inline';
$filename = $matches[2];
}
else if ($disposition == 'attachment') {
// Filename is given in Content-Type e.g.
/* Content-Type: application/pdf; name="Filename.pdf"
Content-Transfer-Encoding: base64
Content-Disposition: attachment
*/
$contenttypeHeader = $part->getHeader('content-type');
if ($contenttypeHeader === null) {
throw new InvalidArgumentException('missing header: "Content-Type"');
}
$contenttype = $contenttypeHeader->getValue();
if (preg_match('/(.+);\s*name(?:\*[0-9]){0,1}="([^"]+)".*$/m', $contenttype, $matches)) {
$isInline = strtolower($matches[1]) === 'inline';
$filename = $matches[2]; $filename = $matches[2];
} else { } else {
throw new InvalidArgumentException( $contenttype = $part->getHeaderValue('content-type');
sprintf('missing filename in header value "Content-Type" = "%s"', $contenttype)
);
}
}
else if ($disposition == 'inline') {
$isInline = true;
$filename = "OpenXE_file.inline";
}
else if (strpos($disposition,'attachment;\n') == 0) { // No filename, check for content type message/rfc822
$contenttypeHeader = $part->getHeader('content-type'); $contenttype = str_replace(["\n\r", "\n", "\r"], '', $contenttype);
if ($contenttypeHeader === null) {
throw new InvalidArgumentException('missing header: "Content-Type"');
}
$contenttype = $contenttypeHeader->getValue();
if ($contenttype == 'message/rfc822') { // file_put_contents('debug.txt',date("HH:mm:ss")."\nConttype: ".$contenttype,FILE_APPEND); // FILE_APPEND
if (preg_match('/(.+);\s*name(?:\*[0-9]){0,1}="*([^"]+)"*.*$/m', $contenttype, $matches)) { // Name in content-type
$filename = $matches[2];
} else if ($contenttype == 'message/rfc822') { // RFC822 message
$filename = 'ForwardedMessage.eml'; $filename = 'ForwardedMessage.eml';
}
}
$encodingHeader = $part->getHeader('content-transfer-encoding');
if ($encodingHeader === null) {
$content_transfer_encoding = '';
} else { } else {
/* throw new InvalidArgumentException( $content_transfer_encoding = $encodingHeader->getValue();
sprintf('unexpected header value "Content-Disposition" = "%s"', $disposition)
);*/
$filename = "OpenXE_file.unknown";
}
}
else {
throw new InvalidArgumentException(
sprintf('unexpected header value "Content-Disposition" = "%s", not message/rfc822', $disposition)
);
} }
// Thunderbird UTF URL-Format // Thunderbird UTF URL-Format
@ -148,7 +144,6 @@ class MailAttachmentData implements MailAttachmentInterface
$filename = rawurldecode($filename); $filename = rawurldecode($filename);
} }
$cid = null; $cid = null;
$contentIdHeader = $part->getHeader('content-id'); $contentIdHeader = $part->getHeader('content-id');
if ($contentIdHeader !== null) { if ($contentIdHeader !== null) {
@ -157,9 +152,13 @@ class MailAttachmentData implements MailAttachmentInterface
$cid = $cidMatches[1]; $cid = $cidMatches[1];
} }
} }
if ($attachmenttype == 'inline' && $cid != null) {
$filename = "cid:".$cid;
}
$content = $part->getContent(); $content = $part->getContent();
if ($content === null) { // This should not be if ($content === null) { // This should not be
// file_put_contents('debug.txt',date("HH:mm:ss")."\n".print_r($part,true)); // FILE_APPEND
throw new InvalidArgumentException( throw new InvalidArgumentException(
sprintf('content is null "%s"', substr(print_r($part,true),0,1000)) sprintf('content is null "%s"', substr(print_r($part,true),0,1000))
); );
@ -169,8 +168,8 @@ class MailAttachmentData implements MailAttachmentInterface
$filename, $filename,
$content, $content,
$part->getContentType(), $part->getContentType(),
$encoding, $content_transfer_encoding,
$isInline, $attachmenttype == 'inline',
$cid $cid
); );
} }

View File

@ -147,6 +147,7 @@ final class MailMessageData implements MailMessageInterface, JsonSerializable
$parts = []; $parts = [];
$this->findAttachmentParts($this->contentPart, $parts); $this->findAttachmentParts($this->contentPart, $parts);
$attachments = []; $attachments = [];
foreach ($parts as $part) { foreach ($parts as $part) {
$attachments[] = MailAttachmentData::fromMailMessagePart($part); $attachments[] = MailAttachmentData::fromMailMessagePart($part);
} }
@ -162,18 +163,16 @@ final class MailMessageData implements MailMessageInterface, JsonSerializable
*/ */
private function findAttachmentParts(MailMessagePartInterface $part, array &$resultArray): void private function findAttachmentParts(MailMessagePartInterface $part, array &$resultArray): void
{ {
try {
$header = $part->getHeader('content-disposition');
$split = explode(';', $header->getValue());
if ($split[0] === 'attachment' || $split[0] === 'inline') {
$resultArray[] = $part;
return; if ($part->isMultipart()) {
} // Recurse subparts
} catch (Throwable $e) {
for ($i = 0; $i < $part->countParts(); $i++) { for ($i = 0; $i < $part->countParts(); $i++) {
$this->findAttachmentParts($part->getPart($i), $resultArray); $this->findAttachmentParts($part->getPart($i), $resultArray);
} }
} else {
if (MailAttachmentData::getAttachmentPartType($part) != null) {
$resultArray[] = $part;
}
} }
} }

View File

@ -83,6 +83,18 @@ final class MailMessagePartData implements MailMessagePartInterface, JsonSeriali
return $this->headers[strtolower($name)]; return $this->headers[strtolower($name)];
} }
/**
* @inheritDoc
*/
public function getHeaderValue(string $name): ?string
{
$header = $this->getHeader($name);
if ($header == null) {
return (null);
}
return($header->getValue());
}
/** /**
* @inheritDoc * @inheritDoc
*/ */

View File

@ -495,7 +495,7 @@ class TicketImportHelper
try { try {
// $this->logger->debug('Start import', ['message' => substr(print_r($message,true),1000)]); // $this->logger->debug('Start import', ['message' => substr(print_r($message,true),1000)]);
$this->logger->debug('Start import', []); $this->logger->debug('Start import '.$messageNumber, []);
$result = $this->importMessage($message); $result = $this->importMessage($message);
@ -507,11 +507,11 @@ class TicketImportHelper
$this->mailClient->setFlags((int)$messageNumber, ['\\Seen']); $this->mailClient->setFlags((int)$messageNumber, ['\\Seen']);
} }
} else { } else {
$this->logger->error('Error during email import', ['message' => substr(print_r($message,true),0,1000)]); $this->logger->error('Error during email import '.$messageNumber, ['message' => substr(print_r($message,true),0,1000)]);
continue; continue;
} }
} catch (Throwable $e) { } catch (Throwable $e) {
$this->logger->error('Error during email import', ['message' => substr(print_r($message,true),0,1000)]); $this->logger->error('Error during email import '.$messageNumber, ['message' => substr(print_r($message,true),0,1000)]);
continue; continue;
} }
} }