From 1205ea2d0a8c02f7c314e4b3a588200f04ddc6ec Mon Sep 17 00:00:00 2001 From: Jared Hancock <jared@osticket.com> Date: Mon, 13 Apr 2015 15:22:07 -0500 Subject: [PATCH] tnef: Fix various TNEF processing issues --- include/tnef_decoder.php | 75 ++++++++++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/include/tnef_decoder.php b/include/tnef_decoder.php index 0459998d4..08bf3755c 100644 --- a/include/tnef_decoder.php +++ b/include/tnef_decoder.php @@ -63,8 +63,8 @@ class TnefStreamReader implements Iterator { $this->push($stream); // Read header - if (self::SIGNATURE != $this->_geti(32)) - throw new TnefException("Invalid signature"); + if (self::SIGNATURE != ($S = $this->_geti(32))) + throw new TnefException(sprintf("%08x: Invalid signature magic", $S)); $this->_geti(16); // Attach key @@ -109,6 +109,10 @@ class TnefStreamReader implements Iterator { return $value; } + protected function skip($bytes) { + $this->pos += $bytes; + } + function check($block) { $sum = 0; $bytes = strlen($block['data']); $bs = 1024; for ($i=0; $i < $bytes; $i+=$bs) { @@ -117,7 +121,8 @@ class TnefStreamReader implements Iterator { $sum = $sum % 65536; } if ($block['checksum'] != $sum) - throw new TnefException('Corrupted block. Invalid checksum'); + throw new TnefException(sprintf('Corrupted block. %04x: Invalid checksum, expected %04x', + $sum, $block['checksum'])); } function next() { @@ -288,6 +293,16 @@ class TnefAttributeStreamReader extends TnefStreamReader { $this->pos = 4; } + /** + * Read a single typed value from the current input stream. The type is + * a 16-bit number receved as an argument which should be one of the + * Type* constants defined in this class. + * + * According to the TNEF spec, all types regardless of their actual + * size, must be rounded up in size to the next multiple of four (4) + * bytes. Therefore 16-bit values and a strings will need to have extra + * padding consumed to keep the stream on track. + */ protected function readPhpValue($type) { switch ($type) { case self::TypeUnspecified: @@ -296,27 +311,53 @@ class TnefAttributeStreamReader extends TnefStreamReader { return null; case self::TypeInt16: - return $this->_geti(16); + // Signed 16-bit value = INT16. + $int16 = unpack('v', $this->_getx(4)); + $sign = $int16 & 0x8000; + if ($sign) + // Use two's compliment + $int16 = - ((~$int16 & 0xFFFF) + 1); + return $int16; case self::TypeInt32: + // Signed 32-bit value = INT32. + // FIXME: Convert to signed value return $this->_geti(32); case self::TypeBoolean: - return (bool) $this->_geti(32); + // 16-bit Boolean (non-zero = TRUE) + list($bool) = unpack('v', $this->_getx(4)); + return 0 != $bool; case self::TypeFlt32: - list($f) = unpack('f', $this->_getx(8)); + // Signed 32-bit floating point= FLOAT. + list($f) = unpack('f', $this->_getx(4)); return $f; case self::TypeFlt64: + // 64-bit floating point= DOUBLE. list($d) = unpack('d', $this->_getx(8)); return $d; - case self::TypeAppTime: case self::TypeCurency: - case self::TypeInt64: + // Signed 64-bit int = OLE CURRENCY type. + // FIXME: Convert to PHP double return $this->_getx(8); + case self::TypeInt64: + // 8-byte signed integer= INT64. + $x = $this->_getx(8); + if (phpversion() >= '5.6.3') + list($x) = unpack('P', $x); + return $x; + + case self::TypeAppTime: + list($d) = unpack('d', $this->_getx(8)); + // Application time= OLE DATE type. + // Thanks, http://stackoverflow.com/a/10443946/1025836 + // Convert to UNIX timestamp, UTC timezone is assumed + return ($d - 25569) * 86400; + case self::TypeSystime: $a = unpack('Vl/Vh', $this->_getx(8)); // return FileTimeToU64(f) / 10000000 - 11644473600 @@ -326,7 +367,6 @@ class TnefAttributeStreamReader extends TnefStreamReader { case self::TypeString8: case self::TypeUnicode: case self::TypeBinary: - case self::TypeObject: $length = $this->_geti(32); /* Pad to next 4 byte boundary. */ @@ -341,18 +381,28 @@ class TnefAttributeStreamReader extends TnefStreamReader { /* Read and truncate to length. */ $text = substr($this->_getx($datalen), 0, $length); if ($type == self::TypeUnicode) { - $text = Charset::utf8($text, 'ucs2'); + // TNEF spec says encoding is UTF-16LE + $text = Charset::utf8($text, 'UTF-16LE'); } return $text; + case self::TypeObject: + $length = $this->_geti(32); + $oid = $this->_getx(16); + if (bin2hex($text) == "0703020000000000c000000000000046") { + // TODO: Create stream parser for embedded TNEF stream + } + $this->skip($length - 16); + $this->skip((4 - ($length % 4)) % 4); + return null; + case self::TypeCLSID: return $this->_getx(16); default: throw new TnefException(sprintf('0x%04x: Bad data type', $type)); } - } function next() { @@ -476,6 +526,9 @@ class TnefStreamParser { case self::idMessageID: $msg->_set('MessageId', $info['data']); break; + case self::idSubject: + $msg->_set('Subject', $info['data']); + break; case self::attMsgProps: // Message properties (includig body) -- GitLab