diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 85bd6966..576d5895 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -16,11 +16,11 @@ jobs:
# Keys:
# - experimental: Whether the build is "allowed to fail".
matrix:
- php: ['7.3', '7.4', '8.0', '8.1']
+ php: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
experimental: [false]
include:
- - php: '8.2'
+ - php: '8.4'
experimental: true
name: "PHP: ${{ matrix.php }}"
@@ -41,12 +41,12 @@ jobs:
# Install dependencies and handle caching in one go.
# @link https://github.com/marketplace/actions/install-composer-dependencies
- - name: "Install Composer dependencies (PHP < 8.2)"
- if: ${{ matrix.php < '8.2' }}
+ - name: "Install Composer dependencies (PHP < 8.4)"
+ if: ${{ matrix.php < '8.4' }}
uses: "ramsey/composer-install@v1"
- - name: "Install Composer dependencies (PHP 8.2)"
- if: ${{ matrix.php >= '8.2' }}
+ - name: "Install Composer dependencies (PHP 8.4)"
+ if: ${{ matrix.php >= '8.4' }}
uses: "ramsey/composer-install@v1"
with:
composer-options: --ignore-platform-reqs
diff --git a/ChangeLog b/ChangeLog
index aa17400d..e688d379 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+1.10.0 - [RELEASEDATE]
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed #96: Add new option 'parseMultipartMixedTextAttachmentsAsFiles' to
+ ezcMailParserOptions to retrieve only text attachments, that are sub-parts of
+ multipart/mixed parts an ezcMailFile object, instead of the default ezcMailText
+ object.
+
1.9.7 - Tuesday 20 August 2024
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/src/options/parser_options.php b/src/options/parser_options.php
index ac0b6816..3d0de157 100644
--- a/src/options/parser_options.php
+++ b/src/options/parser_options.php
@@ -33,6 +33,7 @@
* $options->mailClass = 'myCustomMailClass'; // extends ezcMail
* $options->fileClass = 'myCustomFileClass'; // extends ezcMailFile
* $options->parseTextAttachmentsAsFiles = true; // to get the text attachments in ezcMailFile objects
+ * $options->parseMultipartMixedTextAttachmentsAsFiles = true; // to get all text attachments that are sub-parts of multipart/mixed parts as ezcMailFile objects
*
* $parser = new ezcMailParser( $options );
*
@@ -43,6 +44,7 @@
* $parser->options->mailClass = 'myCustomMailClass'; // extends ezcMail
* $parser->options->fileClass = 'myCustomFileClass'; // extends ezcMailFile
* $parser->options->parseTextAttachmentsAsFiles = true;
+ * $parser->options->parseMultipartMixedTextAttachmentsAsFiles = true;
*
*
* @property string $mailClass
@@ -54,7 +56,11 @@
* by the parser to handle file attachments. The default value is
* ezcMailFile.
* @property string $parseTextAttachmentsAsFiles
- * Specifies whether to parse the text attachments in an ezcMailTextPart
+ * Specifies whether to parse the text attachments in an ezcMailText part
+ * (default) or in an ezcMailFile (by setting the option to true).
+ * @property string $parseMultipartMixedTextAttachmentsAsFiles
+ * Specifies whether to parse the text attachments that are sub-parts
+ * of a multipart/mixed part as an ezcMailText part
* (default) or in an ezcMailFile (by setting the option to true).
* @package Mail
* @version //autogen//
@@ -74,7 +80,8 @@ public function __construct( array $options = array() )
{
$this->mailClass = 'ezcMail'; // default value for mail class is 'ezcMail'
$this->fileClass = 'ezcMailFile'; // default value for file attachment class is 'ezcMailFile'
- $this->parseTextAttachmentsAsFiles = false; // default is to parse text attachments in ezcMailTextPart objects
+ $this->parseTextAttachmentsAsFiles = false; // default is to parse text attachments in ezcMailText objects
+ $this->parseMultipartMixedTextAttachmentsAsFiles = false; // default is to parse multiple/mixed text attachments in ezcMailText objects
parent::__construct( $options );
}
@@ -140,6 +147,16 @@ public function __set( $propertyName, $propertyValue )
ezcMailPartParser::$parseTextAttachmentsAsFiles = $propertyValue;
break;
+
+ case 'parseMultipartMixedTextAttachmentsAsFiles':
+ if ( !is_bool( $propertyValue ) )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' );
+ }
+ $this->properties[$propertyName] = $propertyValue;
+ ezcMailPartParser::$parseMultipartMixedTextAttachmentsAsFiles = $propertyValue;
+ break;
+
default:
throw new ezcBasePropertyNotFoundException( $propertyName );
}
diff --git a/src/parser/interfaces/part_parser.php b/src/parser/interfaces/part_parser.php
index a81378b3..89fd4e96 100644
--- a/src/parser/interfaces/part_parser.php
+++ b/src/parser/interfaces/part_parser.php
@@ -66,7 +66,7 @@ abstract class ezcMailPartParser
'sender', 'subject', 'sender', 'to' );
/**
- * The default is to parse text attachments into ezcMailTextPart objects.
+ * The default is to parse text attachments into ezcMailText objects.
*
* Setting this to true before calling the parser will parse text attachments
* into ezcMailFile objects. Use the parser options for this:
@@ -81,6 +81,23 @@ abstract class ezcMailPartParser
*/
public static $parseTextAttachmentsAsFiles = false;
+ /**
+ * The default is to parse text attachments into ezcMailText objects.
+ *
+ * Setting this to true before calling the parser will parse text attachments
+ * that are sub-parts of Multipart/Mixed parts into ezcMailFail objects.
+ * Use the parser options for this:
+ *
+ *
+ * $parser = new ezcMailParser();
+ * $parser->options->parseMultipartMixedTextAttachmentsAsFiles = true;
+ * // call $parser->parseMail( $set );
+ *
+ *
+ * @var bool
+ */
+ public static $parseMultipartMixedTextAttachmentsAsFiles = false;
+
/**
* The name of the last header parsed.
*
@@ -118,7 +135,7 @@ abstract public function finish();
* @param ezcMailHeadersHolder $headers
* @return ezcMailPartParser
*/
- static public function createPartParserForHeaders( ezcMailHeadersHolder $headers )
+ static public function createPartParserForHeaders( ezcMailHeadersHolder $headers, ?ezcMailPartParser $parentParser = null )
{
// default as specified by RFC2045 - #5.2
$mainType = 'text';
@@ -168,7 +185,18 @@ static public function createPartParserForHeaders( ezcMailHeadersHolder $headers
break;
case 'text':
- if ( ezcMailPartParser::$parseTextAttachmentsAsFiles === true )
+ if ( ezcMailPartParser::$parseMultipartMixedTextAttachmentsAsFiles === true )
+ {
+ if ( $parentParser instanceof ezcMailMultipartMixedParser )
+ {
+ $bodyParser = new ezcMailFileParser( $mainType, $subType, $headers );
+ }
+ else
+ {
+ $bodyParser = new ezcMailTextParser( $subType, $headers );
+ }
+ }
+ else if ( ezcMailPartParser::$parseTextAttachmentsAsFiles === true )
{
$bodyParser = new ezcMailFileParser( $mainType, $subType, $headers );
}
diff --git a/src/parser/parser.php b/src/parser/parser.php
index fef2a83e..1604d5de 100644
--- a/src/parser/parser.php
+++ b/src/parser/parser.php
@@ -83,6 +83,15 @@
* $parser->options->parseTextAttachmentsAsFiles = true;
*
*
+ * In some cases, you might only want to create {@link ezcMailFile} objects
+ * for text attachments that are sub-parts of multipart/mixed parts.
+ * In that case, you can use the parseMultipartMixedTextAttachmentsAsFiles
+ * option. Example:
+ *
+ * $parser = new ezcMailParser();
+ * $parser->options->parseMultipartMixedTextAttachmentsAsFiles = true;
+ *
+ *
* @property ezcMailParserOptions $options
* Holds the options you can set to the mail parser.
*
diff --git a/src/parser/parts/multipart_parser.php b/src/parser/parts/multipart_parser.php
index d31fb802..52ecbd50 100644
--- a/src/parser/parts/multipart_parser.php
+++ b/src/parser/parts/multipart_parser.php
@@ -181,7 +181,7 @@ public function parseBody( $origLine )
{
if ( $this->parserState == self::PARSE_STATE_HEADERS && $line == '' )
{
- $this->currentPartParser = self::createPartParserForHeaders( $this->currentPartHeaders );
+ $this->currentPartParser = self::createPartParserForHeaders( $this->currentPartHeaders, $this );
$this->parserState = self::PARSE_STATE_BODY;
}
else if ( $this->parserState == self::PARSE_STATE_HEADERS )
diff --git a/src/parser/parts/rfc822_parser.php b/src/parser/parts/rfc822_parser.php
index eed7c413..0a74c30d 100644
--- a/src/parser/parts/rfc822_parser.php
+++ b/src/parser/parts/rfc822_parser.php
@@ -107,7 +107,7 @@ public function parseBody( $origLine )
}
// get the correct body type
- $this->bodyParser = self::createPartParserForHeaders( $headers );
+ $this->bodyParser = self::createPartParserForHeaders( $headers, $this );
}
else if ( $this->parserState == self::PARSE_STATE_HEADERS )
{
diff --git a/tests/parser/data/various/test-html-text-and-text-attachment b/tests/parser/data/various/test-html-text-and-text-attachment
new file mode 100644
index 00000000..581e8159
--- /dev/null
+++ b/tests/parser/data/various/test-html-text-and-text-attachment
@@ -0,0 +1,64 @@
+MIME-Version: 1.0
+Date: Tue, 27 Aug 2024 09:28:55 +1000
+Message-ID:
+Subject: test txt attachment
+From: Sam Lee
+To: samlee001@example.com
+Content-Type: multipart/mixed; boundary="0000000000000f1e9806209e7d06"
+
+--0000000000000f1e9806209e7d06
+Content-Type: multipart/alternative; boundary="0000000000000f1e9606209e7d04"
+
+--0000000000000f1e9606209e7d04
+Content-Type: text/plain; charset="UTF-8"
+
+test
+
+--
+Sam Lee
+
+
+Big Business
+
+He/Him/His
+
+--0000000000000f1e9606209e7d04
+Content-Type: text/html; charset="UTF-8"
+Content-Transfer-Encoding: quoted-printable
+
+
+--0000000000000f1e9606209e7d04--
+--0000000000000f1e9806209e7d06
+Content-Type: text/plain; charset="UTF-8"; name="2_load_xss.html.txt"
+Content-Disposition: attachment; filename="2_load_xss.html.txt"
+Content-Transfer-Encoding: base64
+X-Attachment-Id: f_m0bmqmih2
+Content-ID:
+
+77u/PGh0bWw+DQo8Zm9ybSBlbmN0eXBlPSJhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29k
+ZWQiIG1ldGhvZD0iUE9TVCIgYWN0aW9uPSJodHRwOi8vbG9jYWxob3N0L3dwLWFkbWluL2FkbWlu
+LnBocD9wYWdlPUNpdmlDUk0mcT1jaXZpY3JtJTJGYWRtaW4lMkZja2VkaXRvciI+DQo8aW5wdXQg
+dHlwZT0idGV4dCIgdmFsdWU9Im1vb25vIiBuYW1lPSJjb25maWdfc2tpbiI+IDxicj4NCjxpbnB1
+dCB0eXBlPSJ0ZXh0IiB2YWx1ZT0iQ0tFRElUT1IuZWRpdG9yQ29uZmlnID0gZnVuY3Rpb24oIGNv
+bmZpZyApIHt9OyIgbmFtZT0iY29uZmlnIj4gPGJyPg0KPGlucHV0IHR5cGU9InRleHQiIHZhbHVl
+PSIiIG5hbWU9ImNvbmZpZ19leHRyYVBsdWdpbnMiPiA8YnI+DQo8aW5wdXQgdHlwZT0idGV4dCIg
+dmFsdWU9Ii4uLy4uLy4uLy4uLy4uL3VwbG9hZHMvY2l2aWNybS9wZXJzaXN0L2NybS1ja2VkaXRv
+ci14c3MuanMiIG5hbWU9ImNvbmZpZ19jdXN0b21Db25maWciPiA8YnI+DQo8aW5wdXQgdHlwZT0i
+c3VibWl0IiB2YWx1ZT0iUlVOIFBPQyI+DQo8L2Zvcm0+DQo8L2h0bWw+
+--0000000000000f1e9806209e7d06
+Content-Type: application/msword; name="form_03.doc"
+Content-Disposition: attachment; filename="form_03.doc"
+Content-Transfer-Encoding: base64
+X-Attachment-Id: f_m0bmqhqx1
+Content-ID:
+
+
+--0000000000000f1e9806209e7d06
+Content-Type: application/pdf; name="2932_1 Ward Grouped Mayor-Lismore.pdf"
+Content-Disposition: attachment; filename="2932_1 Ward Grouped Mayor-Lismore.pdf"
+Content-Transfer-Encoding: base64
+X-Attachment-Id: f_m0bmqhpv0
+Content-ID:
+
+
+--0000000000000f1e9806209e7d06--
diff --git a/tests/parser/parser_test.php b/tests/parser/parser_test.php
index 8ef979be..8c324fae 100644
--- a/tests/parser/parser_test.php
+++ b/tests/parser/parser_test.php
@@ -1863,5 +1863,117 @@ public function testSpaceBeforeFileName()
}
}
+ public function testVarious14a()
+ {
+ $parser = new ezcMailParser();
+ $parser->options->parseTextAttachmentsAsFiles = false;
+
+ $set = new SingleFileSet( 'various/test-html-text-and-text-attachment' );
+ $mail = $parser->parseMail( $set );
+ $this->assertEquals( 1, count( $mail ) );
+ $mail = $mail[0];
+ $parts = $mail->body->getParts();
+
+ $this->assertEquals( 4, count( $parts ) );
+ $this->assertEquals( 'ezcMailMultipartAlternative', get_class( $parts[0] ) );
+
+ $this->assertEquals( 'ezcMailText', get_class( $parts[1] ) );
+ $this->assertEquals( 'plain', $parts[1]->subType );
+
+ $this->assertEquals( 'ezcMailFile', get_class( $parts[2] ) );
+ $this->assertEquals( 'form_03.doc', basename( $parts[2]->fileName ) );
+ $this->assertEquals( 'application', $parts[2]->contentType );
+ $this->assertEquals( 'msword', $parts[2]->mimeType );
+
+ $this->assertEquals( 'ezcMailFile', get_class( $parts[3] ) );
+ $this->assertEquals( '2932_1 Ward Grouped Mayor-Lismore.pdf', basename( $parts[3]->fileName ) );
+ $this->assertEquals( 'application', $parts[3]->contentType );
+ $this->assertEquals( 'pdf', $parts[3]->mimeType );
+
+ $alternativeParts = $parts[0]->getParts();
+ $this->assertEquals( 2, count( $alternativeParts ) );
+ $this->assertEquals( 'ezcMailText', get_class( $alternativeParts[0] ) );
+ $this->assertEquals( 'plain', $alternativeParts[0]->subType );
+ $this->assertEquals( 'ezcMailText', get_class( $alternativeParts[1] ) );
+ $this->assertEquals( 'html', $alternativeParts[1]->subType );
+ }
+
+ public function testVarious14b()
+ {
+ $parser = new ezcMailParser();
+ $parser->options->parseTextAttachmentsAsFiles = true;
+
+ $set = new SingleFileSet( 'various/test-html-text-and-text-attachment' );
+ $mail = $parser->parseMail( $set );
+ $this->assertEquals( 1, count( $mail ) );
+ $mail = $mail[0];
+ $parts = $mail->body->getParts();
+
+ $this->assertEquals( 4, count( $parts ) );
+ $this->assertEquals( 'ezcMailMultipartAlternative', get_class( $parts[0] ) );
+
+ $this->assertEquals( 'ezcMailFile', get_class( $parts[1] ) );
+ $this->assertEquals( '2_load_xss.html.txt', basename( $parts[1]->fileName ) );
+ $this->assertEquals( 'text', $parts[1]->contentType );
+ $this->assertEquals( 'plain', $parts[1]->mimeType );
+
+ $this->assertEquals( 'ezcMailFile', get_class( $parts[2] ) );
+ $this->assertEquals( 'form_03.doc', basename( $parts[2]->fileName ) );
+ $this->assertEquals( 'application', $parts[2]->contentType );
+ $this->assertEquals( 'msword', $parts[2]->mimeType );
+
+ $this->assertEquals( 'ezcMailFile', get_class( $parts[3] ) );
+ $this->assertEquals( '2932_1 Ward Grouped Mayor-Lismore.pdf', basename( $parts[3]->fileName ) );
+ $this->assertEquals( 'application', $parts[3]->contentType );
+ $this->assertEquals( 'pdf', $parts[3]->mimeType );
+
+ $alternativeParts = $parts[0]->getParts();
+ $this->assertEquals( 2, count( $alternativeParts ) );
+ $this->assertEquals( 'ezcMailFile', get_class( $alternativeParts[0] ) );
+ $this->assertEquals( 'text', $alternativeParts[0]->contentType );
+ $this->assertEquals( 'plain', $alternativeParts[0]->mimeType );
+
+ $this->assertEquals( 'ezcMailFile', get_class( $alternativeParts[1] ) );
+ $this->assertEquals( 'application', $alternativeParts[1]->contentType );
+ $this->assertEquals( 'html', $alternativeParts[1]->mimeType );
+ }
+
+
+ public function testVarious14c()
+ {
+ $parser = new ezcMailParser();
+ $parser->options->parseMultipartMixedTextAttachmentsAsFiles = true;
+
+ $set = new SingleFileSet( 'various/test-html-text-and-text-attachment' );
+ $mail = $parser->parseMail( $set );
+ $this->assertEquals( 1, count( $mail ) );
+ $mail = $mail[0];
+ $parts = $mail->body->getParts();
+
+ $this->assertEquals( 4, count( $parts ) );
+ $this->assertEquals( 'ezcMailMultipartAlternative', get_class( $parts[0] ) );
+
+ $this->assertEquals( 'ezcMailFile', get_class( $parts[1] ) );
+ $this->assertEquals( '2_load_xss.html.txt', basename( $parts[1]->fileName ) );
+ $this->assertEquals( 'text', $parts[1]->contentType );
+ $this->assertEquals( 'plain', $parts[1]->mimeType );
+
+ $this->assertEquals( 'ezcMailFile', get_class( $parts[2] ) );
+ $this->assertEquals( 'form_03.doc', basename( $parts[2]->fileName ) );
+ $this->assertEquals( 'application', $parts[2]->contentType );
+ $this->assertEquals( 'msword', $parts[2]->mimeType );
+
+ $this->assertEquals( 'ezcMailFile', get_class( $parts[3] ) );
+ $this->assertEquals( '2932_1 Ward Grouped Mayor-Lismore.pdf', basename( $parts[3]->fileName ) );
+ $this->assertEquals( 'application', $parts[3]->contentType );
+ $this->assertEquals( 'pdf', $parts[3]->mimeType );
+
+ $alternativeParts = $parts[0]->getParts();
+ $this->assertEquals( 2, count( $alternativeParts ) );
+ $this->assertEquals( 'ezcMailText', get_class( $alternativeParts[0] ) );
+ $this->assertEquals( 'plain', $alternativeParts[0]->subType );
+ $this->assertEquals( 'ezcMailText', get_class( $alternativeParts[1] ) );
+ $this->assertEquals( 'html', $alternativeParts[1]->subType );
+ }
}
?>