diff --git a/classes/rawemailmessage.class.inc.php b/classes/rawemailmessage.class.inc.php index 9fa5a1f..5042808 100644 --- a/classes/rawemailmessage.class.inc.php +++ b/classes/rawemailmessage.class.inc.php @@ -172,6 +172,21 @@ public function GetMessageId() */ public function GetAttachments(&$aAttachments = null, $aPart = null, &$index = 1) { + // This Regex complies with RFC 2045 regarding the Grammar of Content Type Headers Filenames: + // (1) Allow all chars for the filename, if the filename is quoted with double quotes + // (2) Allow all chars for the filename, if the filename is quoted with single quotes + // (3) If the filename is not quoted, allow only ASCII chars and exclude the following + // chars from the set, referenced as "tspecials" in the RFC: + // + // + // ()<>@,;:\"/[]?= + // To keep the regex as simple as possible, the _allowed_ chars are whitelisted + // with their corresponding hexval. + $sFileNameRegex = <<GetHeader('content-disposition', $aPart['headers']); - if (($sContentDisposition != '') && (preg_match('/filename="([^"]+)"/', $sContentDisposition, $aMatches))) + if (($sContentDisposition != '') && (preg_match('/filename='.$sFileNameRegex.'/', $sContentDisposition, $aMatches))) { $sFileName = $aMatches[1]; } - else - { - if (($sContentDisposition != '') && (preg_match('/filename=([^"]+)/', $sContentDisposition, $aMatches))) // same but without quotes - { - $sFileName = $aMatches[1]; - } - } $bInline = true; if (stripos($sContentDisposition, 'attachment;') !== false) @@ -223,10 +231,17 @@ public function GetAttachments(&$aAttachments = null, $aPart = null, &$index = 1 { $sType = $aMatches[1]; } - if (empty($sFileName) && preg_match('/name="([^"]+)"/', $sContentType, $aMatches)) + if (empty($sFileName) && preg_match('/name='.$sFileNameRegex.'/', $sContentType, $aMatches)) { $sFileName = $aMatches[1]; } + // Note: Since the RFC2045-compliant regex above has different levels of matches, + // because of the use of capture groups, indexing into the results with + // $aMatches[1] sometimes returns the filename with quotes, but sometimes not. + // For that reason (and for the sake of codesimplicity) we strip all quotes + // from filenames here and will be fine. + $sFileName = str_replace("'", '', $sFileName); + $sFileName = str_replace('"', '', $sFileName); if (empty($sFileName)) { // generate a name based on the type of the file... diff --git a/test/emailsSample/email_with_and_without_quotes_around_attachment_name.eml b/test/emailsSample/email_with_and_without_quotes_around_attachment_name.eml new file mode 100644 index 0000000..6dd4f06 --- /dev/null +++ b/test/emailsSample/email_with_and_without_quotes_around_attachment_name.eml @@ -0,0 +1,97 @@ +Return-Path: +Received: from www192.your-server.de + by www192.your-server.de with LMTP + id cANPJDAmYmNaVQEAUXDXKw + (envelope-from ); Wed, 02 Nov 2022 09:11:28 +0100 +Envelope-to: martin.raenker@itomig.de +Delivery-date: Wed, 02 Nov 2022 09:11:28 +0100 +Authentication-Results: www192.your-server.de; + iprev=pass (localhost) smtp.remote-ip=127.0.0.1; + spf=pass smtp.mailfrom=itomig.de; + dkim=pass header.d=itomig.de header.s=default2012 header.a=rsa-sha256; + dmarc=skipped +Received: from localhost ([127.0.0.1] helo=www192.your-server.de) + by www192.your-server.de with esmtps (TLS1.3) tls TLS_AES_256_GCM_SHA384 + (Exim 4.94.2) + (envelope-from ) + id 1oq8qO-000NG0-7r + for martin.raenker@itomig.de; Wed, 02 Nov 2022 09:11:28 +0100 +DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=itomig.de; + s=default2012; h=Subject:From:To:MIME-Version:Date:Message-ID:Content-Type: + Sender:Reply-To:Cc:Content-Transfer-Encoding:Content-ID:Content-Description: + Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: + In-Reply-To:References; bh=qfsa/c5xPqZ3d+6wNfzUExTW0ZNkPkALI3o0hp2R85A=; b=r5 + zbg+7XSo/HBfaFAqqdBD0gDiOIa8ujhqmikWoXps5jf9oMJVJ6af5ynLdfZPbxkpWss5t6os7LhO/ + w/ZdvjR8sOpvIX5nkR3DJxnG76Z28p0aFtpBz1OSIXpWe7LmSj8mBwsXBuh6jTLLH2xRGY3gBEIU9 + fkOlRbJQ6lU3qhS9Xa9cgSGECPfhIvZzdM/Rb0vdvvERCmrpvr9TylqFb+3RA05pQJBOvG3i/ih7O + 5a9RytEsd5rpN1IabPoMV6UJFhKaxPY4hMG4qdgzI2GQZAcoK5kNm/IrUxeHQ8KLy5HigJ1I11e9e + kzqzy6hT0ZKrELFcqh6Y8L6EzgtC+aOg==; +Received: from [95.90.147.54] (helo=[192.168.0.103]) + by www192.your-server.de with esmtpsa (TLS1.3) tls TLS_AES_256_GCM_SHA384 + (Exim 4.94.2) + (envelope-from ) + id 1oq8qO-000NFs-3t + for martin.raenker@itomig.de; Wed, 02 Nov 2022 09:11:28 +0100 +Content-Type: multipart/mixed; boundary="------------8PrwbWLJ6Ep94lElB18RfVAR" +Message-ID: <2525d616-c82b-fa1e-05c6-065e9f91f46d@itomig.de> +Date: Wed, 2 Nov 2022 09:11:25 +0100 +MIME-Version: 1.0 +User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 + Thunderbird/102.2.2 +Content-Language: en-US +To: martin.raenker@itomig.de +From: Martin Raenker +Subject: test for attachments with unquoted name +X-Authenticated-Sender: martin.raenker@itomig.de +X-Virus-Scanned: Clear (ClamAV 0.103.7/26707/Tue Nov 1 21:23:26 2022) +X-local-sign: yes +X-DKIM-Status: pass [(itomig.de) - 127.0.0.1] +Delivered-To: itomig-martin.raenker@itomig.de + +This is a multi-part message in MIME format. +--------------8PrwbWLJ6Ep94lElB18RfVAR +Content-Type: text/plain; charset=UTF-8; format=flowed +Content-Transfer-Encoding: 7bit + +testbody +--------------8PrwbWLJ6Ep94lElB18RfVAR +Content-Type: application/octet-stream; charset=UTF-8; name="example_attachment_mail.csv" +Content-Disposition: attachment; +Content-Transfer-Encoding: base64 + +InRlc3QiLCJ0b3N0Igo= + +--------------8PrwbWLJ6Ep94lElB18RfVAR +Content-Type: application/octet-stream; charset=UTF-8; name='example_attachment_mail.csv' +Content-Disposition: attachment; +Content-Transfer-Encoding: base64 + +InRlc3QiLCJ0b3N0Igo= + +--------------8PrwbWLJ6Ep94lElB18RfVAR +Content-Type: application/octet-stream; charset=UTF-8; name=example_attachment_mail.csv +Content-Disposition: attachment; +Content-Transfer-Encoding: base64 + +InRlc3QiLCJ0b3N0Igo= + +--------------8PrwbWLJ6Ep94lElB18RfVAR +Content-Type: application/octet-stream; charset=UTF-8; +Content-Disposition: attachment; filename="example_attachment_mail.csv" +Content-Transfer-Encoding: base64 + +InRlc3QiLCJ0b3N0Igo= + +--------------8PrwbWLJ6Ep94lElB18RfVAR +Content-Type: application/octet-stream; charset=UTF-8; +Content-Disposition: attachment; filename='example_attachment_mail.csv' +Content-Transfer-Encoding: base64 + +InRlc3QiLCJ0b3N0Igo= + +--------------8PrwbWLJ6Ep94lElB18RfVAR +Content-Type: application/octet-stream; charset=UTF-8; +Content-Disposition: attachment; filename=example_attachment_mail.csv +Content-Transfer-Encoding: base64 + +InRlc3QiLCJ0b3N0Igo=