1<?php 2/** 3 * Horde_ActiveSync_Utils:: 4 * 5 * @license http://www.horde.org/licenses/gpl GPLv2 6 * 7 * @copyright 2010-2020 Horde LLC (http://www.horde.org) 8 * @author Michael J Rubinsky <mrubinsk@horde.org> 9 * @package ActiveSync 10 */ 11/** 12 * Horde_ActiveSync_Utils:: contains general utilities. 13 * 14 * @license http://www.horde.org/licenses/gpl GPLv2 15 * 16 * @copyright 2010-2020 Horde LLC (http://www.horde.org) 17 * @author Michael J Rubinsky <mrubinsk@horde.org> 18 * @package ActiveSync 19 */ 20class Horde_ActiveSync_Utils 21{ 22 /** 23 * Decode a base64 encoded URI 24 * 25 * @param string $url The Base64 encoded string. 26 * 27 * @return array The decoded request 28 */ 29 public static function decodeBase64($uri) 30 { 31 $commandMap = array( 32 0 => 'Sync', 33 1 => 'SendMail', 34 2 => 'SmartForward', 35 3 => 'SmartReply', 36 4 => 'GetAttachment', 37 9 => 'FolderSync', 38 10 => 'FolderCreate', 39 11 => 'FolderDelete', 40 12 => 'FolderUpdate', 41 13 => 'MoveItems', 42 14 => 'GetItemEstimate', 43 15 => 'MeetingResponse', 44 16 => 'Search', 45 17 => 'Settings', 46 18 => 'Ping', 47 19 => 'ItemOperations', 48 20 => 'Provision', 49 21 => 'ResolveRecipients', 50 22 => 'ValidateCert' 51 ); 52 $stream = fopen('php://temp', 'r+'); 53 fwrite($stream, base64_decode($uri)); 54 rewind($stream); 55 $results = array(); 56 // Version, command, locale 57 $data = unpack('CprotocolVersion/Ccommand/vlocale', fread($stream, 4)); 58 $results['ProtVer'] = substr($data['protocolVersion'], 0, -1) . '.' . substr($data['protocolVersion'], -1); 59 $results['Cmd'] = $commandMap[$data['command']]; 60 $results['Locale'] = $data['locale']; 61 62 // deviceId 63 $length = ord(fread($stream, 1)); 64 if ($length > 0) { 65 $data = fread($stream, $length); 66 $data = unpack('H' . ($length * 2) . 'DevID', $data); 67 $results['DeviceId'] = $data['DevID']; 68 } 69 70 // policyKey 71 $length = ord(fread($stream, 1)); 72 if ($length > 0) { 73 $data = unpack('VpolicyKey', fread($stream, $length)); 74 $results['PolicyKey'] = $data['policyKey']; 75 } 76 77 // deviceType 78 $length = ord(fread($stream, 1)); 79 if ($length > 0) { 80 $data = unpack('A' . $length . 'devType', fread($stream, $length)); 81 $results['DeviceType'] = $data['devType']; 82 } 83 84 // Remaining properties 85 while (!feof($stream)) { 86 $tag = ord(fread($stream, 1)); 87 $length = ord(fread($stream, 1)); 88 if ($length > 0 || $tag == 7) { 89 switch ($tag) { 90 case 0: 91 $data = unpack('A' . $length . 'AttName', fread($stream, $length)); 92 $results['AttachmentName'] = $data['AttName']; 93 break; 94 case 1: 95 $data = unpack('A' . $length . 'CollId', fread($stream, $length)); 96 $results['CollectionId'] = $data['CollId']; 97 break; 98 case 3: 99 $data = unpack('A' . $length . 'ItemId', fread($stream, $length)); 100 $results['ItemId'] = $data['ItemId']; 101 break; 102 case 4: 103 $data = unpack('A' . $length . 'Lid', fread($stream, $length)); 104 $results['LongId'] = $data['Lid']; 105 break; 106 case 5: 107 $data = unpack('A' . $length . 'Pid', fread($stream, $length)); 108 $results['ParentId'] = $data['Pid']; 109 break; 110 case 6: 111 $data = unpack('A' . $length . 'Oc', fread($stream, $length)); 112 $results['Occurrence'] = $data['Oc']; 113 break; 114 case 7: 115 $options = ord(fread($stream, 1)); 116 $results['SaveInSent'] = !!($options & 0x01); 117 $results['AcceptMultiPart'] = !!($options & 0x02); 118 break; 119 case 8: 120 $data = unpack('A' . $length . 'User', fread($stream, $length)); 121 $results['User'] = $data['User']; 122 break; 123 } 124 } 125 } 126 127 return $results; 128 } 129 130 /** 131 * Obtain the UID from a MAPI GOID. 132 * 133 * See http://msdn.microsoft.com/en-us/library/hh338153%28v=exchg.80%29.aspx 134 * 135 * @param string $goid Base64 encoded Global Object Identifier. 136 * 137 * @return string The UID 138 * @deprecated Will be removed in H6. Use Horde_Mapi::getUidFromGoid 139 */ 140 public static function getUidFromGoid($goid) 141 { 142 $goid = base64_decode($goid); 143 144 // First, see if it's an Outlook UID or not. 145 if (substr($goid, 40, 8) == 'vCal-Uid') { 146 // For vCal UID values: 147 // Bytes 37 - 40 contain length of data and padding 148 // Bytes 41 - 48 are == vCal-Uid 149 // Bytes 53 until next to the last byte (/0) contain the UID. 150 return trim(substr($goid, 52, strlen($goid) - 1)); 151 } else { 152 // If it's not a vCal UID, then it is Outlook style UID: 153 // The entire decoded goid is converted to hex representation with 154 // bytes 17 - 20 converted to zero 155 $hex = array(); 156 foreach (str_split($goid) as $chr) { 157 $hex[] = sprintf('%02X', ord($chr)); 158 } 159 array_splice($hex, 16, 4, array('00', '00', '00', '00')); 160 return implode('', $hex); 161 } 162 } 163 164 /** 165 * Create a MAPI GOID from a UID 166 * See http://msdn.microsoft.com/en-us/library/ee157690%28v=exchg.80%29 167 * 168 * @param string $uid The UID value to encode. 169 * 170 * @return string A Base64 encoded GOID 171 * @deprecated Will be removed in H6. Use Horde_Mapi::createGoid 172 */ 173 public static function createGoid($uid, $options = array()) 174 { 175 // Bytes 1 - 16 MUST be equal to the GOID identifier: 176 $arrayid = '040000008200E00074C5B7101A82E008'; 177 178 // Bytes 17 - 20 - Exception replace time (YH YL M D) 179 $exception = '00000000'; 180 181 // Bytes 21 - 28 The 8 byte creation time (can be all zeros if not available). 182 $creationtime = '0000000000000000'; 183 184 // Bytes 29 - 36 Reserved 8 bytes must be all zeros. 185 $reserved = '0000000000000000'; 186 187 // Bytes 37 - 40 - A long value describing the size of the UID data. 188 $size = strlen($uid); 189 190 // Bytes 41 - 52 - MUST BE vCal-Uid 0x01 0x00 0x00 0x00 191 $vCard = '7643616C2D55696401000000'; 192 193 // The UID Data: 194 $hexuid = ''; 195 foreach (str_split($uid) as $chr) { 196 $hexuid .= sprintf('%02X', ord($chr)); 197 } 198 199 // Pack it 200 $goid = pack('H*H*H*H*VH*H*x', $arrayid, $exception, $creationtime, $reserved, $size, $vCard, $hexuid); 201 202 return base64_encode($goid); 203 } 204 205 /** 206 * Ensure $data is converted to valid UTF-8 data. Works as follows: 207 * Converts to UTF-8, assuming data is in $from_charset encoding. If 208 * that produces invalid UTF-8, attempt to convert to most common mulitibyte 209 * encodings. If that *still* fails, strip out non 7-Bit characters...and 210 * force encoding to UTF-8 from $from_charset as a last resort. 211 * 212 * @param string $data The string data to convert to UTF-8. 213 * @param string $from_charset The character set to assume $data is encoded 214 * in. 215 * 216 * @return string A valid UTF-8 encoded string. 217 */ 218 public static function ensureUtf8($data, $from_charset) 219 { 220 $text = Horde_String::convertCharset($data, $from_charset, 'UTF-8'); 221 if (!Horde_String::validUtf8($text)) { 222 $test_charsets = array( 223 'windows-1252', 224 'UTF-8' 225 ); 226 foreach ($test_charsets as $charset) { 227 if ($charset != $from_charset) { 228 $text = Horde_String::convertCharset($data, $charset, 'UTF-8'); 229 if (Horde_String::validUtf8($text)) { 230 return $text; 231 } 232 } 233 } 234 // Invalid UTF-8 still found. Strip out non 7-bit characters, or if 235 // that fails, force a conversion to UTF-8 as a last resort. Need 236 // to break string into smaller chunks to avoid hitting 237 // https://bugs.php.net/bug.php?id=37793 238 $chunk_size = 4000; 239 $text = ''; 240 while ($data !== false && strlen($data)) { 241 $test = self::_stripNon7BitChars(substr($data, 0, $chunk_size)); 242 if ($test !== false) { 243 $text .= $test; 244 } else { 245 return Horde_String::convertCharset($data, $from_charset, 'UTF-8', true); 246 } 247 $data = substr($data, $chunk_size); 248 } 249 } 250 251 return $text; 252 } 253 254 /** 255 * Strip out non 7Bit characters from a text string. 256 * 257 * @param string $text The string to strip. 258 * 259 * @return string|boolean The stripped string, or false if failed. 260 */ 261 protected static function _stripNon7BitChars($text) 262 { 263 return preg_replace('/[^\x09\x0A\x0D\x20-\x7E]/', '', $text); 264 } 265 266} 267