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

Long filenames in "Content-Disposition: attachment; filename" breaks extension #1469

Closed
JelleSFS opened this issue Jun 5, 2018 · 31 comments · Fixed by #1840
Closed

Long filenames in "Content-Disposition: attachment; filename" breaks extension #1469

JelleSFS opened this issue Jun 5, 2018 · 31 comments · Fixed by #1840

Comments

@JelleSFS
Copy link

JelleSFS commented Jun 5, 2018

- Problem description
I've been getting several complaints by customers about them receiving mail with attachments which they cannot open because of some malformed file extension.

It turns out the filenames are folded after 76 characters. This SEEMS RFC-compliant.
The extra spacing character is interpreted as a space and, of course, doesn't cause problems when not in the extension.

When sending the same, original, file through Outlook (client or web), GMail or any other hosted service, the file arrives with the complete and untouched extension.

- Code/Steps to reproduce
Send an email and include a file with a filename just over 76 characters long.

$filename = "WayTooLongFileNameWhichWillCorruptTheFileExtensionAtCharacterSeventySeven.pdf"; // filename is 77 characters
$mailer->AddAttachment($att->file, $filename, $att->encoding, $att->mimeType);

- Debug Output

--b1_6IjFX38R88zg8cQbwrrdGs9XKE4P6sRVpqGdadM00Is
Content-Type: application/pdf; name="WayTooLongFileNameWhichWillCorruptTheFileExtensionAtCharacterSeventySeven.pd f"
Content-Transfer-Encoding: base64
Content-ID: <WayTooLongFileNameWhichWillCorruptTheFileExtensionAtCharacterSeventySeven.pdf>
Content-Disposition: attachment; filename="WayTooLongFileNameWhichWillCorruptTheFileExtensionAtCharacterSeventySeven.pd f"

- Possible solution
As said, the current implementation SEEMS RFC-compliant. But RFC 2184 Section 3 speaks about Parameter Value Continuation and ways to break too long parameter values into smaller chunks without folding and altering the original values.

RFC 2184 Section 3
...

The obvious solution, then, is to use multiple parameters to contain
a single parameter value and to use some kind of distinguished name
to indicate when this is being done. And this obvious solution is
exactly what is specified here: The asterisk character ("*") followed
by a decimal count is employed to indicate that multiple parameters
are being used to encapsulate a single parameter value. The count
starts at 0 and increments by 1 for each subsequent section of the
parameter value. Decimal values are used and neither leading zeroes
nor gaps in the sequence are allowed.

The original parameter value is recovered by concatenating the
various sections of the parameter, in order. For example, the
content-type field

 Content-Type: message/external-body; access-type=URL;
  URL*0="ftp:https://";
  URL*1="cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar"

is semantically identical to

 Content-Type: message/external-body; access-type=URL;
  URL="ftp:https://cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar"

Shortening filenames is not an option because:

  • clients include specific indentifiers into the filename which can differ in length
  • regular email clients are able to send files with names exceeding 76 characters in length without folding/altering

- System-info
PHPMailer 6.0.5 (Composer)
PHP 7.1
CentOS 6.9/7.4

Can you look into this?
Thank you!

@JelleSFS
Copy link
Author

JelleSFS commented Aug 7, 2018

Does anyone have any thoughts on this?

@caugner
Copy link
Contributor

caugner commented Sep 23, 2019

I can confirm that this bug is still present in v6.0.7:

<?php

use PHPMailer\PHPMailer\PHPMailer;

require_once 'vendor/autoload.php';

$mail = new PHPMailer();
$mail->setFrom('[email protected]');
$mail->addAddress('[email protected]');
$mail->Subject = 'Attachment with long filename';
$mail->Body = 'See attachment.';
$mail->addStringAttachment('Acme', '0________1_________2_________3_________4_________5_________6_________712.txt');
$mail->addStringAttachment('Acme', '0________1_________2_________3_________4_________5_________6_________7123.txt');
$mail->addStringAttachment('Acme', '0________1_________2_________3_________4_________5_________6_________71234.txt');
$mail->addStringAttachment('Acme', '0________1_________2_________3_________4_________5_________6_________712345.txt');
$mail->preSend();

$class = new ReflectionClass(PHPMailer::class);
$property = ($class)->getProperty('MIMEBody');
$property->setAccessible(true);
echo $property->getValue($mail);
Output (click to expand)
This is a multi-part message in MIME format.
--b1_jlMWJgilAaGhwtM5ADv0clNuf9UzYKN0mFyujopXA
Content-Type: text/plain; charset=us-ascii

See attachment.

--b1_jlMWJgilAaGhwtM5ADv0clNuf9UzYKN0mFyujopXA
Content-Type: text/plain; name="0________1_________2_________3_________4_________5_________6_________712.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=0________1_________2_________3_________4_________5_________6_________712.txt

QWNtZQ==

--b1_jlMWJgilAaGhwtM5ADv0clNuf9UzYKN0mFyujopXA
Content-Type: text/plain; name="0________1_________2_________3_________4_________5_________6_________7123.tx
 t"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="0________1_________2_________3_________4_________5_________6_________7123.tx
 t"

QWNtZQ==

--b1_jlMWJgilAaGhwtM5ADv0clNuf9UzYKN0mFyujopXA
Content-Type: text/plain; name="0________1_________2_________3_________4_________5_________6_________71234.t
 xt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="0________1_________2_________3_________4_________5_________6_________71234.t
 xt"

QWNtZQ==

--b1_jlMWJgilAaGhwtM5ADv0clNuf9UzYKN0mFyujopXA
Content-Type: text/plain; name="0________1_________2_________3_________4_________5_________6_________712345.
 txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="0________1_________2_________3_________4_________5_________6_________712345.
 txt"

QWNtZQ==

--b1_jlMWJgilAaGhwtM5ADv0clNuf9UzYKN0mFyujopXA--

Clients (e.g. Thunderbird) don't ignore the whitespace introduced by the forced line break.

@caugner
Copy link
Contributor

caugner commented Sep 23, 2019

Here's how Thunderbird formats these long filenames:

(click to expand)
This is a multi-part message in MIME format.
--------------57B868A2D60F97EE89D8CB28
Content-Type: text/plain; charset=utf-8; format=flowed
Content-Transfer-Encoding: 7bit

See attachments.

--------------57B868A2D60F97EE89D8CB28
Content-Type: text/plain; charset=UTF-8;
 name="0________1_________2_________3_________4_________5_________6_________712.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
 filename*0="0________1_________2_________3_________4_________5_________6";
 filename*1="_________712.txt"

QWNtZQ==
--------------57B868A2D60F97EE89D8CB28
Content-Type: text/plain; charset=UTF-8;
 name="0________1_________2_________3_________4_________5_________6_________7123.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
 filename*0="0________1_________2_________3_________4_________5_________6";
 filename*1="_________7123.txt"

QWNtZQ==
--------------57B868A2D60F97EE89D8CB28
Content-Type: text/plain; charset=UTF-8;
 name="0________1_________2_________3_________4_________5_________6_________71234.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
 filename*0="0________1_________2_________3_________4_________5_________6";
 filename*1="_________71234.txt"

QWNtZQ==
--------------57B868A2D60F97EE89D8CB28
Content-Type: text/plain; charset=UTF-8;
 name="0________1_________2_________3_________4_________5_________6_________712345.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
 filename*0="0________1_________2_________3_________4_________5_________6";
 filename*1="_________712345.txt"

QWNtZQ==
--------------57B868A2D60F97EE89D8CB28--

Note that Content-Type is split at "semicolons" before splitting within the value, whereas Content-Disposition is split right away using filename*n notation.

@caugner
Copy link
Contributor

caugner commented Sep 23, 2019

A fix should be possible in PHPMailer::attachAll().

@Synchro
Copy link
Member

Synchro commented Sep 23, 2019

Dang, yet another header encoding scheme! That's from RFC2184, as suggested by RC2183.

I suspect this is the same problem as in #1525, though the solution there will be to use RFC2047 encoding, which has the added advantage that it works with 8-bit charsets. I think that would probably work here too. That said, 2184 encodes individual fields within a header across multiple lines, whereas 2047 encodes the entire header value; I'm not sure which would be the better way, though I'm tempted more by 2047 because it does not require parsing the header value at all.

@Synchro
Copy link
Member

Synchro commented Sep 23, 2019

The difference between the two approaches in your example is that the Content-type header is doing normal RFC822 folding, whereas the content-disposition header is doing RFC2184 followed by RFC822. I expect that the former would start doing 2047 encoding if you made your filename long enough. I'm not too keen on header-specific encoding schemes...

@caugner
Copy link
Contributor

caugner commented Sep 23, 2019

@Synchro Actually Thunderbird never wraps the Content-Types name:

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

See attachment.

--------------968B4C00104B2FB5873AF594
Content-Type: text/plain; charset=UTF-8;
 name="0________1_________2_________3_________4_________5_________6_________7_________8_________9________10________11________12________13________14________15________16________17________18________19________20.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
 filename*0="0________1_________2_________3_________4_________5_________6";
 filename*1="_________7_________8_________9________10________11________12";
 filename*2="________13________14________15________16________17________18";
 filename*3="________19________20.txt"

QWNtZQ==
--------------968B4C00104B2FB5873AF594--

@Synchro
Copy link
Member

Synchro commented Sep 23, 2019

Trying going over 998 chars in that header; I would expect it to start doing RFC2047 like Apple Mail does.

@caugner
Copy link
Contributor

caugner commented Sep 23, 2019

I'm on a Windows machine right now, where filenames are limited to 255 characters, so I'm afraid I can't test that.

@Synchro
Copy link
Member

Synchro commented Sep 23, 2019

Ah, you can probably provoke the same behaviour using a very long subject line - ultimately they're all headers.

@caugner
Copy link
Contributor

caugner commented Sep 23, 2019

I just tried to send an email using Thunderbird with the following subject, but ironically this resulted in the mail not having any subject at all: _________1_________2_________3_________4_________5_________6_________7_________8_________9_________0_________1_________2_________3_________4_________5_________6_________7_________8_________9_________0_________1_________2_________3_________4_________5_________6_________7_________8_________9_________0_________1_________2_________3_________4_________5_________6_________7_________8_________9_________0_________1_________2_________3_________4_________5_________6_________7_________8_________9_________0_________1_________2_________3_________4_________5_________6_________7_________8_________9_________0_________1_________2_________3_________4_________5_________6_________7_________8_________9_________0_________1_________2_________3_________4_________5_________6_________7_________8_________9_________0_________1_________2_________3_________4_________5_________6_________7_________8_________9_________0_________1_________2_________3_________4_________5_________6_________7_________8_________9_________0

@Synchro
Copy link
Member

Synchro commented Sep 23, 2019

Ha! It's nice to find bugs like that :) Have a look at #1525 for an example of how Apple mail does it.

@caugner
Copy link
Contributor

caugner commented Sep 23, 2019

Have you already tried sending an attachment with a long filename using Apple Mail? I don't find it obvious that Apple Mail encodes regular headers and multipart headers the same way.

PS: I just got the following long header line in Thunderbird with a 200 character subject:

Subject: 0________1_________2_________3_________4_________5_________6_________7_________8_________9________10________11________12________13________14________15________16________17________18________19________20.txt

@Synchro
Copy link
Member

Synchro commented Sep 23, 2019

There are two thresholds for line length in email: 76 and 998. Senders should limit line length to 76 and must limit line length to 998. You can see in your earlier examples that it attempts to do the former by folding the name property onto a new line, but after that it can't fold at the next 76 char boundary because the value has no spaces in to permit folding, so it's allowed to grow longer than 76 chars. If that continues until it hits 998 chars, RFC822 doesn't say what should happen as it can't fold a very long unbroken value without corrupting it - effectively what's been flagged in this issue and #1525. RFC2047 encoding provides a neat workaround for that problem, folding unbroken lines without corruption. Header lines that long are historically unusual, but are increasingly occurring because of headers like DKIM and list-unsubscribe that can contain long URLs.

A 200 char subject is longer than 76, but won't cause problems because it's well below 998, so it will work as is with no additional encoding.

@Synchro
Copy link
Member

Synchro commented Sep 23, 2019

The bug here could be that it's forcing folding at 76 chars instead of letting it expand up to 998.

@caugner
Copy link
Contributor

caugner commented Sep 23, 2019

I find it a bit counterintuitive that the name value is wrapped in PHPMailer::encodeHeader() independently from the Content-Type line to this point:

PHPMailer/src/PHPMailer.php

Lines 2942 to 2947 in 3c1c6e0

$mime[] = sprintf(
'Content-Type: %s; name="%s"%s',
$type,
$this->encodeHeader($this->secureHeader($name)),
static::$LE
);

The responsible wrapping happens in line 3171 here btw:

PHPMailer/src/PHPMailer.php

Lines 3166 to 3175 in 3c1c6e0

} elseif (strlen($str) > $maxlen) {
//No chars need encoding, but line is too long, so fold it
$encoded = trim($this->wrapText($str, $maxlen, false));
if ($str == $encoded) {
//Wrapping nicely didn't work, wrap hard instead
$encoded = trim(chunk_split($str, static::STD_LINE_LENGTH, static::$LE));
}
$encoded = str_replace(static::$LE, "\n", trim($encoded));
$encoded = preg_replace('/^(.*)$/m', ' \\1', $encoded);
} else {

@caugner
Copy link
Contributor

caugner commented Sep 23, 2019

Replacing this:

PHPMailer/src/PHPMailer.php

Lines 2941 to 2954 in 3c1c6e0

if (!empty($name)) {
$mime[] = sprintf(
'Content-Type: %s; name="%s"%s',
$type,
$this->encodeHeader($this->secureHeader($name)),
static::$LE
);
} else {
$mime[] = sprintf(
'Content-Type: %s%s',
$type,
static::$LE
);
}

With the following resulted in a slightly better folding (at least for the Content-Type):

                if (!empty($name)) {
                    $value = sprintf(
                        '%s; name="%s"%s',
                        $type,
                        $this->secureHeader($name),
                        static::$LE
                    );
                } else {
                    $value = $type;
                }
                $mime[] = sprintf(
                    'Content-Type: %s%s',
                    $this->encodeHeader($value),
                    static::$LE
                );
Result (click to expand)
This is a multi-part message in MIME format.
--b1_NvrkCrmMXVYh2dGBtmaF5quphhyZI8RbWP8T4Mcjus
Content-Type: text/plain; charset=us-ascii

See attachment.

--b1_NvrkCrmMXVYh2dGBtmaF5quphhyZI8RbWP8T4Mcjus
Content-Type: text/plain;
 name="0________1_________2_________3_________4_________5_________6_________712.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=0________1_________2_________3_________4_________5_________6_________712.txt

QWNtZQ==

--b1_NvrkCrmMXVYh2dGBtmaF5quphhyZI8RbWP8T4Mcjus
Content-Type: text/plain;
 name="0________1_________2_________3_________4_________5_________6_________7123.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="0________1_________2_________3_________4_________5_________6_________7123.tx
 t"

QWNtZQ==

--b1_NvrkCrmMXVYh2dGBtmaF5quphhyZI8RbWP8T4Mcjus
Content-Type: text/plain;
 name="0________1_________2_________3_________4_________5_________6_________71234.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="0________1_________2_________3_________4_________5_________6_________71234.t
 xt"

QWNtZQ==

--b1_NvrkCrmMXVYh2dGBtmaF5quphhyZI8RbWP8T4Mcjus
Content-Type: text/plain;
 name="0________1_________2_________3_________4_________5_________6_________712345.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="0________1_________2_________3_________4_________5_________6_________712345.
 txt"

QWNtZQ==

--b1_NvrkCrmMXVYh2dGBtmaF5quphhyZI8RbWP8T4Mcjus--

@caugner
Copy link
Contributor

caugner commented Sep 23, 2019

This could also be done for Content-Disposition:

PHPMailer/src/PHPMailer.php

Lines 2974 to 2996 in 3c1c6e0

if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) {
$mime[] = sprintf(
'Content-Disposition: %s; filename="%s"%s',
$disposition,
$encoded_name,
static::$LE . static::$LE
);
} else {
if (!empty($encoded_name)) {
$mime[] = sprintf(
'Content-Disposition: %s; filename=%s%s',
$disposition,
$encoded_name,
static::$LE . static::$LE
);
} else {
$mime[] = sprintf(
'Content-Disposition: %s%s',
$disposition,
static::$LE . static::$LE
);
}
}

Replace by:

                    if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) {
                        $value = sprintf(
                            '%s; filename="%s"',
                            $disposition,
                            $encoded_name
                        );
                    } else {
                        if (!empty($encoded_name)) {
                            $value = sprintf(
                                '%s; filename=%s',
                                $disposition,
                                $encoded_name
                            );
                        } else {
                            $value = sprintf(
                                '%s%s',
                                $disposition
                            );
                        }
                    }
                    $mime[] = sprintf(
                        'Content-Disposition: %s%s',
                        $this->encodeHeader($value),
                        static::$LE . static::$LE
                    );
Result (click to expand)
This is a multi-part message in MIME format.
--b1_BE2rRDAJShL3wbYrhOymXQHIkEfOepzpaGfaWc2buE
Content-Type: text/plain; charset=us-ascii

See attachment.

--b1_BE2rRDAJShL3wbYrhOymXQHIkEfOepzpaGfaWc2buE
Content-Type: text/plain;
 name="0________1_________2_________3_________4_________5_________6_________712.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
 filename=0________1_________2_________3_________4_________5_________6_________712.txt

QWNtZQ==

--b1_BE2rRDAJShL3wbYrhOymXQHIkEfOepzpaGfaWc2buE
Content-Type: text/plain;
 name="0________1_________2_________3_________4_________5_________6_________7123.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
 filename="0________1_________2_________3_________4_________5_________6_________7123.tx
  t"

QWNtZQ==

--b1_BE2rRDAJShL3wbYrhOymXQHIkEfOepzpaGfaWc2buE
Content-Type: text/plain;
 name="0________1_________2_________3_________4_________5_________6_________71234.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
 filename="0________1_________2_________3_________4_________5_________6_________71234.t
  xt"

QWNtZQ==

--b1_BE2rRDAJShL3wbYrhOymXQHIkEfOepzpaGfaWc2buE
Content-Type: text/plain;
 name="0________1_________2_________3_________4_________5_________6_________712345.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
 filename="0________1_________2_________3_________4_________5_________6_________712345.
  txt"

QWNtZQ==

--b1_BE2rRDAJShL3wbYrhOymXQHIkEfOepzpaGfaWc2buE--

@caugner
Copy link
Contributor

caugner commented Sep 23, 2019

@Synchro Would you see any problem with this approach (as kind of suggested by yourself):

https://github.com/PHPMailer/PHPMailer/pull/1840/files

Result (click to expand)
This is a multi-part message in MIME format.
--b1_nfXwmUrvTe7p2bfr3MftNT6Q40w1zPglvJZMxbSs04
Content-Type: text/plain; charset=us-ascii

See attachment.

--b1_nfXwmUrvTe7p2bfr3MftNT6Q40w1zPglvJZMxbSs04
Content-Type: text/plain; name="0________1_________2_________3_________4_________5_________6_________712.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=0________1_________2_________3_________4_________5_________6_________712.txt

QWNtZQ==

--b1_nfXwmUrvTe7p2bfr3MftNT6Q40w1zPglvJZMxbSs04
Content-Type: text/plain; name="=?iso-8859-1?Q?0=5F=5F=5F=5F=5F=5F=5F=5F1=5F=5F=5F=5F=5F=5F=5F=5F=5F2=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F3=5F=5F=5F=5F=5F=5F=5F=5F=5F4=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F5=5F=5F=5F=5F=5F=5F=5F=5F=5F6=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F7123.txt?="
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="=?iso-8859-1?Q?0=5F=5F=5F=5F=5F=5F=5F=5F1=5F=5F=5F=5F=5F=5F=5F=5F=5F2=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F3=5F=5F=5F=5F=5F=5F=5F=5F=5F4=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F5=5F=5F=5F=5F=5F=5F=5F=5F=5F6=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F7123.txt?="

QWNtZQ==

--b1_nfXwmUrvTe7p2bfr3MftNT6Q40w1zPglvJZMxbSs04
Content-Type: text/plain; name="=?iso-8859-1?Q?0=5F=5F=5F=5F=5F=5F=5F=5F1=5F=5F=5F=5F=5F=5F=5F=5F=5F2=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F3=5F=5F=5F=5F=5F=5F=5F=5F=5F4=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F5=5F=5F=5F=5F=5F=5F=5F=5F=5F6=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F71234.txt?="
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="=?iso-8859-1?Q?0=5F=5F=5F=5F=5F=5F=5F=5F1=5F=5F=5F=5F=5F=5F=5F=5F=5F2=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F3=5F=5F=5F=5F=5F=5F=5F=5F=5F4=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F5=5F=5F=5F=5F=5F=5F=5F=5F=5F6=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F71234.txt?="

QWNtZQ==

--b1_nfXwmUrvTe7p2bfr3MftNT6Q40w1zPglvJZMxbSs04
Content-Type: text/plain; name="=?iso-8859-1?Q?0=5F=5F=5F=5F=5F=5F=5F=5F1=5F=5F=5F=5F=5F=5F=5F=5F=5F2=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F3=5F=5F=5F=5F=5F=5F=5F=5F=5F4=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F5=5F=5F=5F=5F=5F=5F=5F=5F=5F6=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F712345.txt?="
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="=?iso-8859-1?Q?0=5F=5F=5F=5F=5F=5F=5F=5F1=5F=5F=5F=5F=5F=5F=5F=5F=5F2=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F3=5F=5F=5F=5F=5F=5F=5F=5F=5F4=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F5=5F=5F=5F=5F=5F=5F=5F=5F=5F6=5F?=
 =?iso-8859-1?Q?=5F=5F=5F=5F=5F=5F=5F=5F712345.txt?="

QWNtZQ==

--b1_nfXwmUrvTe7p2bfr3MftNT6Q40w1zPglvJZMxbSs04--

Synchro pushed a commit that referenced this issue Sep 25, 2019
* Always Q-encode headers exceeding maximum length

Previously, headers exceeding the maximum line length without
any special characters were only folded. This lead to problems
with long filenames (#1469) and long headers in general (#1525).

Now, long headers are always Q-encoded (and still folded).

* Use ASCII as Q-encoding charset if applicable

Previously, headers were Q-encoded using the message
charset, e.g. UTF-8. This is excessive for ASCII
values, as it requires a unicode engine.

Now, we use ASCII if we only find 7-bit characters.

* Separate header encoding from encoding selection

* Use ASCII for B-encoding as well

* Refactor max line length calculation

Previously, we calculated the maximum
line length for header encoding both
for B- and Q-encoding, even though
they share the same limits.

Now, we calculate these once for both.
@Synchro
Copy link
Member

Synchro commented Sep 25, 2019

Changes from #1840 should have fixed this issue - please report if you still see this problem.

@chrisSCM
Copy link

chrisSCM commented Jan 28, 2020

I have just updated to version 6.1.4 and I still have this issue.
Is there anything special, a certain configuration or the like, that I have to do to avoid the cut off?

I guess my code is nothing special:

    $mail = new PHPMailer;
    $mail->isSMTP();
    $mail->Host = _MAIL_HOST;
    $mail->SMTPAuth = _MAIL_SMTPAUTH;
    $mail->Username = _MAIL_USER;
    $mail->Password = _MAIL_PWD;
    $mail->SMTPSecure = _MAIL_SMTPSECURE;
    $mail->Port = _MAIL_PORT;
    $mail->setFrom(_MAIL_FROM, _MAIL_FROM_NAME);
    $mail->addAddress($address);
    $mail->SMTPDebug = 0; // Enable verbose debug output
    $mail->CharSet = 'UTF-8';
    $mail->isHTML(true);
    $mail->Subject = $subjectl;
    $mail->Body = $messageHTML;
    $mail->addAttachment($pj, $longFilename));
    $mail->send();

@caugner
Copy link
Contributor

caugner commented Jan 28, 2020

@chrisSCM Would you be able to post the relevant lines of the email source code including the wrapped filename?

@chrisSCM
Copy link

Of course - thanks for your fast reply. (I had to anonymize a few fields first)

Return-path: <[email protected]>
Delivery-date: Tue, 28 Jan 2020 15:45:08 +0100
Received: from mi006.mc1.domain.com ([80.237.43.43])
	by vwp0932.webpack.domain.com running ExIM with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
	id 1iwQU2-0000Gr-9y; Tue, 28 Jan 2020 15:45:08 +0100
Received: from smtpout11.r2.mail-out.email.net ([67.36.141.23])
	by ... with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256)
	id 1iwQT8-00048a-Hu
	for [email protected]; Tue, 28 Jan 2020 15:45:04 +0100
Date: Tue, 28 Jan 2020 15:45:04 +0100
To: <[email protected]>
From: Firma GmbH <[email protected]>
Subject: Quittung Nr.  025-3 RRechnungsnummerr W259
Message-ID: <[email protected]>
X-Mailer: PHPMailer 6.1.4 (https://github.com/PHPMailer/PHPMailer)
MIME-Version: 1.0
Content-Type: multipart/mixed;
	boundary="b1_xON0QmUygNqKaTefGv0U65iRiam7TQEABJP8nJNNin8"
Content-Transfer-Encoding: 8bit
X-Originating-IP: [209.182.101.80]
X-ClientProxiedBy: CAS1.indiv3.local (172.16.1.1) To DAG4EX2.indiv3.local
 (172.16.2.8)
X-Ovh-Tracer-Id: 5256263716711836261
X-VR-SPAMSTATE: OK
X-VR-SPAMSCORE: 0
X-VR-SPAMCAUSE: gggruggvucftvghtrhhoucdtuddrgedugedrfeeggdeifecutefuodetggdotefrodftvfcurfhrohhfihhlvgemucfqggfjpdevjffgvefmvefgnecuuegrihhlohhuthemucehtddtnecunecujfgurhepfffvhffukffogggtgfhisehmkeerreehtdejnecuhfhrohhmpefnrghgvghrihhsucfimhgsjfcuoehinhhfoheslhgrghgvrhhishdruggvqeenucfkpheptddrtddrtddrtddpuddtledrudelvddruddttddrgedvnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmohguvgepshhmthhpqdhouhhtpdhhvghlohepvgigfedrmhgrihhlrdhovhhhrdhnvghtpdhinhgvtheptddrtddrtddrtddpmhgrihhlfhhrohhmpehinhhfoheslhgrghgvrhhishdruggvpdhrtghpthhtoheptghhrhhishhtihhnvgdrshhlohhtthihsehsohhfthifrghrvgdqlhgrsghorhdruggv
X-HE-Virus-Scanned: Yes
X-HE-Spam-Level: +
X-HE-Spam-Score: 1.8
X-HE-Spam-Report: Content analysis details:   (1.8 points)
  pts rule name              description
 ---- ---------------------- --------------------------------------------------
 -0.0 RCVD_IN_DNSWL_NONE     RBL: Sender listed at https://www.dnswl.org/,
                              no trust
                             [54.36.141.2 listed in list.dnswl.org]
  0.3 HTML_IMAGE_ONLY_04     BODY: HTML: images with 0-400 bytes of words
  0.7 MIME_HTML_ONLY         BODY: Message only has text/html MIME parts
  0.1 HTML_MESSAGE           BODY: HTML included in message
  0.0 HTML_EXTRA_CLOSE       BODY: HTML contains far too many close tags
 -0.0 RCVD_IN_MSPIKE_H2      RBL: Average reputation (+2)
                             [54.36.141.2 listed in wl.mailspike.net]
  0.6 HTML_MIME_NO_HTML_TAG  HTML-only message, but there is no HTML
                             tag
  0.0 T_REMOTE_IMAGE         Message contains an external image
Envelope-to: [email protected]

--b1_xON0QmUygNqKaTefGv0U65iRiam7TQEABJP8nJNNin8
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: 8bit

EMAIL TEXT

--b1_xON0QmUygNqKaTefGv0U65iRiam7TQEABJP8nJNNin8
Content-Type: application/pdf; name="_025-3_RRechnungsnummerr_W259.pdf"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
	filename="_025-3_RRechnungsnummerr_W259.pdf"

JVBERi0xLjMKMyAwIG9iago8PC9UeXBlIC9QYWdlCi9QYXJlbnQgMSAwIFIKL1Jlc291cmNlcyAy
IDAgUgovQ29udGVudHMgNCAwIFI+PgplbmRvYmoKNCAwIG9iago8PC9GaWx0ZXIgL0ZsYXRlRGVj
b2RlIC9MZW5ndGggODg3Pj4Kc3RyZWFtCnicjVZNb9s4EL3nV8yxPZjh90dOW6PbdXfbQxIDAfam
xJStrSSjkgwvcuoP7U/poUMrMilbrYMARkg+Dt8bPs6Iw99XlCgD+6v5Eq4/MHCEUljm8OcyzAjn
iHZgjCKSwXIFb4BbQhnhlNO3sPzvBCcNofqA+2dXrzxoxswETHBi3QG28E0Dn4unTeZLgMW2fK6y
up7Ywiwx/LDl3ZfsufD13q9BAUxAqSRKHqDaMW3hofBtuX3apNgglamo9StYHEmg+CclKgSuOOEW
jHZECXiq4Pojg/dbuA3bueGEcdDWhuSFo253Rdft6vXJGZPp1JoT2ov5lK19g9vaZ193za46kn2J
E7ewcCBjMrCcUSIVNB7y6XMYY0QasFQQo+M5RQt/VY+LNA8M7xLzaiyqtP0FP2TlqvW+7ZqsbT3w
+QguiTOpH97PjDGCIe+ybLvMd10KZ0iUYg7RMP2NFHW+/aPsyZCVT7DOEENTBy3REVRwDjPgDKgD
R7kdBceoIjXTfr8nk7G1Ihgi8dAiQ3eWbePXRdv5Bj40vnjcNWtY3M0RZ5Rkw3ZKnFZAicaEUswy
hWY9ZN3GrOOzEAr9rg7XxA1RGmZ4W8yEe7q/sJ7DaL0ahhRPV1DC/ek6U4o4NowDYDwTETHEV/T2
GpC6pX1wRbToLzHrdtXguNurcagJMRcA+SvInCK4FcGyiaDxTERMCGLWEG1SRf9mmzI8q8p3m+3R
C6htHHXyon4LyF/B6xQh0alUpNrGMxExoU0IFopFou3O577x9XMUNQ43IeoCIH8FoVOEElj7XCpq
PBMRE6KkYgQrcyJq7rHerOGx2XXdNiqjxBqL785hHWDD6xs9jkFMP5m+tX59eEvKamLMyHsD4GWc
PC9UeXBlIC9Gb250Ci9CYXNlRm9udCAvSGVsdmV0aWNhCi9TdWJ0eXBlIC9UeXBlMQovRW5jb2Rp
bmcgL1dpbkFuc2lFbmNvZGluZwo+PgplbmRvYmoKNiAwIG9iago8PC9UeXBlIC9YT2JqZWN0Ci9T
dWJ0eXBlIC9JbWFnZQovV2lkdGggNTEyCi9IZWlnaHQgMjY1Ci9Db2xvclNwYWNlIFsvSW5kZXhl
ZCAvRGV2aWNlUkdCIDUgNyAwIFJdCi9CaXRzUGVyQ29tcG9uZW50IDgKL0ZpbHRlciAvRmxhdGVE
ZWNvZGUKL0RlY29kZVBhcm1zIDw8L1ByZWRpY3RvciAxNSAvQ29sb3JzIDEgL0JpdHNQZXJDb21w
b25lbnQgOCAvQ29sdW1ucyA1MTI+PgovTGVuZ3RoIDI1NjM+PgpzdHJlYW0KeNrtndtinDoMRVGF
//+XT5OeNpmJsSRjg2e09nPCgLy0fcEW268fEtnQW0rkZ2tvz61PmN4cgiYAxCeDDgEgNAkR2DD/
3B3BRvvnJmDD/nN3Axvtn5uADf/P3QtstH9uAjY6gNydwEb75yYAAACAOGQmAADSA8AUIPVEYMMA
clsAAGQHgB4gdx8AANkBQAghhBBCCCGEEEIIIYQQQgghhBBCCCGEEHoHqbIvMq9Ey77vRYlEzub/
aP1PFWwgafJ/CRtImvzfEcAGciY/NpA++bEBkh8bSJ/82ADJjw2kT35s4C2l4ebHBt7J+/duYQNZ
kx8bSJ/82ADJjw2kT35sgOTHBtInPzZA8mMD6ZMfGyD5sYH0yY8NkPzYQPrkxwZIfmwgffJjAyQ/
NpA++bEBkh8bSJ/82ADJjw2kT35sgOTHBtInPzZA8mMD6ZMfGyD5sYH0yf9N2EDS5McGSH5sIH3y
YwMkPzaQPvmxAZIfG0if/NiAM/nfvfmxgczJjw2Q/NhA+uTHBkh+bCB98mMDJD82kD75k9sAyZ/a
Bmj9zDZA8qe2AVo/sw2Q/KltgNbPbAMkf2oboPUz2wDJn9oGhNZPbAMkf2obIPkz2wDJn9oGSP7M
NkDyp7YBkj+zDZD8qW2A5M9sAyR/ahsg+TPbAMl/PQNC8mMDJD82QPJjAyQ/NkDyYwMkPzZA8mMD
JD82QPJjAyQ/NkDyYwMkPzZA8mMDnOvCBk56P5HMawNne/6CYtLhohQhQgghhBBCCCGEEEIIIYQQ
QgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEJvLZlTcWTgRX/f4UONFem4A6c6LiGTgtz51B0t9Vj1
Z1DxMRl00WoRo6KeC/ZUP5KeSxS7VFMwyPWnLnMgmAPA01V7r1JOlEi7DIDPsMm4ILeeegIDcwDY
z1/Vql9n5d2lABgIBIJs1W0bXid2CgDPbVeGN7/tAhcD0AydO8iesn2DEZgCQDEiGyYoHvPrAWgA
6Qyyu2KvLg6AnLxjf+nihglcD8Dx3fiCHKjYPNAFZgDwM3/LhPQ3bvgGAA4JcAVZ7qkXPgOAYoZ2
WPsf3/EdABw1iyfIEv0tXRaAWguWM/R0BeIWAA6e0xHkeMV+WRaA6qNMa/+je74HgDoBjiCXmwxg
AgBy5n67qpfrQgBUb8YOsu43GcAEADTgjUPavx6MuwCo3Ywd5NsMYAIApZ9YPZr0qMrHC5VS/L5b
Oj5tIOFL+JrGDLJW1/5/P7T8ee5z06prAZB+ZNWe8h584sLOKTmPsnfFUuJBLtZE/xmChdcBNDQ+
ttCpzKuq42W5CYDa7ZRwkNU1kCgzDGA8AP29VvH+TwUBvQ0Az8K3FeTiC9WX3ei6AGhwitxcP/Qu
rt/XBfjaLwhAsd6RDTSA4QA8PIsG2kBCxGh0Yj0TALFu3AiyBMbLn+4n6wLw+CxPOMQMoHj/vG4U
FwLw494lFmQNWaUONYDRAOjjk6j7waS7x/AtBU8FQPZQC58DYBNZGIDyFHR3I2h43UBDL4OmAvD8
l+cAmPuJ+LkAyDPJ6ry4dCwblOY48VIAjBw2glyiE+Z1AfjR50tnL1p8tKm79eYCIABQWQSQKhK+
mZSrvZobMS8FYGu/+4wBsMvLAlAZ8/mGgbIP7wZfGIDysgBU0l1czaDjc+CFANB9v28YOBKAamO7
+oAJveCdAJSTAIzb8XctAFW79/QBE3qAVxoEavSgyaoAVJPd0wdo5/6xZQA4Nw2U+FmjJQHQerhK
eNNkeTkAzi0EHe2h8Z2CXQeAg95e7SiW+XvTxdCdS8GtHYFlthGMA+DI6x1HxcuEeXBwl52cAeDk
y6DGJor5EIwD4HC0Z/cBE4YAVwJw9nWwY1PwPAbGAXA43zN3BciLA3B6Q4jvbhcvENEY7Vu/IDMW
wq4DwH6RaQfZdzBo6QIRjfm+1Qe8NAAjNoVu/qNh6xaIaFxGYz1gAwDxl2u6BoBB28IDBIxeJRwF
QHOyF0uQ4h9ujzvW4wDAdy6kdvu+ILuPh5UV9wQ2bb60Yy2vAcDMo2F//uwOExgEgAQWQvStAdAz
djDgLLfDYDbtnylYjVstksA4L+ndpNIbOOTlP1+ALzjQCvI4tzxWxacBgbOdcaO0LYJ0OigYs7h
UOc40H04tI2A9E2V5gIQONdpHAASd61gV73oa04H+8aBgePhxV/3agkAQqe6YoeoD03g4NXILQD4
pq+xGkFHx3/Kii+DYl/FNQB2fC+gsWXuFgB840CNTYCqR8AqL4WnvA4Ofmc9FGazKPJhoQT52/iN
/RFtAHq+H+8BwDUObANQDh76i4L6c0/ZEBJcxZFTMzc1ozlyZSl+DdfZQM9iQBOA9kMfnT/aZ33n
OsB6KzwHPm36gr49xclywDQtoDAK7Pm644pdQJLjyr/6e2YpHKpZkC+rYWX5wg0Sjxo73MdXiL0
WA/TZXQgWy9QREYgL1RleHQgL0ltYWdlQiAvSW1hZ2VDIC9JbWFnZUldCi9Gb250IDw8Ci9GMSA1
IDAgUgo+PgovWE9iamVjdCA8PAovSTEgNiAwIFIKPj4KPj4KZW5kb2JqCjggMCBvYmoKPDwKL1By
b2R1Y2VyIChGUERGIDEuNykKL1RpdGxlICj+/wBbAEwAYQBnAGUAcgBpAHMAIABHAG0AYgBIAF0A
IABRAHUAaQB0AHQAdQBuAGcpCi9DcmVhdGlvbkRhdGUgKEQ6MjAyMDAxMjgxNTQ1MDQpCj4+CmVu
ZG9iago5IDAgb2JqCjw8Ci9UeXBlIC9DYXRhbG9nCi9QYWdlcyAxIDAgUgo+PgplbmRvYmoKeHJl
ZgowIDEwCjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMTA0NCAwMDAwMCBuIAowMDAwMDA0MTQ3
IDAwMDAwIG4gCjAwMDAwMDAwMDkgMDAwMDAgbiAKMDAwMDAwMDA4NyAwMDAwMCBuIAowMDAwMDAx
MTMxIDAwMDAwIG4gCjAwMDAwMDEyMjcgMDAwMDAgbiAKMDAwMDAwNDA1MSAwMDAwMCBuIAowMDAw
MDA0MjYxIDAwMDAwIG4gCjAwMDAwMDQzOTQgMDAwMDAgbiAKdHJhaWxlcgo8PAovU2l6ZSAxMAov
Um9vdCA5IDAgUgovSW5mbyA4IDAgUgo+PgpzdGFydHhyZWYKNDQ0MwolJUVPRgo=

--b1_xON0QmUygNqKaTefGv0U65iRiam7TQEABJP8nJNNin8--


@Synchro
Copy link
Member

Synchro commented Jan 28, 2020

I don't see that wrapping anything? It's doing regular folding that looks correct, but none of it has any extra encoding or splitting of filenames?

@chrisSCM
Copy link

chrisSCM commented Jan 28, 2020

I'm not into the topic enough, I just noticed that for some emails the attachment file name is fine, and for some it's cut off. But just now I think I've found the problem: There's a '/' character in the filename! I guess that's not something the lib likes too much!?

Any other special characters to be avoided?

@Synchro
Copy link
Member

Synchro commented Jan 28, 2020

It's not so much the lib as the file system - given a path containing reserved characters, particularly those that are used as path delimiters (/ and \), they need to be escaped if they are actually part of the name rather than the path. PHPMailer (like all file system functions) on a file system with a / path delimiter will assume that a/b.txt is a file called b.txt in a folder called a, not a file called a/b.txt. To distinguish you would need to escape it yourself, probably with escapeshellarg().

I don't think there's anything that PHPMailer can or should do – escaping the filename is up to you, and I think any escaping should be carried through to the filenames that appear in the message.

@chrisSCM
Copy link

chrisSCM commented Jan 28, 2020

Thanks - I didn't see any connection with the filesystem, because it's only the attachment name, the actual content does come from the filesystem, yes, but under a different name.

So that has to mean, somewhere between those two operations - between setting the attachment name and the actual email sending - there are filesystem operations included, regarding the attachment name!?

In that case your explanation makes perfect sense.

@Synchro
Copy link
Member

Synchro commented Jan 28, 2020

Well, of course - it has to load the file and encode it into the email, and it uses the filename to do so. For example, when you call addAttachment it returns false if it can't read the file, e.g. for permissions, or if the file doesn't exist.

Attachment encoding is something that could use refreshing in PHPMailer – it's currently very memory intensive. Ideally it would be rewritten using a generator that never needed to load the whole file at once.

@chrisSCM
Copy link

chrisSCM commented Jan 28, 2020

In my case, it doesn't load any file.
The first argument in this call:

$mail->addStringAttachment( $string_PDF, $attachmentFileName );

...which is $string_PDF, contains the PDF content! That's why I'm so puzzled, sorry.

The $attachmentFileName contains the filename of the attached file, it doesn't exist in the filesystem - unless during processing it's written to the filesystem for whatever reason.

You mean for encoding it has to write the attachment file to hard disk first? In the documentation it says "Add a string or binary attachment (non-filesystem)."

(escapeshellarg didn't help, btw, but I guess I'll just remove such chars for now - and yes, that fixes it (as expected))

@caugner
Copy link
Contributor

caugner commented Jan 28, 2020

What was the value of $longFilename/$attachmentFileName when creating the mail above?

@Synchro
Copy link
Member

Synchro commented Jan 28, 2020

Oh - your code example used addAttachment(). I didn't realise you were using addStringAttachment(); indeed that should not touch the file system, and all encoding is done in memory.

I'm not quite sure what should happen in this case. I guess whether it is treated as a filename or a relative path is dependent on the receiver's OS, if PHPMailer passes it through untouched.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants