From dba2fbdd5e00f85c9edb0f2e9098804fcabb6b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donatas=20Olsevi=C4=8Dius?= Date: Fri, 5 May 2023 11:04:01 +0200 Subject: [PATCH 1/2] Add an optional validation of attachment checksum --- src/TNEFDecoder/TNEFAttachment.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/TNEFDecoder/TNEFAttachment.php b/src/TNEFDecoder/TNEFAttachment.php index 214761c..f868912 100644 --- a/src/TNEFDecoder/TNEFAttachment.php +++ b/src/TNEFDecoder/TNEFAttachment.php @@ -24,6 +24,7 @@ class TNEFAttachment { var $debug; + var $validateChecksum; var $mailinfo; var $files; var $files_nested; @@ -31,9 +32,10 @@ class TNEFAttachment var $current_receiver; var $body; - function __construct($debug = false) + function __construct($debug = false, $validateChecksum = false) { $this->debug = $debug; + $this->validateChecksum = $validateChecksum; $this->files = array(); $this->attachments = array(); $this->mailinfo = new TNEFMailinfo(); @@ -158,7 +160,13 @@ function tnef_decode_attribute(&$buffer) $attribute = tnef_geti32($buffer); // attribute if $length = tnef_geti32($buffer); // length $value = tnef_getx($length, $buffer); // data - tnef_geti16($buffer); // checksum + $checksumAtt = tnef_geti16($buffer); // checksum + if ($value !== null && $this->validateChecksum) { + $checksum = array_sum(unpack('C*', $value)) & 0xFFFF; + if ($checksum !== $checksumAtt) { + throw new \Exception('Checksums do not match'); + } + } switch($attribute) { @@ -339,7 +347,7 @@ function extract_mapi_attrs(&$buffer) if ($this->debug) tnef_log("MAPI Found nested attachment. Processing new one."); tnef_getx(16, $value); // skip the next 16 bytes (unknown data) - $att = new TNEFAttachment($this->debug); + $att = new TNEFAttachment($this->debug, $this->validateChecksum); $att->decodeTnef($value); $this->attachments[] = $att; if ($this->debug) From 67bba6777396e259470454d895a8722f3ee28327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donatas=20Olsevi=C4=8Dius?= Date: Mon, 8 May 2023 07:39:56 +0200 Subject: [PATCH 2/2] add a test for invalid checksums --- test/TNEFAttachmentTest.php | 109 +++++++++++---------------- test/testfiles/invalid-checksum.tnef | Bin 0 -> 2445 bytes 2 files changed, 44 insertions(+), 65 deletions(-) create mode 100644 test/testfiles/invalid-checksum.tnef diff --git a/test/TNEFAttachmentTest.php b/test/TNEFAttachmentTest.php index 2d9d08c..491de09 100644 --- a/test/TNEFAttachmentTest.php +++ b/test/TNEFAttachmentTest.php @@ -7,45 +7,6 @@ class TNEFAttachmentTest extends TestCase { -// /** -// * Test decoding winmail.dat from email attachment encoded in base64 -// */ -// public function testDecode() { -// $buffer = base64_decode(file_get_contents(dirname(__FILE__) . "/testfiles/base64_attachment.txt")); -// file_put_contents(dirname(__FILE__) . "/testfiles/winmail.dat", $buffer); -// -// $attachment = new TNEFAttachment(); -// $attachment->decodeTnef($buffer); -// $files = $attachment->getFiles(); -// -// $this->assertEquals(4, count($files)); -// $this->assertEquals("image001.jpg", $files[0]->getName()); -// $this->assertEquals("image003.jpg", $files[1]->getName()); -// $this->assertEquals("image004.jpg", $files[2]->getName()); -// $this->assertEquals("image005.jpg", $files[3]->getName()); -// -// foreach ($files as $file) { -// $this->assertEquals("image/jpeg", $file->getType()); -// } -// -// } -// -// /** -// * Test decoding winmail.dat file from filesystem -// */ -// public function testDecode2() { -// $buffer = file_get_contents(dirname(__FILE__) . "/testfiles/winmail2.dat"); -// -// $attachment = new TNEFAttachment($buffer); -// $attachment->decodeTnef($buffer); -// $files = $attachment->getFiles(); -// -// $this->assertEquals(3, count($files)); -// $this->assertEquals("EmbeddedRTF.rtf", $files[0]->getName()); -// $this->assertEquals("zappa_av1.jpg", $files[1]->getName()); -// $this->assertEquals("bookmark.htm", $files[2]->getName()); -// } - /** * Test decoding winmail.dat file from filesystem */ @@ -53,7 +14,7 @@ public function testDecode3() { $buffer = file_get_contents(dirname(__FILE__) . "/testfiles/two-files.tnef"); - $attachment = new TNEFAttachment($buffer); + $attachment = new TNEFAttachment(); $attachment->decodeTnef($buffer); $files = $attachment->getFiles(); @@ -62,35 +23,53 @@ public function testDecode3() $this->assertEquals("README", $files[1]->getName()); } - public function testDecodeAuto() { - $files = scandir ( dirname(__FILE__) . "/testfiles/"); - foreach ($files as $file) { - $ext = explode('.', $file); - if (count($ext) == 2 && $ext[1] == "tnef") { - $buffer = file_get_contents(dirname(__FILE__) . "/testfiles/" . $file); - $list = file(dirname(__FILE__) . "/testfiles/" . $ext[0] . '.list', FILE_IGNORE_NEW_LINES + FILE_SKIP_EMPTY_LINES); - $attachment = new TNEFAttachment(); - $attachment->decodeTnef($buffer); - $this->assertEquals(count($list), count($attachment->files)); - $decodedFiles = array_map(function(TNEFFileBase$file) {return $file->getName();}, $attachment->files); - - $withoutRtf = array_filter($list, array($this, "endsWithRtf")); - $withoutRtfdecoded = array_filter($decodedFiles, array($this, "endsWithRtf")); - - $rtfs = array_diff($withoutRtf, $list); - $rtfsDecoded = array_diff($withoutRtfdecoded, $decodedFiles); - - $this->assertEquals(count($rtfs), count($rtfsDecoded)); - - sort($withoutRtfdecoded); - sort($withoutRtf); - $this->assertEquals($withoutRtf, $withoutRtfdecoded); - } + /** + * @dataProvider tnefFileProvider + */ + public function testDecodeAuto($tnefFile, $listFile) { + $buffer = file_get_contents($tnefFile); + $attachment = new TNEFAttachment(false, true); + + if ($listFile === null) { + $this->expectExceptionMessage('Checksums do not match'); + } + $attachment->decodeTnef($buffer); + + $list = file($listFile, FILE_IGNORE_NEW_LINES + FILE_SKIP_EMPTY_LINES); + $this->assertEquals(count($list), count($attachment->files)); + $decodedFiles = array_map(function(TNEFFileBase$file) {return $file->getName();}, $attachment->files); + + $withoutRtf = array_filter($list, array($this, "endsWithRtf")); + $withoutRtfdecoded = array_filter($decodedFiles, array($this, "endsWithRtf")); + + $rtfs = array_diff($withoutRtf, $list); + $rtfsDecoded = array_diff($withoutRtfdecoded, $decodedFiles); + + $this->assertEquals(count($rtfs), count($rtfsDecoded)); + + sort($withoutRtfdecoded); + sort($withoutRtf); + $this->assertEquals($withoutRtf, $withoutRtfdecoded); + } + + public function tnefFileProvider() { + $tnefFiles = glob(dirname(__FILE__) . "/testfiles/*.tnef"); + $result = []; + foreach ($tnefFiles as $tnefFile) { + $pathinfo = pathinfo($tnefFile); + $listFile = $pathinfo['dirname'] . '/' . $pathinfo['filename'] . '.list'; + + $result[] = [ + $tnefFile, + file_exists($listFile) ? $listFile : null, + ]; } + + return $result; } private function endsWithRtf($value) { - try{ + try { return explode('.', $value)[1] != 'rtf'; } catch (Throwable $e) { return false; diff --git a/test/testfiles/invalid-checksum.tnef b/test/testfiles/invalid-checksum.tnef new file mode 100644 index 0000000000000000000000000000000000000000..f1d4741d9458d2b53a6409963c6c4ac51ae02290 GIT binary patch literal 2445 zcmcIlU2Gdg5T3n@f6p~e3Tf>&?v?(8l-RlRzng~K`8TQK#C8%|{)An}aZ<-|;@D|~ z_;HI+c_TvNfhU55C_+eW5f30C>VlA}zO)GXf)Er1i6VhiwLE}C5xeWeYRXZhBJOUz zGxL2jH#eT$U3%k@2N?rWFKeL%$c4zQSyqD4G?<9nDQyg-{EQj<5&5(56}oFhn3Xi<7P6Y$Mpf!mWOiv=tJR z+=gxW%*;$~R+_CAbNP%tTPk9k75?Za!Tt%Czz=Z2Rt&bELsk%_yKz~tNH`RZDf|o1 zOnDqmoy22d@7nAk?!cgVs<&eqoof&FE`4(9&9|TZrS;4AuRwj|>7ySKj?P4UU&lYM z;gQ5u7|9=SxZ54@8<(CnngplwCp7N#pldwy7Q4zoOLj_MU&vJi z{yWWP=0!-bSK9VGr>|)*{LJ_2N=Ctb|7m@isr{}$Ud=*+V-1(lc|*D~Gt2Nf?v(zl zzLwEuCi8lp&*VFgYovUpvyd;CLpRM~%Sxu#vTQL~E?PeKWI?jPOBWqE%86Z-m5%e?xsWo|0JuV{qAj>NnXjubzhSTqRcF;29sKDp!a!qzk9(Q+Ow z?+owsP2Or_x%bXUtg%Skyj-EPWORA=YLa+#tzq)qI&UVZhLzMkW?iSnH4%z;d$v);MKlHPkm zn)OsQHA~vATIs6#z2LJsd|sQ&mG-;%hei;;>1wX6tNR_NXyZM8=cj&KJ(5}K(y#7* zehc%3*}eHZILLp=0bA4ZY9d0N{?hgY_N9h`GgQP)O&k>Q6vkH{AiahZ@iB~({vhkt zXLsW~I7Obi`5s0QM{76-EsD6Y>91DATbsB>5#PFoTS2RcdoWJEgA8(6L8r*`{*8xQ zS)uN}J9r$J6nQ7scpMmNoI)IUxseYCn-p zeTYP5(66`2{gj_KP0EZJtO0N!{hOA zROW}|Ku>VEFBJ|BCI-X9W1~cij}FEYqa(v4q!p?(z6#5u@v%h1wn%McmoH|@1^dEu zac-fsVdu1hL^)*Z9&Nzg_z)-;ux;J3EF&ckkU6!cr?sW-Kj&fbxl5$;A)aSp|Wjn142Zv}Z4i1wt;dTEQ)fp6}GqFja z5#H-Q`DXy1X%c7!L*_GRoNs-Uv))ry=r9s_U1!i&&AzmPoAxc)|+H#^Q<)9Fjwk0j%LCV6#P2 Y;V2pM&`_AJDF(ejYj&@-`TCW=09&1|2mk;8 literal 0 HcmV?d00001