206 lines
6.4 KiB
PHP
206 lines
6.4 KiB
PHP
<?php
|
|
/**
|
|
* Horde_Mapi::
|
|
*
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
|
|
* @copyright 2009-2017 Horde LLC (http://www.horde.org)
|
|
* @author Michael J Rubinsky <mrubinsk@horde.org>
|
|
* @package Mapi_Utils
|
|
*/
|
|
/**
|
|
* Utility functions for dealing with Microsoft MAPI structures.
|
|
*
|
|
* Copyright 2009-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.
|
|
*
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
|
|
* @copyright 2009-2017 Horde LLC (http://www.horde.org)
|
|
* @author Michael J Rubinsky <mrubinsk@horde.org>
|
|
* @package Mapi_Utils
|
|
*/
|
|
class Horde_Mapi
|
|
{
|
|
/**
|
|
* Determine if the current machine is little endian.
|
|
*
|
|
* @return boolean True if endianness is little endian, otherwise false.
|
|
*/
|
|
public static function isLittleEndian()
|
|
{
|
|
$testint = 0x00FF;
|
|
$p = pack('S', $testint);
|
|
|
|
return ($testint === current(unpack('v', $p)));
|
|
}
|
|
|
|
/**
|
|
* Change the byte order of a number. Used to allow big endian machines to
|
|
* decode the timezone blobs, which are encoded in little endian order.
|
|
*
|
|
* @param integer $num The number to reverse.
|
|
*
|
|
* @return integer The number, in the reverse byte order.
|
|
*/
|
|
public static function chbo($num)
|
|
{
|
|
$u = unpack('l', strrev(pack('l', $num)));
|
|
|
|
return $u[1];
|
|
}
|
|
|
|
/**
|
|
* Obtain the UID from a MAPI GOID.
|
|
*
|
|
* See http://msdn.microsoft.com/en-us/library/hh338153%28v=exchg.80%29.aspx
|
|
*
|
|
* @param string $goid Base64 encoded Global Object Identifier.
|
|
*
|
|
* @return string The UID
|
|
*/
|
|
public static function getUidFromGoid($goid)
|
|
{
|
|
$goid = base64_decode($goid);
|
|
|
|
// First, see if it's an Outlook UID or not.
|
|
if (substr($goid, 40, 8) == 'vCal-Uid') {
|
|
// For vCal UID values:
|
|
// Bytes 37 - 40 contain length of data and padding
|
|
// Bytes 41 - 48 are == vCal-Uid
|
|
// Bytes 53 until next to the last byte (/0) contain the UID.
|
|
return trim(substr($goid, 52, strlen($goid) - 1));
|
|
} else {
|
|
// If it's not a vCal UID, then it is Outlook style UID:
|
|
// The entire decoded goid is converted to hex representation with
|
|
// bytes 17 - 20 converted to zero
|
|
$hex = array();
|
|
foreach (str_split($goid) as $chr) {
|
|
$hex[] = sprintf('%02X', ord($chr));
|
|
}
|
|
array_splice($hex, 16, 4, array('00', '00', '00', '00'));
|
|
return implode('', $hex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a MAPI GOID from a UID
|
|
* See http://msdn.microsoft.com/en-us/library/ee157690%28v=exchg.80%29
|
|
*
|
|
* @param string $uid The UID value to encode.
|
|
*
|
|
* @return string A Base64 encoded GOID
|
|
*/
|
|
public static function createGoid($uid, $options = array())
|
|
{
|
|
// Bytes 1 - 16 MUST be equal to the GOID identifier:
|
|
$arrayid = '040000008200E00074C5B7101A82E008';
|
|
|
|
// Bytes 17 - 20 - Exception replace time (YH YL M D)
|
|
$exception = '00000000';
|
|
|
|
// Bytes 21 - 28 The 8 byte creation time (can be all zeros if not available).
|
|
$creationtime = '0000000000000000';
|
|
|
|
// Bytes 29 - 36 Reserved 8 bytes must be all zeros.
|
|
$reserved = '0000000000000000';
|
|
|
|
// Bytes 37 - 40 - A long value describing the size of the UID data.
|
|
$size = strlen($uid);
|
|
|
|
// Bytes 41 - 52 - MUST BE vCal-Uid 0x01 0x00 0x00 0x00
|
|
$vCard = '7643616C2D55696401000000';
|
|
|
|
// The UID Data:
|
|
$hexuid = '';
|
|
foreach (str_split($uid) as $chr) {
|
|
$hexuid .= sprintf('%02X', ord($chr));
|
|
}
|
|
|
|
// Pack it
|
|
$goid = pack('H*H*H*H*VH*H*x', $arrayid, $exception, $creationtime, $reserved, $size, $vCard, $hexuid);
|
|
|
|
return base64_encode($goid);
|
|
}
|
|
|
|
/**
|
|
* Converts a Windows FILETIME value to a unix timestamp.
|
|
*
|
|
* Adapted from:
|
|
* http://stackoverflow.com/questions/610603/help-me-translate-long-value-expressed-in-hex-back-in-to-a-date-time
|
|
*
|
|
* @param string $ft Binary representation of FILETIME from a pTypDate
|
|
* MAPI property.
|
|
*
|
|
* @return integer The unix timestamp.
|
|
* @throws Horde_Mapi_Exception
|
|
*/
|
|
public static function filetimeToUnixtime($ft)
|
|
{
|
|
$ft = bin2hex($ft);
|
|
$dtval = substr($ft, 0, 16); // clip overlength string
|
|
$dtval = str_pad($dtval, 16, '0'); // pad underlength string
|
|
$quad = self::_flipEndian($dtval);
|
|
try {
|
|
$win64_datetime = self::_hexToBcint($quad);
|
|
return self::_win64ToUnix($win64_datetime);
|
|
} catch (Horde_Mapi_Exception $e) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// swap little-endian to big-endian
|
|
protected static function _flipEndian($str)
|
|
{
|
|
// make sure #digits is even
|
|
if ( strlen($str) & 1 )
|
|
$str = '0' . $str;
|
|
|
|
$t = '';
|
|
for ($i = strlen($str)-2; $i >= 0; $i-=2)
|
|
$t .= substr($str, $i, 2);
|
|
|
|
return $t;
|
|
}
|
|
|
|
// convert hex string to BC-int
|
|
protected static function _hexToBcint($str)
|
|
{
|
|
if (!extension_loaded('bcmath')) {
|
|
throw new Horde_Mapi_Exception('bcmath extension not loaded.');
|
|
}
|
|
|
|
$hex = array(
|
|
'0'=>'0', '1'=>'1', '2'=>'2', '3'=>'3', '4'=>'4',
|
|
'5'=>'5', '6'=>'6', '7'=>'7', '8'=>'8', '9'=>'9',
|
|
'a'=>'10', 'b'=>'11', 'c'=>'12', 'd'=>'13', 'e'=>'14', 'f'=>'15',
|
|
'A'=>'10', 'B'=>'11', 'C'=>'12', 'D'=>'13', 'E'=>'14', 'F'=>'15'
|
|
);
|
|
|
|
$bci = '0';
|
|
$len = strlen($str);
|
|
for ($i = 0; $i < $len; ++$i) {
|
|
$bci = bcmul($bci, '16');
|
|
$ch = $str[$i];
|
|
if (isset($hex[$ch]))
|
|
$bci = bcadd($bci, $hex[$ch]);
|
|
}
|
|
|
|
return $bci;
|
|
}
|
|
|
|
protected static function _win64ToUnix($bci)
|
|
{
|
|
if (!extension_loaded('bcmath')) {
|
|
throw new Horde_Mapi_Exception('bcmath extension not loaded.');
|
|
}
|
|
|
|
// Unix epoch as a Windows file date-time value
|
|
$magicnum = '116444735995904000';
|
|
$t = bcsub($bci, $magicnum); // Cast to Unix epoch
|
|
return bcdiv($t, '10000000', 0); // Convert from ticks to seconds
|
|
}
|
|
|
|
|
|
}
|