-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit f996b56
Showing
9 changed files
with
481 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
vendor | ||
composer.lock | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
{ | ||
"name": "opcodesio/mail-parser", | ||
"description": "Parse emails without the mailparse extension", | ||
"keywords": [ | ||
"arukompas", | ||
"opcodesio", | ||
"php", | ||
"mail", | ||
"email", | ||
"email parser" | ||
], | ||
"license": "MIT", | ||
"authors": [ | ||
{ | ||
"name": "Arunas Skirius", | ||
"email": "[email protected]", | ||
"role": "Developer" | ||
} | ||
], | ||
"scripts": { | ||
"test": "vendor/bin/pest" | ||
}, | ||
"autoload": { | ||
"psr-4": { | ||
"Opcodes\\MailParser\\": "src/" | ||
} | ||
}, | ||
"autoload-dev": { | ||
"psr-4": { | ||
"Opcodes\\MailParser\\Tests\\": "tests" | ||
} | ||
}, | ||
"require": { | ||
"php": "^8.0" | ||
}, | ||
"config": { | ||
"sort-packages": true, | ||
"allow-plugins": { | ||
"pestphp/pest-plugin": true | ||
} | ||
}, | ||
"require-dev": { | ||
"pestphp/pest": "^2.16", | ||
"symfony/var-dumper": "^6.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.3/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true"> | ||
<testsuites> | ||
<testsuite name="Test Suite"> | ||
<directory suffix="Test.php">./tests</directory> | ||
</testsuite> | ||
</testsuites> | ||
<coverage/> | ||
<source> | ||
<include> | ||
<directory suffix=".php">./app</directory> | ||
<directory suffix=".php">./src</directory> | ||
</include> | ||
</source> | ||
</phpunit> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
<?php | ||
|
||
namespace Opcodes\MailParser; | ||
|
||
class Message | ||
{ | ||
protected string $message; | ||
|
||
protected string $boundary; | ||
|
||
protected array $headers = []; | ||
|
||
/** | ||
* @var MessagePart[] | ||
*/ | ||
protected array $parts = []; | ||
|
||
public function __construct(string $message) | ||
{ | ||
$this->message = $message; | ||
|
||
$this->parse(); | ||
} | ||
|
||
public static function fromString($message): self | ||
{ | ||
return new self($message); | ||
} | ||
|
||
public static function fromFile($path): self | ||
{ | ||
return new self(file_get_contents($path)); | ||
} | ||
|
||
public function getBoundary(): string | ||
{ | ||
return $this->boundary; | ||
} | ||
|
||
public function getHeaders(): array | ||
{ | ||
return $this->headers; | ||
} | ||
|
||
public function getHeader(string $header, $default = null): ?string | ||
{ | ||
return $this->headers[$header] ?? $default; | ||
} | ||
|
||
public function getId(): string | ||
{ | ||
$header = $this->getHeader('Message-ID', ''); | ||
|
||
return trim($header, '<>'); | ||
} | ||
|
||
public function getSubject(): string | ||
{ | ||
return $this->getHeader('Subject', ''); | ||
} | ||
|
||
public function getFrom(): string | ||
{ | ||
return $this->getHeader('From', ''); | ||
} | ||
|
||
public function getTo(): string | ||
{ | ||
return $this->getHeader('To', ''); | ||
} | ||
|
||
public function getReplyTo(): string | ||
{ | ||
return $this->getHeader('Reply-To', ''); | ||
} | ||
|
||
public function getDate(): ?\DateTime | ||
{ | ||
return \DateTime::createFromFormat( | ||
'D, d M Y H:i:s O', | ||
$this->getHeader('Date') | ||
) ?: null; | ||
} | ||
|
||
public function getParts(): array | ||
{ | ||
return $this->parts; | ||
} | ||
|
||
protected function parse() | ||
{ | ||
// Parse the email message into headers and body | ||
$lines = explode("\n", $this->message); | ||
$headerInProgress = null; | ||
|
||
$collectingBody = false; | ||
$currentBody = ''; | ||
$currentBodyHeaders = []; | ||
$currentBodyHeaderInProgress = null; | ||
|
||
foreach ($lines as $line) { | ||
if ($headerInProgress) { | ||
$this->headers[$headerInProgress] .= "\n" . $line; | ||
$headerInProgress = str_ends_with($line, ';'); | ||
continue; | ||
} | ||
|
||
if ($currentBodyHeaderInProgress) { | ||
$currentBodyHeaders[$currentBodyHeaderInProgress] .= "\n" . $line; | ||
$currentBodyHeaderInProgress = str_ends_with($line, ';'); | ||
continue; | ||
} | ||
|
||
if (isset($this->boundary) && $line === '--'.$this->boundary.'--') { | ||
// We've reached the end of the message | ||
$this->addPart($currentBody, $currentBodyHeaders); | ||
continue; | ||
} | ||
|
||
if (isset($this->boundary) && $line === '--'.$this->boundary) { | ||
if ($collectingBody) { | ||
// We've reached the end of a part, add it and reset the variables | ||
$this->addPart($currentBody, $currentBodyHeaders); | ||
} | ||
|
||
$collectingBody = true; | ||
$currentBody = ''; | ||
$currentBodyHeaders = []; | ||
continue; | ||
} | ||
|
||
if ($collectingBody && preg_match('/^(?<key>[A-Za-z\-0-9]+): (?<value>.*)$/', $line, $matches)) { | ||
$currentBodyHeaders[$matches['key']] = $matches['value']; | ||
|
||
// if the last character is a semicolon, then the header is continued on the next line | ||
if (str_ends_with($matches['value'], ';')) { | ||
$currentBodyHeaderInProgress = $matches['key']; | ||
} | ||
|
||
continue; | ||
} | ||
|
||
if ($collectingBody) { | ||
$currentBody .= $line."\n"; | ||
continue; | ||
} | ||
|
||
if (preg_match('/^Content-Type: multipart\/mixed; boundary=(?<boundary>.*)$/', $line, $matches)) { | ||
$this->headers['Content-Type'] = 'multipart/mixed; boundary='.$matches['boundary']; | ||
$this->boundary = trim($matches['boundary'], '"'); | ||
continue; | ||
} | ||
|
||
if (preg_match('/^(?<key>[A-Za-z\-0-9]+): (?<value>.*)$/', $line, $matches)) { | ||
$this->headers[$matches['key']] = $matches['value']; | ||
|
||
// if the last character is a semicolon, then the header is continued on the next line | ||
if (str_ends_with($matches['value'], ';')) { | ||
$headerInProgress = $matches['key']; | ||
} | ||
|
||
continue; | ||
} | ||
} | ||
} | ||
|
||
protected function addPart(string $currentBody, array $currentBodyHeaders): void | ||
{ | ||
$this->parts[] = new MessagePart(trim($currentBody), $currentBodyHeaders); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
<?php | ||
|
||
namespace Opcodes\MailParser; | ||
|
||
class MessagePart | ||
{ | ||
protected string $content; | ||
|
||
protected array $headers; | ||
|
||
public function __construct(string $content, array $headers = []) | ||
{ | ||
$this->content = $content; | ||
$this->headers = $headers; | ||
} | ||
|
||
public function getContentType(): string | ||
{ | ||
return $this->headers['Content-Type'] ?? ''; | ||
} | ||
|
||
public function getHeaders(): array | ||
{ | ||
return $this->headers; | ||
} | ||
|
||
public function getHeader(string $name, $default = null): mixed | ||
{ | ||
return $this->headers[$name] ?? $default; | ||
} | ||
|
||
public function getContent(): string | ||
{ | ||
if (strtolower($this->getHeader('Content-Transfer-Encoding', '')) === 'base64') { | ||
return base64_decode($this->content); | ||
} | ||
|
||
return $this->content; | ||
} | ||
|
||
public function isAttachment(): bool | ||
{ | ||
return str_starts_with($this->getHeader('Content-Disposition'), 'attachment'); | ||
} | ||
|
||
public function getFilename(): string | ||
{ | ||
if (preg_match('/filename=([^;]+)/', $this->getHeader('Content-Disposition'), $matches)) { | ||
return trim($matches[1], '"'); | ||
} | ||
|
||
if (preg_match('/name=([^;]+)/', $this->getContentType(), $matches)) { | ||
return trim($matches[1], '"'); | ||
} | ||
|
||
return ''; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
From: Arunas Practice <[email protected]> | ||
To: Arunas arukomp <[email protected]> | ||
Reply-To: Arunas Practice <[email protected]> | ||
Subject: Appointment confirmation | ||
Message-ID: <[email protected]> | ||
MIME-Version: 1.0 | ||
Date: Thu, 24 Aug 2023 14:51:14 +0100 | ||
Content-Type: multipart/mixed; boundary=lGiKDww4 | ||
|
||
--lGiKDww4 | ||
Content-Type: text/html; charset=utf-8 | ||
Content-Transfer-Encoding: quoted-printable | ||
|
||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | ||
<html xmlns="http://www.w3.org/1999/xhtml"> | ||
<!-- omitted content --> | ||
</html> | ||
|
||
--lGiKDww4 | ||
Content-Type: text/calendar; name=Appointment.ics | ||
Content-Transfer-Encoding: base64 | ||
Content-Disposition: attachment; name=Appointment.ics; | ||
filename=Appointment.ics | ||
QkVHSU46VkNBTEVOREFSDQpWRVJTSU9OOjIuMA0KUFJPRElEOi0vL2hhY2tzdy9oYW5kY2FsLy9O | ||
T05TR01MIHYxLjAvL0VODQpCRUdJTjpWVElNRVpPTkUNClRaSUQ6RXVyb3BlL0xvbmRvbg0KWC1M | ||
SUMtTE9DQVRJT046RXVyb3BlL0xvbmRvbg0KQkVHSU46REFZTElHSFQNClRaT0ZGU0VURlJPTTor | ||
MDEwMA0KVFpPRkZTRVRUTzorMDIwMA0KVFpOQU1FOkNFU1QN | ||
--lGiKDww4-- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<?php | ||
|
||
// uses(Tests\TestCase::class)->in('Feature'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php | ||
|
||
namespace Opcodes\MailParser\Tests; | ||
|
||
use PHPUnit\Framework\TestCase as BaseTestCase; | ||
|
||
abstract class TestCase extends BaseTestCase | ||
{ | ||
// | ||
} |
Oops, something went wrong.