Describe the bug
Attachment::getContent() and Attachment::save() return/write the full raw MIME
part instead of the decoded attachment content when the email has a nested
multipart/mixed structure and the boundary values in the Content-Type headers
already include the -- prefix.
To Reproduce
-
Receive or parse an email with the following structure:
- Root:
multipart/mixed (boundary_27)
- Part 1:
multipart/alternative (boundary_26) containing text/plain and text/html
- Part 2:
multipart/mixed (boundary_28) containing an application/octet-stream attachment
-
Note that the boundary values in the Content-Type headers include -- as prefix:
Content-Type: multipart/mixed; boundary=--boundary_28_ef2e7fb5-cf91-4186-95ff-f421173253ee
This causes the actual delimiters in the body to have four dashes (----boundary_28...)
instead of the standard two.
- Fetch the message and call
getAttachments():
$attachments = $message->getAttachments();
- Call
getContent() or save() on any attachment:
$content = $attachments->first()->getContent();
// or
$attachments->first()->save($path, $filename);
- Observe that instead of the decoded file content, the returned value is the raw
MIME block including boundary lines, headers and the base64 body unprocessed:
----boundary_28_ef2e7fb5-cf91-4186-95ff-f421173253ee
Content-Type: application/octet-stream; name="text/csv"
Content-Transfer-Encoding: base64
...
77u/ZmVjaGEsTHVnYXIs...
----boundary_28_ef2e7fb5-cf91-4186-95ff-f421173253ee--
The following raw body sample can be used to reproduce the issue:
----boundary_27_e518d7ea-b9aa-481d-9e56-7542504f05f2
Content-Type: multipart/alternative; boundary=--boundary_26_04f7c6a7-a7e7-494c-b32e-f2ed2d42b011
----boundary_26_04f7c6a7-a7e7-494c-b32e-f2ed2d42b011
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
The report is accessible at the following address: http://example.com/report
----boundary_26_04f7c6a7-a7e7-494c-b32e-f2ed2d42b011--
----boundary_27_e518d7ea-b9aa-481d-9e56-7542504f05f2
Content-Type: multipart/mixed; boundary=--boundary_28_ef2e7fb5-cf91-4186-95ff-f421173253ee
----boundary_28_ef2e7fb5-cf91-4186-95ff-f421173253ee
Content-Type: application/octet-stream; name="text/csv"
Content-Transfer-Encoding: base64
Content-ID: <d62fe95b-6e96-4193-897d-fada2361702b>
Content-Disposition: attachment; filename="=?utf-8?B?RkxUQ1IwMDAxLmNzdg==?="
77u/ZmVjaGEsTHVnYXIsVGlwb19Db21idXN0aWJsZSx2b2x1bWVuLG1vbnRvLElkZW50aWZpY2Fk
b3JfQ2xpZW50ZSxwbGFjYSxJZGVudGlmaWNhZG9yX0NvbXByYQ0KMjAyNi0wMS0wMSAwODowMDow
MCxTVEFUSU9OIE5PUlRILERpZXNlbCw1MC4wMCwiMjYsNTAwLjAwIiwxMDAxLEFBQS0wMDEsNTU1
RTAwNDAxNTMwQUFBQUFBMQ0KMjAyNi0wMS0wMSAwOToxNTowMCxTVEFUSU9OIFNPVVRILFBsdXMg
OTEsMzAuMDAsIjE4LDAwMC4wMCIsMTAwMixCQkItMDAyLDU1NUUwMDQwMTUzMEJCQkJCQjINCjIw
MjYtMDEtMDEgMTA6MzA6MDAsU1RBVElPTiBFQVNULERpZXNlbCw3NS4wMCwiMzksNzUwLjAwIiwx
MDAzLENDQy0wMDMsNTU1RTAwNDAxNTMwQ0NDQ0NDMw0KMjAyNi0wMS0wMSAxMTo0NTowMCxTVEFU
SU9OIFdFU1QsRGllc2VsLDYwLjAwLCIzMSw4MDAuMDAiLDEwMDQsRERELTAwNCw1NTVFMDA0MDE1
MzBEREREREQ0DQoyMDI2LTAxLTAxIDEzOjAwOjAwLFNUQVRJT04gQ0VOVFJBTCxTdXBlciwyNS4w
MCwiMTUsNzUwLjAwIiwxMDA1LEVFRS0wMDUsNTU1RTAwNDAxNTMwRUVFRUVFNQ0K
----boundary_28_ef2e7fb5-cf91-4186-95ff-f421173253ee--
----boundary_27_e518d7ea-b9aa-481d-9e56-7542504f05f2--
Expected behavior
getContent() should return the decoded file content:
fecha,Lugar,Tipo_Combustible,volumen,monto,Identificador_Cliente,placa,Identificador_Compra
2026-01-01 08:00:00,STATION NORTH,Diesel,50.00,"26,500.00",1001,AAA-001,555E00401530AAAAAA1
2026-01-01 09:15:00,STATION SOUTH,Plus 91,30.00,"18,000.00",1002,BBB-002,555E00401530BBBBBB2
2026-01-01 10:30:00,STATION EAST,Diesel,75.00,"39,750.00",1003,CCC-003,555E00401530CCCCCC3
2026-01-01 11:45:00,STATION WEST,Diesel,60.00,"31,800.00",1004,DDD-004,555E00401530DDDDDD4
2026-01-01 13:00:00,STATION CENTRAL,Super,25.00,"15,750.00",1005,EEE-005,555E00401530EEEEEE5
Screenshots
Not applicable.
Desktop / Server
- OS: Linux (Ubuntu 24)
- PHP: 8.x
- webklex/laravel-imap: 6.2.0
- Laravel: 12
Additional context
Two factors combine to cause the failure:
1 — Double-dash boundary values:
The sender includes -- inside the boundary value in the Content-Type header,
which is non-standard per RFC 2046. This produces four-dash delimiters (----) in
the body instead of the standard two, likely breaking the boundary regex
/boundary=(.*?(?=;)|(.*))/i used by Structure::find_parts().
2 — Missing recursive parsing of nested multipart/mixed:
Instead of recursively entering the inner multipart/mixed (Part 2 / boundary_28)
to extract the application/octet-stream attachment, the library returns the entire
raw MIME block of Part 2 as the attachment content.
Current workaround until this is fixed:
private function decodeMimeAttachment(string $rawContent): string
{
$parts = preg_split('/\r?\n\r?\n/', $rawContent, 2);
if (count($parts) < 2) {
return $rawContent;
}
$headers = $parts[0];
$body = preg_replace('/\r?\n?----boundary.*$/s', '', $parts[1]);
if (stripos($headers, 'Content-Transfer-Encoding: base64') !== false) {
$body = base64_decode(str_replace(["\r\n", "\r", "\n"], '', trim($body)));
}
return $body;
}
Describe the bug
Attachment::getContent()andAttachment::save()return/write the full raw MIMEpart instead of the decoded attachment content when the email has a nested
multipart/mixedstructure and the boundary values in theContent-Typeheadersalready include the
--prefix.To Reproduce
Receive or parse an email with the following structure:
multipart/mixed(boundary_27)multipart/alternative(boundary_26) containingtext/plainandtext/htmlmultipart/mixed(boundary_28) containing anapplication/octet-streamattachmentNote that the boundary values in the
Content-Typeheaders include--as prefix:This causes the actual delimiters in the body to have four dashes (
----boundary_28...)instead of the standard two.
getAttachments():getContent()orsave()on any attachment:MIME block including boundary lines, headers and the base64 body unprocessed:
The following raw body sample can be used to reproduce the issue:
Expected behavior
getContent()should return the decoded file content:Screenshots
Not applicable.
Desktop / Server
Additional context
Two factors combine to cause the failure:
1 — Double-dash boundary values:
The sender includes
--inside the boundary value in theContent-Typeheader,which is non-standard per RFC 2046. This produces four-dash delimiters (
----) inthe body instead of the standard two, likely breaking the boundary regex
/boundary=(.*?(?=;)|(.*))/iused byStructure::find_parts().2 — Missing recursive parsing of nested multipart/mixed:
Instead of recursively entering the inner
multipart/mixed(Part 2 / boundary_28)to extract the
application/octet-streamattachment, the library returns the entireraw MIME block of Part 2 as the attachment content.
Current workaround until this is fixed: