Files
server/usr/share/psa-pear/pear/php/Horde/Compress/Tnef.php
2026-01-07 20:52:11 +01:00

767 lines
31 KiB
PHP

<?php
/**
* Copyright 2002-2017 Horde LLC (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*
* The TNEF rendering is based on code by:
* Graham Norbury <gnorbury@bondcar.com>
* Original design by:
* Thomas Boll <tb@boll.ch>, Mark Simpson <damned@world.std.com>
*
* @author Jan Schneider <jan@horde.org>
* @author Michael Slusarz <slusarz@horde.org>
* @author Michael J Rubinsky <mrubinsk@horde.org>
* @category Horde
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package Compress
*/
/**
* The Horde_Compress_Tnef class allows MS-TNEF data to be displayed.
*
* @author Jan Schneider <jan@horde.org>
* @author Michael Slusarz <slusarz@horde.org>
* @author Michael J Rubinsky <mrubinsk@horde.org>
* @category Horde
* @copyright 2002-2017 Horde LLC
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package Compress
*/
class Horde_Compress_Tnef extends Horde_Compress_Base
{
const PSETID_MEETING = '{6ED8DA90-450B-101B-98DA-00AA003F1305}';
const PSETID_APPOINTMENT = '{00062002-0000-0000-C000-000000000046}';
const PSETID_COMMON = '{00062008-0000-0000-C000-000000000046}';
const PSETID_PUBLIC_STRINGS = '{00020329-0000-0000-C000-000000000046}';
const PSETID_NOTE = '{0006200E-0000-0000-C000-000000000046}';
const PSETID_TASK = '{00062003-0000-0000-C000-000000000046}';
const PSETID_MAPI = '{00020328-0000-0000-C000-000000000046}';
const SIGNATURE = 0x223e9f78;
const LVL_MESSAGE = 0x01;
const LVL_ATTACHMENT = 0x02;
// @deprecated Now lives in Horde_Compress_Tnef_Rtf::
const RTF_COMPRESSED = 0x75465a4c;
const RTF_UNCOMPRESSED = 0x414c454d;
// TNEF specific properties (includes the type).
const AOWNER = 0x60000;
const ASENTFOR = 0x60001;
const AORIGINALMCLASS = 0x70006;
const ASUBJECT = 0x18004;
const ADATESENT = 0x38005;
const ADATERECEIVED = 0x38006;
const AFROM = 0x08000;
const ASTATUS = 0x68007;
const AMCLASS = 0x78008;
const AMESSAGEID = 0x18009;
const APARENTID = 0x1800a;
const ACONVERSATIONID = 0x1800b;
const ABODY = 0x2800c;
const APRIORITY = 0x4800d;
const ATTACHDATA = 0x6800f;
const AFILENAME = 0x18010;
const ATTACHMETAFILE = 0x68011;
const ATTACHCREATEDATE = 0x38012;
const ADATEMODIFIED = 0x38020;
// idAttachRendData
const ARENDDATA = 0x69002;
const AMAPIPROPS = 0x69003;
const ARECIPIENTTABLE = 0x69004;
const AMAPIATTRS = 0x69005;
// @deprecated constants to be removed in H6
const OEMCODEPAGE = 0x69007;
const AVERSION = 0x89006;
const ID_REQUEST_RESP = 0x40009;
const ID_FROM = 0x8000;
const AIDOWNER = 0x50008;
const ID_DATE_START = 0x30006;
const ID_DATE_END = 0x30007;
// All valid MAPI data types.
// @todo These should all be MAPI_TYPE_*
const MAPI_TYPE_UNSPECIFIED = 0x0000;
const MAPI_NULL = 0x0001;
const MAPI_SHORT = 0x0002;
const MAPI_INT = 0x0003;
const MAPI_FLOAT = 0x0004;
const MAPI_DOUBLE = 0x0005;
const MAPI_CURRENCY = 0x0006;
const MAPI_APPTIME = 0x0007;
const MAPI_ERROR = 0x000a;
const MAPI_BOOLEAN = 0x000b;
const MAPI_OBJECT = 0x000d;
const MAPI_INT8BYTE = 0x0014;
const MAPI_STRING = 0x001e;
const MAPI_UNICODE_STRING = 0x001f;
const MAPI_SYSTIME = 0x0040;
const MAPI_CLSID = 0x0048;
const MAPI_BINARY = 0x0102;
// Constants for possible value of MAPI_MEETING_REQUEST_TYPE
const MAPI_MEETING_INITIAL = 0x00000001;
const MAPI_MEETING_FULL_UPDATE = 0x100010000;
const MAPI_MEETING_INFO = 0x00020000;
// pidTag* properties. These should all be renamed in H6 to include pidTag
// in the name to make this clear.
const MAPI_MESSAGE_CLASS = 0x001A;
const MAPI_TAG_SUBJECT_PREFIX = 0x003D;
const MAPI_CONVERSATION_TOPIC = 0x0070;
// pidTagSentRepresentingName
const MAPI_SENT_REP_NAME = 0x0042;
// pidTagSentRepresentingEmail
const MAPI_SENT_REP_EMAIL_ADDR = 0x0065;
// pidTagDisplayTo
const MAPI_DISPLAY_TO = 0x0e04;
// pidTagSentRepresentingSMTPAddress
const MAPI_SENT_REP_SMTP_ADDR = 0x5d02;
// pidTagInReplyTo
const MAPI_IN_REPLY_TO_ID = 0x1042;
const MAPI_CREATION_TIME = 0x3007;
const MAPI_MODIFICATION_TIME = 0x3008;
const MAPI_ATTACH_DATA = 0x3701;
const MAPI_ATTACH_EXTENSION = 0x3703;
const MAPI_ATTACH_LONG_FILENAME = 0x3707;
const MAPI_ATTACH_MIME_TAG = 0x370E;
const MAPI_ORIGINAL_CREATORID = 0x3FF9;
const MAPI_LAST_MODIFIER_NAME = 0x3FFA;
const MAPI_CODEPAGE = 0x3FFD;
const MAPI_SENDER_SMTP = 0x5D01;
// Appointment related.
// This is pidTagStartDate and contains the value of PidLidAppointmentStartWhole
const MAPI_START_DATE = 0x0060; // pidTag
const MAPI_END_DATE = 0x0061; // pidTag
const MAPI_APPOINTMENT_SEQUENCE = 0x8201; // pidLid
const MAPI_BUSY_STATUS = 0x8205; // pidLid
const MAPI_MEETING_REQUEST_TYPE = 0x0026; // pidLid
const MAPI_RESPONSE_REQUESTED = 0x0063; // pidTag
const MAPI_APPOINTMENT_LOCATION = 0x8208; // pidLid
const MAPI_APPOINTMENT_URL = 0x8209; // pidLid
const MAPI_APPOINTMENT_START_WHOLE = 0x820D; // Full datetime of start (FILETIME format)
const MAPI_APPOINTMENT_END_WHOLE = 0x820E; // Full datetime of end (FILETIME format)
const MAPI_APPOINTMENT_DURATION = 0x8213; // pidLid - duration in minutes.
const MAPI_APPOINTMENT_SUBTYPE = 0x8215; // (Boolean - all day event?)
const MAPI_APPOINTMENT_RECUR = 0x8216; // This seems to be a combined property of MAPI_RECURRING, MAPI_RECURRING_TYPE etc...?
const MAPI_APPOINTMENT_STATE_FLAGS = 0x8217; // (bitmap for meeting, received, cancelled?)
const MAPI_RESPONSE_STATUS = 0x8218;
const MAPI_RECURRING = 0x8223;
const MAPI_RECURRENCE_TYPE = 0x8231;
const MAPI_ALL_ATTENDEES = 0x8238; // ALL attendees, required/optional and non-sendable.
const MAPI_TO_ATTENDEES = 0x823B; // All "sendable" attendees that are REQUIRED.
// tz. Not sure when to use STRUCT vs DEFINITION_RECUR. Possible ok to always use STRUCT?
const MAPI_TIMEZONE_STRUCT = 0x8233; // Timezone for recurring mtg?
const MAPI_TIMEZONE_DESCRIPTION = 0x8234; // Description for tz_struct?
const MAPI_START_CLIP_START = 0x8235; // Start datetime in UTC
const MAPI_START_CLIP_END = 0x8236; // End datetime in UTC
const MAPI_CONFERENCING_TYPE = 0x8241;
const MAPI_ORGANIZER_ALIAS = 0x8243; // Supposed to be organizer email, but it seems to be empty?
const MAPI_APPOINTMENT_COUNTER_PROPOSAL = 0x8257; // Boolean
const MAPI_TIMEZONE_START = 0x825E; // Timezone of start_whole
const MAPI_TIMEZONE_END = 0x825F; // Timezone of end_whole
const MAPI_TIMEZONE_DEFINITION_RECUR = 0x8260; // Timezone for use in converting meeting date/time in recurring meeting???
const MAPI_REMINDER_DELTA = 0x8501; // Minutes between start of mtg and overdue.
const MAPI_SIGNAL_TIME = 0x8502; // Initial alarm time.
const MAPI_REMINDER_SIGNAL_TIME = 0x8560; // Time that item becomes overdue.
const MAPI_ENTRY_UID = 0x0003; // pidLidGlobalObjectId, PSETID_MEETING
const MAPI_ENTRY_CLEANID = 0x0023; // pidLidCleanGlobalObjectId, PSETID_MEETING
const MAPI_MEETING_TYPE = 0x0026; // pidLidMeetingType, PSETID_MEETING
const MSG_EDITOR_FORMAT = 0x5909;
const MSG_EDITOR_FORMAT_UNKNOWN = 0;
const MSG_EDITOR_FORMAT_PLAIN = 1;
const MSG_EDITOR_FORMAT_HTML = 2;
const MSG_EDITOR_FORMAT_RTF = 3;
const MAPI_NAMED_TYPE_ID = 0x00;
const MAPI_NAMED_TYPE_STRING = 0x01;
const MAPI_NAMED_TYPE_NONE = 0xff;
const MAPI_MV_FLAG = 0x1000;
const IPM_MEETING_REQUEST = 'IPM.Microsoft Schedule.MtgReq';
const IPM_MEETING_RESPONSE_POS = 'IPM.Microsoft Schedule.MtgRespP';
const IPM_MEETING_RESPONSE_NEG = 'IPM.Microsoft Schedule.MtgRespN';
const IPM_MEETING_RESPONSE_TENT = 'IPM.Microsoft Schedule.MtgRespA';
const IPM_MEETING_REQUEST_CANCELLED = 'IPM.Microsoft Schedule.MtgCncl';
const MAPI_MEETING_RESPONSE_POS = 'IPM.Schedule.Meeting.Resp.Pos';
const MAPI_MEETING_RESPONSE_NEG = 'IPM.Schedule.Meeting.Resp.Neg';
const MAPI_MEETING_RESPONSE_TENT = 'IPM.Schedule.Meeting.Resp.Tent';
const IPM_TASK_REQUEST = 'IPM.TaskRequest';
const IPM_TASK_GUID = 0x8519; // pidLidTaskGlobalId, PSETID_Common
const MAPI_TAG_BODY = 0x1000;
const MAPI_NATIVE_BODY = 0x1016;
const MAPI_TAG_HTML = 0x1013;
const MAPI_TAG_RTF_COMPRESSED = 0x1009;
const RECUR_DAILY = 0x200A;
const RECUR_WEEKLY = 0x200B;
const RECUR_MONTHLY = 0x200C;
const RECUR_YEARLY = 0x200D;
const PATTERN_DAY = 0x0000;
const PATTERN_WEEK = 0x0001;
const PATTERN_MONTH = 0x0002;
const PATTERN_MONTH_END = 0x0004;
const PATTERN_MONTH_NTH = 0x0003;
const RECUR_END_DATE = 0x00002021;
const RECUR_END_N = 0x00002022;
/**
*/
public $canDecompress = true;
/**
* Collection of files contained in the TNEF data.
*
* @var array of Horde_Compress_Tnef_Object objects.
*/
protected $_files = array();
/**
* Collection of embedded TNEF attachments within the outer TNEF file.
*
* @var array of Horde_Compress_Tnef objects.
*/
protected $_attachments = array();
/**
*
* @var Horde_Compress_Tnef_MessageData
*/
protected $_msgInfo;
/**
* The TNEF object currently being decoded.
*
* @var Horde_Compress_Tnef_Object
*/
protected $_currentObject;
/**
* Decompress the TNEF data. For BC reasons we can only return a numerically
* indexed array of object data. For more detailed information, use
* self::getFiles(), self::getAttachements(), and self::getMsgInfo().
*
* @todo Refactor return data for Horde 6.
* @return array The decompressed data.
* @throws Horde_Compress_Exception
*/
public function decompress($data, array $params = array())
{
if ($this->_geti($data, 32) == self::SIGNATURE) {
$this->_logger->debug(sprintf(
'TNEF: Signature: 0x%08X Key: 0x%04X',
self::SIGNATURE,
$this->_geti($data, 16))
);
// Version
$this->_geti($data, 8); // lvl_message
$this->_geti($data, 32); // idTnefVersion
$this->_getx($data, $this->_geti($data, 32));
$this->_geti($data, 16); //checksum
// Codepage
$this->_geti($data, 8);
$this->_geti($data, 32); // idCodepage
$this->_getx($data, $this->_geti($data, 32));
$this->_geti($data, 16); //checksum
$out = array();
$this->_msgInfo = new Horde_Compress_Tnef_MessageData($this->_logger);
while (strlen($data) > 0) {
switch ($this->_geti($data, 8)) {
case self::LVL_MESSAGE:
$this->_logger->debug('DECODING LVL_MESSAGE property.');
$this->_decodeMessageProperty($data);
break;
case self::LVL_ATTACHMENT:
$this->_logger->debug('DECODING LVL_ATTACHMENT property.');
$this->_decodeAttachment($data);
break;
}
}
}
// Add the files. @todo the embedded attachments.
foreach ($this->_files as $object) {
$out[] = $object->toArray();
}
return $out;
}
/**
* Return the collection of files in the TNEF data.
*
* @return array @see self::$_files
*/
public function getFiles()
{
return $this->_files;
}
/**
* Return the collection of embedded attachments.
*
* @return array @see self::$_attachments
*/
public function getAttachments()
{
return $this->_attachments;
}
/**
* Return the message information data.
*
* @return array @see self::$_msgInfo
*/
public function getMsgInfo()
{
return $this->_msgInfo;
}
/**
* Sets the current object being decompressed.
*
* @param Horde_Compress_Tnef_Object $object
*/
public function setCurrentObject(Horde_Compress_Tnef_Object $object)
{
$this->_currentObject = $object;
}
/**
* Extract a set of encapsulated MAPI properties. Normally either embedded
* in an attachment structure, or an idMessageProperty structure.
*
* @param string $data The data string.
* @param array &$attachment_data TODO
*/
protected function _extractMapiAttributes($data)
{
// Number of attributes.
$number = $this->_geti($data, 32);
$this->_logger->debug(sprintf('TNEF: Extracting %d MAPI attributes.', $number));
while ((strlen($data) > 0) && $number--) {
$have_mval = false;
$num_mval = 1;
$value = null;
$attr_type = $this->_geti($data, 16);
$attr_name = $this->_geti($data, 16);
$namespace = false;
// Multivalue attributes.
if (($attr_type & self::MAPI_MV_FLAG) != 0) {
$have_mval = true;
$attr_type = $attr_type & ~self::MAPI_MV_FLAG;
$this->_logger->debug(sprintf(
'TNEF: Multivalue attribute of type: 0x%04X',
$attr_type)
);
}
// Named attributes.
if (($attr_name >= 0x8000) && ($attr_name < 0xFFFE)) {
$namespace = $this->_toNamespaceGUID($this->_getx($data, 16));
// The type of named property, an ID or STRING.
$named_type = $this->_geti($data, 32);
switch ($named_type) {
case self::MAPI_NAMED_TYPE_ID:
$pid = $attr_name;
$attr_name = $this->_geti($data, 32);
$msg = sprintf('TNEF: pid: 0x%X type: 0x%X Named Id: %s 0x%04X', $pid, $attr_type, $namespace, $attr_name);
$this->_logger->debug($msg);
break;
case self::MAPI_NAMED_TYPE_STRING:
// @todo. We haven't needed data from any string named id
// yet, but might be able to just assign the name to
// $attr_name and pass it down to _currentObject for now.
// For H6, look at using some lightweight object to transport
// the name/value to the various objects.
$attr_name = 0x9999;
$id_len = $this->_geti($data, 32);
$data_len = $id_len + ((4 - ($id_len % 4)) % 4);
$name = Horde_String::substr($this->_getx($data, $data_len), 0, $id_len);
$name = trim(Horde_String::convertCharset($name, 'UTF-16LE', 'UTF-8'));
$this->_logger->debug(sprintf('TNEF: Named String Id: %s', $name));
break;
case self::MAPI_NAMED_TYPE_NONE:
continue 2;
break;
default:
$msg = sprintf('TNEF: Unknown NAMED type: pid: 0x%X type: 0x%X Named TYPE: %s 0x%04X', $pid, $attr_type, $namespace, $named_type);
$this->_logger->notice($msg);
continue 2;
}
}
if ($have_mval) {
$num_mval = $this->_geti($data, 32);
$this->_logger->debug(sprintf(
'TNEF: Number of multivalues: %s', $num_mval));
}
switch ($attr_type) {
case self::MAPI_NULL:
case self::MAPI_TYPE_UNSPECIFIED:
break;
case self::MAPI_SHORT:
$value = $this->_geti($data, 16);
// Padding. See MS-OXTNEF 2.1.3.4
// Must always pad to a multiple of 4 bytes.
$this->_geti($data, 16);
break;
case self::MAPI_INT:
case self::MAPI_BOOLEAN:
for ($i = 0; $i < $num_mval; $i++) {
$value = $this->_geti($data, 32);
}
break;
case self::MAPI_FLOAT:
case self::MAPI_ERROR:
$value = $this->_getx($data, 4);
break;
case self::MAPI_DOUBLE:
case self::MAPI_APPTIME:
case self::MAPI_CURRENCY:
case self::MAPI_INT8BYTE:
case self::MAPI_SYSTIME:
$value = $this->_getx($data, 8);
break;
case self::MAPI_CLSID:
$this->_logger->debug('TNEF: CLSID??');
$this->_getx($data, 16);
break;
case self::MAPI_STRING:
case self::MAPI_UNICODE_STRING:
case self::MAPI_BINARY:
case self::MAPI_OBJECT:
$num_vals = ($have_mval) ? $num_mval : $this->_geti($data, 32);
for ($i = 0; $i < $num_vals; $i++) {
$length = $this->_geti($data, 32);
/* Pad to next 4 byte boundary. */
$datalen = $length + ((4 - ($length % 4)) % 4);
/* Read and truncate to length. */
$value = substr($this->_getx($data, $datalen), 0, $length);
}
switch ($attr_type) {
case self::MAPI_UNICODE_STRING:
// MAPI Unicode is UTF-16LE; convert to UTF-8
$value = Horde_String::convertCharset(
$value,
'UTF-16LE',
'UTF-8'
);
break;
}
switch ($attr_type) {
case self::MAPI_STRING:
case self::MAPI_UNICODE_STRING:
// Strings are null-terminated.
$value = substr($value, 0, -1);
break;
}
break;
default:
$msg = sprintf(
'TNEF: Unknown attribute type, "0x%X"',
$attr_type);
throw new Horde_Compress_Exception($msg);
$this->_logger->notice($msg);
}
// @todo Utility method to make this log more readable.
$this->_logger->debug(sprintf('TNEF: Attribute: 0x%X Type: 0x%X', $attr_name, $attr_type));
switch ($attr_name) {
case self::MAPI_TAG_RTF_COMPRESSED:
$this->_logger->debug('TNEF: Found compressed RTF text.');
$rtf = new Horde_Compress_Tnef_Rtf($this->_logger, $value);
$this->_files[] = $rtf;
// Give the currentObject a chance to do something with the RTF
// body. This is used, e.g., in meeting requests to populate
// the description field.
if ($this->_currentObject) {
try {
$this->_currentObject->setMapiAttribute($attr_type, $attr_name, $rtf->toPlain());
} catch (Horde_Compress_Exception $e) {
$this->_logger->err(sprintf('TNEF: Unable to set attribute: %s', $e->getMessage()));
}
}
break;
case self::MAPI_ATTACH_DATA:
$this->_logger->debug('TNEF: Found nested MAPI object. Parsing.');
$this->_getx($value, 16);
$att = new Horde_Compress_Tnef($this->_logger);
$att->setCurrentObject($this->_currentObject);
$att->decompress($value);
$this->_attachments[] = $att;
$this->_logger->debug('TNEF: Completed nested attachment parsing.');
break;
default:
try {
$this->_msgInfo->setMapiAttribute($attr_type, $attr_name, $value);
if ($this->_currentObject) {
$this->_currentObject->setMapiAttribute($attr_type, $attr_name, $value, $namespace);
}
} catch (Horde_Compress_Exception $e) {
$this->_logger->err(sprintf('TNEF: Unable to set attribute: %s', $e->getMessage()));
}
}
}
}
/**
* Decodes all LVL_ATTACHMENT data. Attachment data MUST be at the end of
* TNEF stream. First LVL_ATTACHMENT MUST be ARENDDATA (attAttachRendData).
*
* From MS-OXTNEF:
* ; An attachment is determined/delimited by attAttachRendData, followed by
* ; other encoded attributes, if any, and ending with attAttachment
* ; if there are any encoded properties.
* AttachData = AttachRendData [*AttachAttribute] [AttachProps]
* AttachRendData = attrLevelAttachment idAttachRendData Length Data Checksum
* AttachAttribute = attrLevelAttachment idAttachAttr Length Data Checksum
* AttachProps = attrLevelAttachment idAttachment Length Data Checksum
*
* @param [type] &$data [description]
* @return [type] [description]
*/
protected function _decodeAttachment(&$data)
{
$attribute = $this->_geti($data, 32);
$size = $this->_geti($data, 32);
$value = $this->_getx($data, $size);
$this->_geti($data, 16);
switch ($attribute) {
case self::ARENDDATA:
// This delimits the attachment structure. I.e., every attachment
// MUST begin with idAttachRendData.
$this->_logger->debug('Creating new attachment.');
if (!$this->_currentObject instanceof Horde_Compress_Tnef_VTodo) {
$this->_currentObject = new Horde_Compress_Tnef_File($this->_logger);
$this->_files[] = $this->_currentObject;
}
break;
case self::AFILENAME:
// Strip path.
$value = preg_replace('/.*[\/](.*)$/', '\1', $value);
$value = str_replace("\0", '', $value);
$this->_currentObject->setTnefAttribute($attribute, $value, $size);
break;
case self::ATTACHDATA:
// The attachment itself.
$this->_currentObject->setTnefAttribute($attribute, $value, $size);
break;
case self::AMAPIATTRS:
// idAttachment (Attachment properties)
$this->_extractMapiAttributes($value);
break;
default:
if (!empty($this->_currentObject)) {
$this->_currentObject->setTnefAttribute($attribute, $value, $size);
}
}
}
/**
* Decodes TNEF attributes.
*
* @param [type] &$data [description]
* @return [type] [description]
*/
protected function _decodeMessageProperty(&$data)
{
// This contains the type AND the attribute name. We should only check
// against the name since this is very confusing (everything else is
// checked against just name). Can't change until Horde 6 though since
// the constants would have to change. Also, the type identifiers are
// different between MAPI and TNEF. Of course...
// $type = $this->_geti($data, 16);
// $attribute = $this->_geti($data, 16);
$attribute = $this->_geti($data, 32);
$this->_logger->debug(sprintf('TNEF: Message property 0x%X found.', $attribute));
$value = false;
switch ($attribute) {
case self::AMCLASS:
// Start of a new message.
$message_class = trim($this->_decodeAttribute($data));
$this->_logger->debug(sprintf('TNEF: Message class: %s', $message_class));
switch ($message_class) {
case self::IPM_MEETING_REQUEST :
$this->_currentObject = new Horde_Compress_Tnef_Icalendar($this->_logger);
$this->_currentObject->setMethod('REQUEST', $message_class);
$this->_files[] = $this->_currentObject;
break;
case self::IPM_MEETING_RESPONSE_TENT:
case self::IPM_MEETING_RESPONSE_NEG:
case self::IPM_MEETING_RESPONSE_POS:
$this->_currentObject = new Horde_Compress_Tnef_Icalendar($this->_logger);
$this->_currentObject->setMethod('REPLY', $message_class);
$this->_files[] = $this->_currentObject;
break;
case self::IPM_MEETING_REQUEST_CANCELLED:
$this->_currentObject = new Horde_Compress_Tnef_Icalendar($this->_logger);
$this->_currentObject->setMethod('CANCEL', $message_class);
$this->_files[] = $this->_currentObject;
break;
case self::IPM_TASK_REQUEST:
$this->_currentObject = new Horde_Compress_Tnef_VTodo($this->_logger, null, array('parent' => &$this));
$this->_files[] = $this->_currentObject;
break;
default:
$this->_logger->debug(sprintf('Unknown message class: %s', $message_class));
}
break;
case self::AMAPIPROPS:
$this->_logger->debug('TNEF: Extracting encapsulated message properties (idMsgProps)');
$properties = $this->_decodeAttribute($data);
$this->_extractMapiAttributes($properties);
break;
case self::APRIORITY:
case self::AOWNER:
case self::ARECIPIENTTABLE:
case self::ABODY:
case self::ASTATUS:
case self::ACONVERSATIONID:
case self::APARENTID:
case self::AMESSAGEID:
case self::ASUBJECT:
case self::AORIGINALMCLASS:
$value = $this->_decodeAttribute($data);
break;
case self::ADATERECEIVED:
case self::ADATESENT:
case self::ADATEMODIFIED:
case self::ID_DATE_END:
try {
$value = new Horde_Date(Horde_Mapi::filetimeToUnixtime($this->_decodeAttribute($data)), 'UTC');
} catch (Horde_Mapi_Exception $e) {
throw new Horde_Compress_Exception($e);
} catch (Horde_Date_Exception $e) {
$this->_logger->err(sprintf('TNEF: Unable to parse date string - %s', $e->getMessage()));
}
break;
case self::AFROM:
case self::ASENTFOR:
$msgObj = $this->_decodeAttribute($data);
$display_name = $this->_getx($msgObj, $this->_geti($msgObj, 16));
$email = $this->_getx($msgObj, $this->_geti($msgObj, 16));
$value = $email; // @todo - Do we need to pass display name too?
break;
default:
$size = $this->_geti($data, 32);
$value = $this->_getx($data, $size);
$this->_geti($data, 16); // Checksum.
}
if ($value && $this->_currentObject) {
$this->_currentObject->setTnefAttribute($attribute, $value, (empty($size) ? strlen($value) : $size));
}
}
/**
* Decode a single attribute.
*
* @param string &$data The data string.
*/
protected function _decodeAttribute(&$data)
{
$size = $this->_geti($data, 32);
$value = $this->_getx($data, $size);
$this->_geti($data, 16); // Checksum.
return $value;
}
/**
* Pop specified number of bytes from the buffer.
*
* @param string &$data The data string.
* @param integer $bytes How many bytes to retrieve.
*
* @return @todo these also need to exist in the objects. Need to
* refactor this away by adding a data/stream object
* with getx/geti methods with the data hanled internally.
*/
protected function _getx(&$data, $bytes)
{
$value = null;
if (strlen($data) >= $bytes) {
$value = substr($data, 0, $bytes);
$data = substr_replace($data, '', 0, $bytes);
}
return $value;
}
/**
* Pop specified number of bits from the buffer
*
* @param string &$data The data string.
* @param integer $bits How many bits to retrieve.
*
* @return TODO
*/
protected function _geti(&$data, $bits)
{
$bytes = $bits / 8;
$value = null;
if (strlen($data) >= $bytes) {
$value = ord($data[0]);
if ($bytes >= 2) {
$value += (ord($data[1]) << 8);
}
if ($bytes >= 4) {
$value += (ord($data[2]) << 16) + (ord($data[3]) << 24);
}
$data = substr_replace($data, '', 0, $bytes);
}
return $value;
}
protected function _toNamespaceGUID($value)
{
$guid = unpack("VV/v2v/n4n", $value);
return sprintf("{%08X-%04X-%04X-%04X-%04X%04X%04X}",$guid['V'], $guid['v1'], $guid['v2'],$guid['n1'],$guid['n2'],$guid['n3'],$guid['n4']);
}
}