Skip to content
Snippets Groups Projects
Commit 1205ea2d authored by Jared Hancock's avatar Jared Hancock
Browse files

tnef: Fix various TNEF processing issues

parent ad9276e9
No related branches found
No related tags found
No related merge requests found
...@@ -63,8 +63,8 @@ class TnefStreamReader implements Iterator { ...@@ -63,8 +63,8 @@ class TnefStreamReader implements Iterator {
$this->push($stream); $this->push($stream);
// Read header // Read header
if (self::SIGNATURE != $this->_geti(32)) if (self::SIGNATURE != ($S = $this->_geti(32)))
throw new TnefException("Invalid signature"); throw new TnefException(sprintf("%08x: Invalid signature magic", $S));
$this->_geti(16); // Attach key $this->_geti(16); // Attach key
...@@ -109,6 +109,10 @@ class TnefStreamReader implements Iterator { ...@@ -109,6 +109,10 @@ class TnefStreamReader implements Iterator {
return $value; return $value;
} }
protected function skip($bytes) {
$this->pos += $bytes;
}
function check($block) { function check($block) {
$sum = 0; $bytes = strlen($block['data']); $bs = 1024; $sum = 0; $bytes = strlen($block['data']); $bs = 1024;
for ($i=0; $i < $bytes; $i+=$bs) { for ($i=0; $i < $bytes; $i+=$bs) {
...@@ -117,7 +121,8 @@ class TnefStreamReader implements Iterator { ...@@ -117,7 +121,8 @@ class TnefStreamReader implements Iterator {
$sum = $sum % 65536; $sum = $sum % 65536;
} }
if ($block['checksum'] != $sum) 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() { function next() {
...@@ -288,6 +293,16 @@ class TnefAttributeStreamReader extends TnefStreamReader { ...@@ -288,6 +293,16 @@ class TnefAttributeStreamReader extends TnefStreamReader {
$this->pos = 4; $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) { protected function readPhpValue($type) {
switch ($type) { switch ($type) {
case self::TypeUnspecified: case self::TypeUnspecified:
...@@ -296,27 +311,53 @@ class TnefAttributeStreamReader extends TnefStreamReader { ...@@ -296,27 +311,53 @@ class TnefAttributeStreamReader extends TnefStreamReader {
return null; return null;
case self::TypeInt16: 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: case self::TypeInt32:
// Signed 32-bit value = INT32.
// FIXME: Convert to signed value
return $this->_geti(32); return $this->_geti(32);
case self::TypeBoolean: 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: 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; return $f;
case self::TypeFlt64: case self::TypeFlt64:
// 64-bit floating point= DOUBLE.
list($d) = unpack('d', $this->_getx(8)); list($d) = unpack('d', $this->_getx(8));
return $d; return $d;
case self::TypeAppTime:
case self::TypeCurency: case self::TypeCurency:
case self::TypeInt64: // Signed 64-bit int = OLE CURRENCY type.
// FIXME: Convert to PHP double
return $this->_getx(8); 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: case self::TypeSystime:
$a = unpack('Vl/Vh', $this->_getx(8)); $a = unpack('Vl/Vh', $this->_getx(8));
// return FileTimeToU64(f) / 10000000 - 11644473600 // return FileTimeToU64(f) / 10000000 - 11644473600
...@@ -326,7 +367,6 @@ class TnefAttributeStreamReader extends TnefStreamReader { ...@@ -326,7 +367,6 @@ class TnefAttributeStreamReader extends TnefStreamReader {
case self::TypeString8: case self::TypeString8:
case self::TypeUnicode: case self::TypeUnicode:
case self::TypeBinary: case self::TypeBinary:
case self::TypeObject:
$length = $this->_geti(32); $length = $this->_geti(32);
/* Pad to next 4 byte boundary. */ /* Pad to next 4 byte boundary. */
...@@ -341,18 +381,28 @@ class TnefAttributeStreamReader extends TnefStreamReader { ...@@ -341,18 +381,28 @@ class TnefAttributeStreamReader extends TnefStreamReader {
/* Read and truncate to length. */ /* Read and truncate to length. */
$text = substr($this->_getx($datalen), 0, $length); $text = substr($this->_getx($datalen), 0, $length);
if ($type == self::TypeUnicode) { if ($type == self::TypeUnicode) {
$text = Charset::utf8($text, 'ucs2'); // TNEF spec says encoding is UTF-16LE
$text = Charset::utf8($text, 'UTF-16LE');
} }
return $text; 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: case self::TypeCLSID:
return $this->_getx(16); return $this->_getx(16);
default: default:
throw new TnefException(sprintf('0x%04x: Bad data type', $type)); throw new TnefException(sprintf('0x%04x: Bad data type', $type));
} }
} }
function next() { function next() {
...@@ -476,6 +526,9 @@ class TnefStreamParser { ...@@ -476,6 +526,9 @@ class TnefStreamParser {
case self::idMessageID: case self::idMessageID:
$msg->_set('MessageId', $info['data']); $msg->_set('MessageId', $info['data']);
break; break;
case self::idSubject:
$msg->_set('Subject', $info['data']);
break;
case self::attMsgProps: case self::attMsgProps:
// Message properties (includig body) // Message properties (includig body)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment