Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to parse multipart MIME email strings #255

Open
wants to merge 9 commits into
base: 2.26.x
Choose a base branch
from
82 changes: 82 additions & 0 deletions docs/book/message/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,75 @@
$transport->send($message);
```

### Create a Message from a raw email string

You can also create a Message object from a raw email string, one compliant with one or more of the applicable RFCs ([822](https://datatracker.ietf.org/doc/html/rfc822), [2045](https://datatracker.ietf.org/doc/html/rfc2045), [2046](https://datatracker.ietf.org/doc/html/rfc2046), [2047](https://datatracker.ietf.org/doc/html/rfc2047)), by using the static `fromString()` method.

```php
$rawEmail = <<<EOF
From: "[email protected]" <[email protected]>
Subject: test confirmation
To: [email protected], [email protected], [email protected],
[email protected], [email protected], [email protected]
Message-ID: <[email protected]>
Date: Thu, 15 Aug 2019 14:54:37 +0900
X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0;
attachmentreminder=0; deliveryformat=4
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101
Thunderbird/69.0
MIME-Version: 1.0
Content-Type: multipart/mixed;
boundary="------------26A45336F6C6196BD8BBA2A2"
Content-Language: en-US

This is a multi-part message in MIME format.
--------------26A45336F6C6196BD8BBA2A2
Content-Type: text/plain; charset=utf-8; format=flowed
Content-Transfer-Encoding: 7bit

testtest
testtest
testtest
testtest
testtest
testtest

--------------26A45336F6C6196BD8BBA2A2
Content-Type: text/plain; charset=UTF-8;
name="sha1hash.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename="sha1hash.txt"

NzRjOGYwOWRmYTMwZWFjY2ZiMzkyYjEzMjMxNGZjNmI5NzhmMzI1YSAqZmxleC1jb25maXJt
LW1haWwuMS4xMC4wLnhwaQpjY2VlNGI0YWE0N2Y1MTNhYmNlMzQyY2UxZTJlYzJmZDk2MDBl
MzFiICpmbGV4LWNvbmZpcm0tbWFpbC4xLjExLjAueHBpCjA3MWU5ZTM3OGFkMDE3OWJmYWRi
MWJkYzY1MGE0OTQ1NGQyMDRhODMgKmZsZXgtY29uZmlybS1tYWlsLjEuMTIuMC54cGkKOWQ3
YWExNTM0MThlYThmYmM4YmU3YmE2ZjU0Y2U4YTFjYjdlZTQ2OCAqZmxleC1jb25maXJtLW1h
aWwuMS45LjkueHBpCjgxNjg1NjNjYjI3NmVhNGY5YTJiNjMwYjlhMjA3ZDkwZmIxMTg1NmUg
KmZsZXgtY29uZmlybS1tYWlsLnhwaQo=
--------------26A45336F6C6196BD8BBA2A2
Content-Type: application/json;
name="manifest.json"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename="manifest.json"

ewogICJtYW5pZmVzdF92ZXJzaW9uIjogMiwKICAiYXBwbGljYXRpb25zIjogewogICAgImdl
Y2tvIjogewogICAgICAiaWQiOiAiZmxleGlibGUtY29uZmlybS1tYWlsQGNsZWFyLWNvZGUu
Y29tIiwKICAgICAgInN0cmljdF9taW5fdmVyc2lvbiI6ICI2OC4wIgogICAgfQogIH0sCiAg
Im5hbWUiOiAiRmxleCBDb25maXJtIE1haWwiLAogICJkZXNjcmlwdGlvbiI6ICJDb25maXJt
IG1haWxhZGRyZXNzIGFuZCBhdHRhY2htZW50cyBiYXNlZCBvbiBmbGV4aWJsZSBydWxlcy4i
LAogICJ2ZXJzaW9uIjogIjIuMCIsCgogICJsZWdhY3kiOiB7CiAgICAidHlwZSI6ICJ4dWwi
LAogICAgIm9wdGlvbnMiOiB7CiAgICAgICJwYWdlIjogImNocm9tZTovL2NvbmZpcm0tbWFp
bC9jb250ZW50L3NldHRpbmcueHVsIiwKICAgICAgIm9wZW5faW5fdGFiIjogdHJ1ZQogICAg
fQogIH0KfQ==
--------------26A45336F6C6196BD8BBA2A2--
EOF;

$message = Message::fromString($rawEmail);
```

## Configuration Options

The `Message` class has no configuration options, and is instead a value object.
Expand Down Expand Up @@ -426,3 +495,16 @@
```

Serialize to string.

### fromString

Instantiates a `Message` object from a raw message string that is compliant with one or more of the applicable RFCs, including:

- [822](https://datatracker.ietf.org/doc/html/rfc822)
- [2045](https://datatracker.ietf.org/doc/html/rfc2045)
- [2046](https://datatracker.ietf.org/doc/html/rfc2046)
- [2047](https://datatracker.ietf.org/doc/html/rfc2047)

```php
fromString() : Laminas\Mail\Message
```

Check failure on line 510 in docs/book/message/intro.md

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Documentation Linting [8.1, locked], ubuntu-latest, laminas/laminas-continuous-integra...

Files should end with a single newline character
14 changes: 11 additions & 3 deletions src/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -571,9 +571,17 @@
$headers = null;
$content = null;
Mime\Decode::splitMessage($rawMessage, $headers, $content, Headers::EOL);
// if ($headers->has('mime-version')) {
// todo - restore body to mime\message
// }

if ($headers->has('mime-version')) {
$boundary = null;
if ($headers->has('content-type')) {
$contentType = $headers->get('content-type');
$parameters = $contentType->getParameters();

Check failure on line 579 in src/Message.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.1, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

MixedAssignment

src/Message.php:579:17: MixedAssignment: Unable to determine the type that $parameters is being assigned to (see https://psalm.dev/032)

Check failure on line 579 in src/Message.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.1, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

PossiblyFalseReference

src/Message.php:579:46: PossiblyFalseReference: Cannot call method getParameters on possibly false value (see https://psalm.dev/105)

Check failure on line 579 in src/Message.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.1, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

UndefinedMethod

src/Message.php:579:46: UndefinedMethod: Method ArrayIterator::getParameters does not exist (see https://psalm.dev/022)
$boundary = $parameters['boundary'];

Check failure on line 580 in src/Message.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.1, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

MixedAssignment

src/Message.php:580:17: MixedAssignment: Unable to determine the type that $boundary is being assigned to (see https://psalm.dev/032)

Check failure on line 580 in src/Message.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.1, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

MixedArrayAccess

src/Message.php:580:32: MixedArrayAccess: Cannot access array value on mixed variable $parameters (see https://psalm.dev/051)
}
$content = Mime\Message::createFromMessage($content, $boundary);

Check failure on line 582 in src/Message.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.1, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

ReferenceConstraintViolation

src/Message.php:582:13: ReferenceConstraintViolation: Variable $content is limited to values of type string because it is passed by reference, Laminas\Mime\Message type found (see https://psalm.dev/086)

Check failure on line 582 in src/Message.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.1, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

MixedArgument

src/Message.php:582:66: MixedArgument: Argument 2 of Laminas\Mime\Message::createFromMessage cannot be mixed|null, expecting null|string (see https://psalm.dev/030)
}

$message->setHeaders($headers);
$message->setBody($content);
return $message;
Expand Down
24 changes: 24 additions & 0 deletions test/MessageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,7 @@
$raw = file_get_contents(__DIR__ . '/_files/laminas-mail-19.eml');
$message = Message::fromString($raw);
$this->assertInstanceOf(Message::class, $message);
$this->assertIsString($message->getBody());

Check failure on line 836 in test/MessageTest.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (PHPUnit [8.1, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1...

Failed asserting that Laminas\Mime\Message Object #13337 (

Check failure on line 836 in test/MessageTest.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (PHPUnit [8.1, lowest], ubuntu-latest, laminas/laminas-continuous-integration-action@v1...

Failed asserting that Laminas\Mime\Message Object #13337 (

Check failure on line 836 in test/MessageTest.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (PHPUnit [8.1, latest], ubuntu-latest, laminas/laminas-continuous-integration-action@v1...

Failed asserting that Laminas\Mime\Message Object #13337 (

Check failure on line 836 in test/MessageTest.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (PHPUnit [8.2, lowest], ubuntu-latest, laminas/laminas-continuous-integration-action@v1...

Failed asserting that Laminas\Mime\Message Object #13337 (

Check failure on line 836 in test/MessageTest.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (PHPUnit [8.2, latest], ubuntu-latest, laminas/laminas-continuous-integration-action@v1...

Failed asserting that Laminas\Mime\Message Object #13337 (

Check failure on line 836 in test/MessageTest.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (PHPUnit [8.3, lowest], ubuntu-latest, laminas/laminas-continuous-integration-action@v1...

Failed asserting that Laminas\Mime\Message Object #13337 (

Check failure on line 836 in test/MessageTest.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (PHPUnit [8.3, latest], ubuntu-latest, laminas/laminas-continuous-integration-action@v1...

Failed asserting that Laminas\Mime\Message Object #13337 (

$headers = $message->getHeaders();
$this->assertCount(8, $headers);
Expand All @@ -850,6 +850,30 @@
$this->assertEquals('multipart/report', $contentType->getType());
}

public function testCanParseMultipartEmail(): void
{
$raw = file_get_contents(__DIR__ . '/_files/mail_with_pdf_attachment.eml');
$message = Message::fromString($raw);
$this->assertInstanceOf(Message::class, $message);
$this->assertInstanceof(MimeMessage::class, $message->getBody());
$this->assertTrue($message->getBody()->isMultiPart());

Check failure on line 859 in test/MessageTest.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.1, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

MixedMethodCall

test/MessageTest.php:859:48: MixedMethodCall: Cannot determine the type of the object on the left hand side of this expression (see https://psalm.dev/015)

Check failure on line 859 in test/MessageTest.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.1, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

PossiblyInvalidMethodCall

test/MessageTest.php:859:48: PossiblyInvalidMethodCall: Cannot call method on possible string variable (see https://psalm.dev/113)
$parts = $message->getBody()->getParts();

Check failure on line 860 in test/MessageTest.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (Psalm [8.1, locked], ubuntu-latest, laminas/laminas-continuous-integration-action@v1, ...

MixedAssignment

test/MessageTest.php:860:9: MixedAssignment: Unable to determine the type that $parts is being assigned to (see https://psalm.dev/032)
$this->assertCount(2, $parts);
$partOne = $parts[0];
$this->assertCount(2, $partOne->getParts());
$this->assertSame(
"This is a test email with 1 attachment.",
trim($partOne->getParts()[0]->getContent())

Check failure on line 866 in test/MessageTest.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (PHPCodeSniffer [8.1, locked], ubuntu-latest, laminas/laminas-continuous-integration-ac...

Function trim() should not be referenced via a fallback global name, but via a use statement.
);
$this->assertSame(
'<div dir="ltr">This is a test email with 1 attachment.<br clear="all"><div><br></div>-- <br><div class="gmail_signature" data-smartmail="gmail_signature"><div dir="ltr"><img src="https://sendgrid.com/brand/sg-logo-email.png" width="96" height="17"><br><div><br></div></div></div>

Check warning on line 869 in test/MessageTest.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (PHPCodeSniffer [8.1, locked], ubuntu-latest, laminas/laminas-continuous-integration-ac...

Line exceeds 120 characters; contains 292 characters
</div>',
trim($partOne->getParts()[1]->getRawContent())

Check failure on line 871 in test/MessageTest.php

View workflow job for this annotation

GitHub Actions / ci / QA Checks (PHPCodeSniffer [8.1, locked], ubuntu-latest, laminas/laminas-continuous-integration-ac...

Function trim() should not be referenced via a fallback global name, but via a use statement.
);

$attachmentPart = $parts[1];
}

public function testMailHeaderContainsZeroValue(): void
{
$message =
Expand Down
140 changes: 140 additions & 0 deletions test/_files/mail_with_pdf_attachment.eml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
Received: by mx0032p1mdw1.sendgrid.net with SMTP id rOkt2xLLKV Tue, 19 Jul 2016 15:06:29 +0000 (UTC)
Received: from mail-it0-f45.google.com (mail-it0-f45.google.com [209.85.214.45]) by mx0032p1mdw1.sendgrid.net (Postfix) with ESMTPS id 26D6080397 for <[email protected]>; Tue, 19 Jul 2016 15:06:22 +0000 (UTC)
Received: by mail-it0-f45.google.com with SMTP id f6so93587860ith.1 for <[email protected]>; Tue, 19 Jul 2016 08:06:22 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sendgrid.com; s=ga1; h=mime-version:from:date:message-id:subject:to; bh=UYWCIUKTVXyV9U41l+c9+qOlpoeQGcJkKpyOAatNr3Y=; b=c1I/LcqHEJklmAThWr9Z8NKlTPHUlE/8sDSpK382fJtIQcGdUtczG0pijnUHegrFVt FDr4NehtJDD9KFvXLXboLCtObsu5HTN99ckUCCZTibZseA+J8U3jjCqTdj1fmUage5C7 //Iwi0Ndioonzhm18J7KStap66yZ69ED7UxPk=
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:from:date:message-id:subject:to; bh=UYWCIUKTVXyV9U41l+c9+qOlpoeQGcJkKpyOAatNr3Y=; b=lgmLXnmmpNcQMckjshsZsa2/8OjFZzntWYSG5XZo0fi32KHLuBLSHuNDFXn0V4ICp1 1xuT2fZCyhBSgNBiWNbjqFspdemzrBjaI1Tgm/Zz8Fv6wW2XdjpoANNQzJxfdhnecPd5 HvZ5P8+KTqjr4tAa9RmLthDc3UqhV9NRnCnhbW/AZaVQLB8eoJus92tD1GeXpBQml5XF m6vPUGrWGZWNugINkRKxIpk+2uECglAjNm4NpZIi9j7N94CxA18RC4NJ59WIsSybtIer hbCgT1Q13rvGEzvnp6FfFQVbE3DOibNqd0bh/EvZCagFVbnenNc/Q+qHtU9KqFlisSOp xh0w==
X-Gm-Message-State: ALyK8tINVaZIP8YCgQbpg5ya8EnqQo76uxkXUPpDnM+kAyAQQzehFU10EgyuAe2fAmWf/muBiFDy0JDU74Eclp1/
X-Received: by 10.36.76.16 with SMTP id a16mr4479786itb.77.1468940781988; Tue, 19 Jul 2016 08:06:21 -0700 (PDT)
MIME-Version: 1.0
Received: by 10.107.48.17 with HTTP; Tue, 19 Jul 2016 08:06:21 -0700 (PDT)
From: Sender Name <[email protected]>
Date: Tue, 19 Jul 2016 09:06:21 -0600
Message-ID: <CAN_P_JNa25--hzm5=-ES9cnxgWa+h+E49OOAS7sPpV0gsoXCOw@mail.gmail.com>
Subject: Hello
To: [email protected]
Content-Type: multipart/mixed; boundary=001a11447dc881e40f0537fe6d5a

--001a11447dc881e40f0537fe6d5a
Content-Type: multipart/alternative; boundary=001a11447dc881e40b0537fe6d58

--001a11447dc881e40b0537fe6d58
Content-Type: text/plain; charset=UTF-8

This is a test email with 1 attachment.

--001a11447dc881e40b0537fe6d58
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable

<div dir=3D"ltr">This is a test email with 1 attachment.<br clear=3D"all"><=
div><br></div>-- <br><div class=3D"gmail_signature" data-smartmail=3D"gmail=
_signature"><div dir=3D"ltr"><img src=3D"https://sendgrid.com/brand/sg-logo=
-email.png" width=3D"96" height=3D"17"><br><div><br></div></div></div>
</div>

--001a11447dc881e40b0537fe6d58--

--001a11447dc881e40f0537fe6d5a
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document;
name="DockMcWordface.docx"
Content-Disposition: attachment; filename="DockMcWordface.docx"
Content-Transfer-Encoding: base64
X-Attachment-Id: f_iqtleujy0

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

--001a11447dc881e40f0537fe6d5a--