diff --git a/phpunit.xml b/phpunit.xml index 1101cbf..3621538 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,25 +1,17 @@ - - - - - test - - - - - - src/ - - - - - - - \ No newline at end of file + + + + + test + + + + + + + + src/ + + + diff --git a/src/TNEFDecoder/TNEFAttachment.php b/src/TNEFDecoder/TNEFAttachment.php index f868912..55f51b1 100644 --- a/src/TNEFDecoder/TNEFAttachment.php +++ b/src/TNEFDecoder/TNEFAttachment.php @@ -111,15 +111,17 @@ function getBodyElements() return $this->body; } - function decodeTnef(&$buffer) + function decodeTnef($data) { + $buffer = new TNEFBuffer($data); + $tnef_signature = tnef_geti32($buffer); if ($tnef_signature == TNEF_SIGNATURE) { $tnef_key = tnef_geti16($buffer); if ($this->debug) tnef_log(sprintf("Signature: 0x%08x\nKey: 0x%04x\n", $tnef_signature, $tnef_key)); - while (strlen($buffer) > 0) { + while ($buffer->getRemainingBytes() > 0) { $lvl_type = tnef_geti8($buffer); switch ($lvl_type) { @@ -133,7 +135,7 @@ function decodeTnef(&$buffer) default: if ($this->debug) { - $len = strlen($buffer); + $len = $buffer->getRemainingBytes(); if ($len > 0) tnef_log("Invalid file format! Unknown Level $lvl_type. Rest=$len"); } @@ -155,7 +157,7 @@ function decodeTnef(&$buffer) $this->files[$i]->setMessageCodePage($code_page); } - function tnef_decode_attribute(&$buffer) + function tnef_decode_attribute(TNEFBuffer $buffer) { $attribute = tnef_geti32($buffer); // attribute if $length = tnef_geti32($buffer); // length @@ -180,13 +182,13 @@ function tnef_decode_attribute(&$buffer) case TNEF_AMAPIATTRS: if ($this->debug) tnef_log("mapi attrs"); - $this->extract_mapi_attrs($value); + $this->extract_mapi_attrs(new TNEFBuffer($value)); break; case TNEF_AMAPIPROPS: if ($this->debug) tnef_log("mapi props"); - $this->extract_mapi_attrs($value); + $this->extract_mapi_attrs(new TNEFBuffer($value)); break; case TNEF_AMCLASS: @@ -208,14 +210,14 @@ function tnef_decode_attribute(&$buffer) } } - function extract_mapi_attrs(&$buffer) + function extract_mapi_attrs(TNEFBuffer $buffer) { $number = tnef_geti32($buffer); // number of attributes $props = 0; $ended = 0; - while ((strlen($buffer) > 0) && ($props < $number) && (!$ended)) + while (($buffer->getRemainingBytes() > 0) && ($props < $number) && (!$ended)) { $props++; $value = ''; @@ -225,7 +227,7 @@ function extract_mapi_attrs(&$buffer) $num_multivalues = 1; $attr_type = tnef_geti16($buffer); $attr_name = tnef_geti16($buffer); - + if (($attr_type & TNEF_MAPI_MV_FLAG) != 0) { if ($this->debug) @@ -346,7 +348,7 @@ function extract_mapi_attrs(&$buffer) case TNEF_MAPI_ATTACH_DATA: if ($this->debug) tnef_log("MAPI Found nested attachment. Processing new one."); - tnef_getx(16, $value); // skip the next 16 bytes (unknown data) + tnef_getx(16, $buffer); // skip the next 16 bytes (unknown data) $att = new TNEFAttachment($this->debug, $this->validateChecksum); $att->decodeTnef($value); $this->attachments[] = $att; @@ -378,14 +380,14 @@ function extract_mapi_attrs(&$buffer) } if (($this->debug) && ($ended)) { - $len = strlen($buffer); + $len = $buffer->getRemainingBytes(); for ($cnt = 0; $cnt < $len; $cnt++) { - $ord = ord($buffer[$cnt]); + $ord = tnef_geti8($buffer); if ($ord == 0) $char = ""; else - $char = $buffer[$cnt]; + $char = chr($ord); tnef_log(sprintf("Char Nr. %6d = 0x%02x = '%s'", $cnt, $ord, $char)); } } diff --git a/src/TNEFDecoder/TNEFBuffer.php b/src/TNEFDecoder/TNEFBuffer.php new file mode 100644 index 0000000..5b2eb41 --- /dev/null +++ b/src/TNEFDecoder/TNEFBuffer.php @@ -0,0 +1,46 @@ + + * Copyright (c) 2003 Bernd Wiegmann + * Copyright (c) 2002 Graham Norburys + * + * Licensed under the GNU GPL. For full terms see the file COPYING. + * + * @package plugins + * @subpackage tnef_decoder + * + */ + +class TNEFBuffer +{ + private string $data; + private int $offset; + + function __construct(string $data) + { + $this->data = $data; + $this->offset = 0; + } + + function getBytes(int $numBytes): ?string + { + if ($this->getRemainingBytes() < $numBytes) { + $this->offset = strlen($this->data); + return null; + } + + $this->offset += $numBytes; + return substr($this->data, $this->offset - $numBytes, $numBytes); + } + + function getRemainingBytes(): int + { + return strlen($this->data) - $this->offset; + } +} + + + diff --git a/src/TNEFDecoder/TNEFDate.php b/src/TNEFDecoder/TNEFDate.php index d2cf51d..af52020 100644 --- a/src/TNEFDecoder/TNEFDate.php +++ b/src/TNEFDecoder/TNEFDate.php @@ -24,7 +24,7 @@ class TNEFDate var $minute; var $second; - function setTnefBuffer($buffer) + function setTnefBuffer(TNEFBuffer $buffer) { $this->year = tnef_geti16($buffer); $this->month = tnef_geti16($buffer); diff --git a/src/TNEFDecoder/TNEFFile.php b/src/TNEFDecoder/TNEFFile.php index 2815159..09ab3b1 100644 --- a/src/TNEFDecoder/TNEFFile.php +++ b/src/TNEFDecoder/TNEFFile.php @@ -37,7 +37,6 @@ function receiveTnefAttribute($attribute, $value, $length) // filename // case TNEF_AFILENAME: - // strip path // if (($pos = strrpos($value, '/')) !== FALSE) @@ -45,12 +44,13 @@ function receiveTnefAttribute($attribute, $value, $length) else $this->name = $value; + // Strip trailing null bytes if present + $this->name = trim($this->name); break; - // code page // case TNEF_AOEMCODEPAGE: - $this->code_page = tnef_geti16($value); + $this->code_page = tnef_geti16(new TNEFBuffer($value)); break; // the attachment itself @@ -67,11 +67,11 @@ function receiveTnefAttribute($attribute, $value, $length) case TNEF_AATTACHCREATEDATE: $this->created = new TNEFDate(); - $this->created->setTnefBuffer($value); + $this->created->setTnefBuffer(new TNEFBuffer($value)); case TNEF_AATTACHMODDATE: $this->modified = new TNEFDate(); - $this->modified->setTnefBuffer($value); + $this->modified->setTnefBuffer(new TNEFBuffer($value)); break; } } @@ -84,7 +84,6 @@ function receiveMapiAttribute($attr_type, $attr_name, $value, $length, $is_unico // used in preference to AFILENAME value // case TNEF_MAPI_ATTACH_LONG_FILENAME: - // strip path // if (($pos = strrpos($value, '/')) !== FALSE) @@ -93,7 +92,6 @@ function receiveMapiAttribute($attr_type, $attr_name, $value, $length, $is_unico $this->name = $value; if ($is_unicode) $this->name_is_unicode = TRUE; - break; // Is this ever set, and what is format? @@ -101,9 +99,9 @@ function receiveMapiAttribute($attr_type, $attr_name, $value, $length, $is_unico case TNEF_MAPI_ATTACH_MIME_TAG: $type0 = $type1 = ''; $mime_type = explode('/', $value, 2); - if (!empty($mime_type[0])) + if (!empty($mime_type[0])) $type0 = $mime_type[0]; - if (!empty($mime_type[1])) + if (!empty($mime_type[1])) $type1 = $mime_type[1]; $this->type = "$type0/$type1"; if ($is_unicode) { diff --git a/src/TNEFDecoder/TNEFFileBase.php b/src/TNEFDecoder/TNEFFileBase.php index 0d2d5b3..9379dff 100644 --- a/src/TNEFDecoder/TNEFFileBase.php +++ b/src/TNEFDecoder/TNEFFileBase.php @@ -25,7 +25,7 @@ class TNEFFileBase var $created; var $modified; var $debug; - + function __construct($debug) { $this->name = 'Untitled'; diff --git a/src/TNEFDecoder/TNEFFileRTF.php b/src/TNEFDecoder/TNEFFileRTF.php index 68863bc..3394751 100644 --- a/src/TNEFDecoder/TNEFFileRTF.php +++ b/src/TNEFDecoder/TNEFFileRTF.php @@ -20,14 +20,14 @@ class TNEFFileRTF extends TNEFFileBase const MAX_DICT_SIZE = 4096; const INIT_DICT_SIZE = 207; - function __construct($debug, $buffer) + function __construct($debug, $data) { parent::__construct($debug); $this->type = "application/rtf"; $this->name = "EmbeddedRTF.rtf"; $this->debug = $debug; - $this->decode_crtf($buffer); + $this->decode_crtf(new TNEFBuffer($data)); } function getSize() @@ -35,7 +35,7 @@ function getSize() return $this->size; } - function decode_crtf(&$buffer) + function decode_crtf(TNEFBuffer $buffer) { $size_compressed = tnef_geti32($buffer); $this->size = tnef_geti32($buffer); @@ -61,8 +61,10 @@ function decode_crtf(&$buffer) } } - function uncompress(&$data) + function uncompress(TNEFBuffer $buffer) { + $data = tnef_getx($buffer->getRemainingBytes(), $buffer); + $preload = "{\\rtf1\\ansi\\mac\\deff0\\deftab720{\\fonttbl;}{\\f0\\fnil \\froman \\fswiss \\fmodern \\fscript \\fdecor MS Sans SerifSymbolArialTimes New RomanCourier{\\colortbl\\red0\\green0\\blue0\n\r\\par \\pard\\plain\\f0\\fs20\\b\\i\\u\\tab\\tx"; $length_preload = strlen($preload); $init_dict = []; @@ -71,7 +73,7 @@ function uncompress(&$data) } $init_dict = array_merge($init_dict, array_fill(count($init_dict), self::MAX_DICT_SIZE - $length_preload, ' ')); $write_offset = self::INIT_DICT_SIZE; - $output_buffer = ''; + $output_buffer = []; $end = false; $in = 0; $l = strlen($data); @@ -93,7 +95,7 @@ function uncompress(&$data) for ($step = 0; $step < $actual_length; $step++) { $read_offset = ($offset + $step) % self::MAX_DICT_SIZE; $char = $init_dict[$read_offset]; - $output_buffer .= $char; + $output_buffer[] = $char; $init_dict[$write_offset] = $char; $write_offset = ($write_offset + 1) % self::MAX_DICT_SIZE; } @@ -102,13 +104,13 @@ function uncompress(&$data) break; } $val = $data[$in++]; - $output_buffer .= $val; + $output_buffer[] = $val; $init_dict[$write_offset] = $val; $write_offset = ($write_offset + 1) % self::MAX_DICT_SIZE; } } } - $this->content = $output_buffer; + $this->content = new TNEFBuffer(implode('', $output_buffer)); } } diff --git a/src/TNEFDecoder/TNEFMailinfo.php b/src/TNEFDecoder/TNEFMailinfo.php index 35985df..1b83523 100644 --- a/src/TNEFDecoder/TNEFMailinfo.php +++ b/src/TNEFDecoder/TNEFMailinfo.php @@ -60,6 +60,8 @@ function getDateSent() function receiveTnefAttribute($attribute, $value, $length) { + $value = new TNEFBuffer($value); + switch($attribute) { case TNEF_AOEMCODEPAGE: @@ -67,7 +69,7 @@ function receiveTnefAttribute($attribute, $value, $length) break; case TNEF_ASUBJECT: - $this->subject = substr($value, 0, $length - 1); + $this->subject = tnef_getx($length - 1, $value); break; case TNEF_ADATERECEIVED: diff --git a/src/TNEFDecoder/TNEFvCard.php b/src/TNEFDecoder/TNEFvCard.php index 9a5188e..e202610 100644 --- a/src/TNEFDecoder/TNEFvCard.php +++ b/src/TNEFDecoder/TNEFvCard.php @@ -66,7 +66,7 @@ function getCodePage() { if (empty($this->code_page)) return $this->message_code_page; - else + else return $this->code_page; } @@ -158,7 +158,7 @@ function receiveTnefAttribute($attribute, $value, $length) // code page // case TNEF_AOEMCODEPAGE: - $this->code_page = tnef_geti16($value); + $this->code_page = tnef_geti16(new TNEFBuffer($value)); break; } @@ -238,7 +238,7 @@ function evaluateTelefoneAttribute($attr_type, $attr_name, $value, $length) tnef_log("Setting telefone '$telefone_key' to value '$value'"); } } - + return $rc; } @@ -296,7 +296,7 @@ function evaluateHomepageAttribute($attr_type, $attr_name, $value, $length) tnef_log("Setting homepage '$homepage_key' to value '$value'"); } } - + return $rc; } diff --git a/src/TNEFDecoder/functions.php b/src/TNEFDecoder/functions.php index 5dbc7df..b03a937 100644 --- a/src/TNEFDecoder/functions.php +++ b/src/TNEFDecoder/functions.php @@ -66,19 +66,9 @@ function extension_to_mime($extension) * TNEF decoding helper function * */ -function tnef_getx($size, &$buf) +function tnef_getx($size, TNEFBuffer $buf) { - $value = NULL; - $len = strlen($buf); - if ($len >= $size) - { - $value = substr($buf, 0, $size); - $buf = substr_replace($buf, '', 0, $size); - } - else - substr_replace($buf, '', 0, $len); - - return $value; + return $buf->getBytes($size); } @@ -87,18 +77,14 @@ function tnef_getx($size, &$buf) * TNEF decoding helper function * */ -function tnef_geti8(&$buf) +function tnef_geti8(TNEFBuffer $buf): int { - $value = NULL; - $len = strlen($buf); - if ($len >= 1) { - $value = ord($buf[0]); - $buf = substr_replace($buf, '', 0, 1); + $bytes = $buf->getBytes(1, $buf); + if ($bytes === null) { + return null; } - else - substr_replace($buf, '', 0, $len); - return $value; + return ord($bytes[0]); } @@ -107,18 +93,15 @@ function tnef_geti8(&$buf) * TNEF decoding helper function * */ -function tnef_geti16(&$buf) +function tnef_geti16(TNEFBuffer $buf): int { - $value = NULL; - $len = strlen($buf); - if ($len >= 2) { - $value = ord($buf[0]) - + (ord($buf[1]) << 8); - $buf = substr_replace($buf, '', 0, 2); - } else - substr_replace($buf, '', 0, $len); - - return $value; + $bytes = $buf->getBytes(2, $buf); + if ($bytes === null) { + return null; + } + + return ord($bytes[0]) + + (ord($bytes[1]) << 8); } @@ -127,18 +110,15 @@ function tnef_geti16(&$buf) * TNEF decoding helper function * */ -function tnef_geti32(&$buf) +function tnef_geti32(TNEFBuffer $buf): int { - $value = NULL; - $len = strlen($buf); - if ($len >= 4) { - $value = ord($buf[0]) - + (ord($buf[1]) << 8) - + (ord($buf[2]) << 16) - + (ord($buf[3]) << 24); - $buf = substr_replace($buf, '', 0, 4); - } else - substr_replace($buf, '', 0, $len); - - return $value; + $bytes = $buf->getBytes(4, $buf); + if ($bytes === null) { + return null; + } + + return ord($bytes[0]) + + (ord($bytes[1]) << 8) + + (ord($bytes[2]) << 16) + + (ord($bytes[3]) << 24); } diff --git a/test/TNEFAttachmentTest.php b/test/TNEFAttachmentTest.php index 491de09..b79230c 100644 --- a/test/TNEFAttachmentTest.php +++ b/test/TNEFAttachmentTest.php @@ -52,7 +52,7 @@ public function testDecodeAuto($tnefFile, $listFile) { $this->assertEquals($withoutRtf, $withoutRtfdecoded); } - public function tnefFileProvider() { + public static function tnefFileProvider() { $tnefFiles = glob(dirname(__FILE__) . "/testfiles/*.tnef"); $result = []; foreach ($tnefFiles as $tnefFile) { @@ -69,11 +69,11 @@ public function tnefFileProvider() { } private function endsWithRtf($value) { - try { - return explode('.', $value)[1] != 'rtf'; - } catch (Throwable $e) { + $arr = explode('.', $value); + if (count($arr) < 2) { return false; } + return $arr[1] !== 'rtf'; } }