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 new option 'parseMultipartMixedTextAttachmentsAsFiles' to ezcMailParserOptions #96

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}"
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
21 changes: 19 additions & 2 deletions src/options/parser_options.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
* </code>
Expand All @@ -43,6 +44,7 @@
* $parser->options->mailClass = 'myCustomMailClass'; // extends ezcMail
* $parser->options->fileClass = 'myCustomFileClass'; // extends ezcMailFile
* $parser->options->parseTextAttachmentsAsFiles = true;
* $parser->options->parseMultipartMixedTextAttachmentsAsFiles = true;
* </code>
*
* @property string $mailClass
Expand All @@ -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//
Expand All @@ -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 );
}
Expand Down Expand Up @@ -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 );
}
Expand Down
34 changes: 31 additions & 3 deletions src/parser/interfaces/part_parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
*
* <code>
* $parser = new ezcMailParser();
* $parser->options->parseMultipartMixedTextAttachmentsAsFiles = true;
* // call $parser->parseMail( $set );
* </code>
*
* @var bool
*/
public static $parseMultipartMixedTextAttachmentsAsFiles = false;

/**
* The name of the last header parsed.
*
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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 );
}
Expand Down
9 changes: 9 additions & 0 deletions src/parser/parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@
* $parser->options->parseTextAttachmentsAsFiles = true;
* </code>
*
* 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:
* <code>
* $parser = new ezcMailParser();
* $parser->options->parseMultipartMixedTextAttachmentsAsFiles = true;
* </code>
*
* @property ezcMailParserOptions $options
* Holds the options you can set to the mail parser.
*
Expand Down
2 changes: 1 addition & 1 deletion src/parser/parts/multipart_parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down
2 changes: 1 addition & 1 deletion src/parser/parts/rfc822_parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
{
Expand Down
64 changes: 64 additions & 0 deletions tests/parser/data/various/test-html-text-and-text-attachment
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
MIME-Version: 1.0
Date: Tue, 27 Aug 2024 09:28:55 +1000
Message-ID: <[email protected]>
Subject: test txt attachment
From: Sam Lee <[email protected]>
To: [email protected]
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: <f_m0bmqmih2>

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: <f_m0bmqhqx1>


--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: <f_m0bmqhpv0>


--0000000000000f1e9806209e7d06--
112 changes: 112 additions & 0 deletions tests/parser/parser_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}
}
?>
Loading