[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * The Horde_Mime:: class provides methods for dealing with various MIME (see, 4 * e.g., RFC 2045-2049; 2183; 2231) standards. 5 * 6 * ----- 7 * 8 * This file contains code adapted from PEAR's Mail_mimeDecode library (v1.5). 9 * 10 * http://pear.php.net/package/Mail_mime 11 * 12 * This code appears in Horde_Mime::decodeParam(). 13 * 14 * This code was originally released under this license: 15 * 16 * LICENSE: This LICENSE is in the BSD license style. 17 * Copyright (c) 2002-2003, Richard Heyes <richard@phpguru.org> 18 * Copyright (c) 2003-2006, PEAR <pear-group@php.net> 19 * All rights reserved. 20 * 21 * Redistribution and use in source and binary forms, with or 22 * without modification, are permitted provided that the following 23 * conditions are met: 24 * 25 * - Redistributions of source code must retain the above copyright 26 * notice, this list of conditions and the following disclaimer. 27 * - Redistributions in binary form must reproduce the above copyright 28 * notice, this list of conditions and the following disclaimer in the 29 * documentation and/or other materials provided with the distribution. 30 * - Neither the name of the authors, nor the names of its contributors 31 * may be used to endorse or promote products derived from this 32 * software without specific prior written permission. 33 * 34 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 35 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 36 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 37 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 38 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 39 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 40 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 41 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 42 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 43 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 44 * THE POSSIBILITY OF SUCH DAMAGE. 45 * 46 * ----- 47 * 48 * This file contains code adapted from PEAR's PHP_Compat library (v1.6.0a3). 49 * 50 * http://pear.php.net/package/PHP_Compat 51 * 52 * This code appears in Horde_Mime::_uudecode(). 53 * 54 * This code was originally released under the LGPL 2.1 55 * 56 * ----- 57 * 58 * Copyright 1999-2014 Horde LLC (http://www.horde.org/) 59 * 60 * See the enclosed file COPYING for license information (LGPL). If you 61 * did not receive this file, see http://www.horde.org/licenses/lgpl21. 62 * 63 * @author Chuck Hagenbuch <chuck@horde.org> 64 * @author Michael Slusarz <slusarz@horde.org> 65 * @category Horde 66 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 67 * @package Mime 68 */ 69 class Horde_Mime 70 { 71 /** 72 * The RFC defined EOL string. 73 * 74 * @var string 75 */ 76 const EOL = "\r\n"; 77 78 /** 79 * The list of characters required to be quoted in MIME parameters 80 * (regular expression). 81 * 82 * @since 2.1.0 83 * 84 * @var string 85 */ 86 const MIME_PARAM_QUOTED = '/[\x01-\x20\x22\x28\x29\x2c\x2f\x3a-\x40\x5b-\x5d]/'; 87 88 /** 89 * Attempt to work around non RFC 2231-compliant MUAs by generating both 90 * a RFC 2047-like parameter name and also the correct RFC 2231 91 * parameter. See: 92 * http://lists.horde.org/archives/dev/Week-of-Mon-20040426/014240.html 93 * 94 * @var boolean 95 */ 96 static public $brokenRFC2231 = false; 97 98 /** 99 * Use windows-1252 charset when decoding ISO-8859-1 data? 100 * 101 * @var boolean 102 */ 103 static public $decodeWindows1252 = false; 104 105 /** 106 * Determines if a string contains 8-bit (non US-ASCII) characters. 107 * 108 * @param string $string The string to check. 109 * @param string $charset The charset of the string. Defaults to 110 * US-ASCII. 111 * 112 * @return boolean True if string contains non US-ASCII characters. 113 */ 114 static public function is8bit($string, $charset = null) 115 { 116 return ($string != Horde_String::convertCharset($string, $charset, 'US-ASCII')); 117 } 118 119 /** 120 * MIME encodes a string (RFC 2047). 121 * 122 * @param string $text The text to encode (UTF-8). 123 * @param string $charset The character set to encode to. 124 * 125 * @return string The MIME encoded string (US-ASCII). 126 */ 127 static public function encode($text, $charset = 'UTF-8') 128 { 129 /* The null character is valid US-ASCII, but was removed from the 130 * allowed e-mail header characters in RFC 2822. */ 131 if (!self::is8bit($text, 'UTF-8') && (strpos($text, null) === false)) { 132 return $text; 133 } 134 135 $charset = Horde_String::lower($charset); 136 $text = Horde_String::convertCharset($text, 'UTF-8', $charset); 137 138 /* Get the list of elements in the string. */ 139 $size = preg_match_all('/([^\s]+)([\s]*)/', $text, $matches, PREG_SET_ORDER); 140 141 $line = ''; 142 143 /* Return if nothing needs to be encoded. */ 144 foreach ($matches as $key => $val) { 145 if (self::is8bit($val[1], $charset)) { 146 if ((($key + 1) < $size) && 147 self::is8bit($matches[$key + 1][1], $charset)) { 148 $line .= self::_encode($val[1] . $val[2], $charset) . ' '; 149 } else { 150 $line .= self::_encode($val[1], $charset) . $val[2]; 151 } 152 } else { 153 $line .= $val[1] . $val[2]; 154 } 155 } 156 157 return rtrim($line); 158 } 159 160 /** 161 * Internal helper function to MIME encode a string. 162 * 163 * @param string $text The text to encode. 164 * @param string $charset The character set of the text. 165 * 166 * @return string The MIME encoded text. 167 */ 168 static protected function _encode($text, $charset) 169 { 170 $encoded = trim(base64_encode($text)); 171 $c_size = strlen($charset) + 7; 172 173 if ((strlen($encoded) + $c_size) > 75) { 174 $parts = explode(self::EOL, rtrim(chunk_split($encoded, intval((75 - $c_size) / 4) * 4))); 175 } else { 176 $parts[] = $encoded; 177 } 178 179 $p_size = count($parts); 180 $out = ''; 181 182 foreach ($parts as $key => $val) { 183 $out .= '=?' . $charset . '?b?' . $val . '?='; 184 if ($p_size > $key + 1) { 185 /* RFC 2047 [2]: no encoded word can be more than 75 186 * characters long. If longer, you must split the word with 187 * CRLF SPACE. */ 188 $out .= self::EOL . ' '; 189 } 190 } 191 192 return $out; 193 } 194 195 /** 196 * Encodes a line via quoted-printable encoding. 197 * 198 * @param string $text The text to encode (UTF-8). 199 * @param string $eol The EOL sequence to use. 200 * @param integer $wrap Wrap a line at this many characters. 201 * 202 * @return string The quoted-printable encoded string. 203 */ 204 static public function quotedPrintableEncode($text, $eol = self::EOL, 205 $wrap = 76) 206 { 207 $curr_length = 0; 208 $output = ''; 209 210 /* We need to go character by character through the data. */ 211 for ($i = 0, $length = strlen($text); $i < $length; ++$i) { 212 $char = $text[$i]; 213 214 /* If we have reached the end of the line, reset counters. */ 215 if ($char == "\n") { 216 $output .= $eol; 217 $curr_length = 0; 218 continue; 219 } elseif ($char == "\r") { 220 continue; 221 } 222 223 /* Spaces or tabs at the end of the line are NOT allowed. Also, 224 * ASCII characters below 32 or above 126 AND 61 must be 225 * encoded. */ 226 $ascii = ord($char); 227 if ((($ascii === 32) && 228 ($i + 1 != $length) && 229 (($text[$i + 1] == "\n") || ($text[$i + 1] == "\r"))) || 230 (($ascii < 32) || ($ascii > 126) || ($ascii === 61))) { 231 $char_len = 3; 232 $char = '=' . Horde_String::upper(sprintf('%02s', dechex($ascii))); 233 } else { 234 $char_len = 1; 235 } 236 237 /* Lines must be $wrap characters or less. */ 238 $curr_length += $char_len; 239 if ($curr_length > $wrap) { 240 $output .= '=' . $eol; 241 $curr_length = $char_len; 242 } 243 $output .= $char; 244 } 245 246 return $output; 247 } 248 249 /** 250 * Decodes a MIME encoded (RFC 2047) string. 251 * 252 * @param string $string The MIME encoded text. 253 * 254 * @return string The decoded text. 255 */ 256 static public function decode($string) 257 { 258 /* Take out any spaces between multiple encoded words. */ 259 $string = preg_replace('|\?=\s+=\?|', '?==?', $string); 260 261 $out = ''; 262 $old_pos = 0; 263 264 while (($pos = strpos($string, '=?', $old_pos)) !== false) { 265 /* Save any preceding text. */ 266 $out .= substr($string, $old_pos, $pos - $old_pos); 267 268 /* Search for first delimiting question mark (charset). */ 269 if (($d1 = strpos($string, '?', $pos + 2)) === false) { 270 break; 271 } 272 273 $orig_charset = substr($string, $pos + 2, $d1 - $pos - 2); 274 if (self::$decodeWindows1252 && 275 (Horde_String::lower($orig_charset) == 'iso-8859-1')) { 276 $orig_charset = 'windows-1252'; 277 } 278 279 /* Search for second delimiting question mark (encoding). */ 280 if (($d2 = strpos($string, '?', $d1 + 1)) === false) { 281 break; 282 } 283 284 $encoding = substr($string, $d1 + 1, $d2 - $d1 - 1); 285 286 /* Search for end of encoded data. */ 287 if (($end = strpos($string, '?=', $d2 + 1)) === false) { 288 break; 289 } 290 291 $encoded_text = substr($string, $d2 + 1, $end - $d2 - 1); 292 293 switch ($encoding) { 294 case 'Q': 295 case 'q': 296 $out .= Horde_String::convertCharset( 297 preg_replace_callback( 298 '/=([0-9a-f]{2})/i', 299 function($ord) { 300 return chr(hexdec($ord[1])); 301 }, 302 str_replace('_', ' ', $encoded_text)), 303 $orig_charset, 304 'UTF-8' 305 ); 306 break; 307 308 case 'B': 309 case 'b': 310 $out .= Horde_String::convertCharset( 311 base64_decode($encoded_text), 312 $orig_charset, 313 'UTF-8' 314 ); 315 break; 316 317 default: 318 // Ignore unknown encoding. 319 break; 320 } 321 322 $old_pos = $end + 2; 323 } 324 325 return $out . substr($string, $old_pos); 326 } 327 328 /** 329 * Encodes a MIME parameter string pursuant to RFC 2183 & 2231 330 * (Content-Type and Content-Disposition headers). 331 * 332 * @param string $name The parameter name. 333 * @param string $val The parameter value (UTF-8). 334 * @param array $opts Additional options: 335 * - charset: (string) The charset to encode to. 336 * DEFAULT: UTF-8 337 * - lang: (string) The language to use when encoding. 338 * DEFAULT: None specified 339 * 340 * @return array The encoded parameter string (US-ASCII). 341 */ 342 static public function encodeParam($name, $val, array $opts = array()) 343 { 344 $curr = 0; 345 $encode = $wrap = false; 346 $output = array(); 347 348 $charset = isset($opts['charset']) 349 ? $opts['charset'] 350 : 'UTF-8'; 351 352 // 2 = '=', ';' 353 $pre_len = strlen($name) + 2; 354 355 /* Several possibilities: 356 * - String is ASCII. Output as ASCII (duh). 357 * - Language information has been provided. We MUST encode output 358 * to include this information. 359 * - String is non-ASCII, but can losslessly translate to ASCII. 360 * Output as ASCII (most efficient). 361 * - String is in non-ASCII, but doesn't losslessly translate to 362 * ASCII. MUST encode output (duh). */ 363 if (empty($opts['lang']) && !self::is8bit($val, 'UTF-8')) { 364 $string = $val; 365 } else { 366 $cval = Horde_String::convertCharset($val, 'UTF-8', $charset); 367 $string = Horde_String::lower($charset) . '\'' . (empty($opts['lang']) ? '' : Horde_String::lower($opts['lang'])) . '\'' . rawurlencode($cval); 368 $encode = true; 369 /* Account for trailing '*'. */ 370 ++$pre_len; 371 } 372 373 if (($pre_len + strlen($string)) > 75) { 374 /* Account for continuation '*'. */ 375 ++$pre_len; 376 $wrap = true; 377 378 while ($string) { 379 $chunk = 75 - $pre_len - strlen($curr); 380 $pos = min($chunk, strlen($string) - 1); 381 382 /* Don't split in the middle of an encoded char. */ 383 if (($chunk == $pos) && ($pos > 2)) { 384 for ($i = 0; $i <= 2; ++$i) { 385 if ($string[$pos - $i] == '%') { 386 $pos -= $i + 1; 387 break; 388 } 389 } 390 } 391 392 $lines[] = substr($string, 0, $pos + 1); 393 $string = substr($string, $pos + 1); 394 ++$curr; 395 } 396 } else { 397 $lines = array($string); 398 } 399 400 foreach ($lines as $i => $line) { 401 $output[$name . (($wrap) ? ('*' . $i) : '') . (($encode) ? '*' : '')] = $line; 402 } 403 404 if (self::$brokenRFC2231 && !isset($output[$name])) { 405 $output = array_merge(array( 406 $name => self::encode($val, $charset) 407 ), $output); 408 } 409 410 /* Escape certain characters in params (See RFC 2045 [Appendix A]). 411 * Must be quoted-string if one of these exists. 412 * Forbidden: SPACE, CTLs, ()<>@,;:\"/[]?= */ 413 foreach ($output as $k => $v) { 414 if (preg_match(self::MIME_PARAM_QUOTED, $v)) { 415 $output[$k] = '"' . addcslashes($v, '\\"') . '"'; 416 } 417 } 418 419 return $output; 420 } 421 422 /** 423 * Decodes a MIME parameter string pursuant to RFC 2183 & 2231 424 * (Content-Type and Content-Disposition headers). 425 * 426 * @param string $type Either 'Content-Type' or 'Content-Disposition' 427 * (case-insensitive). 428 * @param mixed $data The text of the header or an array of param name 429 * => param values. 430 * 431 * @return array An array with the following entries (all strings in 432 * UTF-8): 433 * - params: (array) The header's parameter values. 434 * - val: (string) The header's "base" value. 435 */ 436 static public function decodeParam($type, $data) 437 { 438 $convert = array(); 439 $ret = array('params' => array(), 'val' => ''); 440 $splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/'; 441 $type = Horde_String::lower($type); 442 443 if (is_array($data)) { 444 // Use dummy base values 445 $ret['val'] = ($type == 'content-type') 446 ? 'text/plain' 447 : 'attachment'; 448 $params = $data; 449 } else { 450 /* This code was adapted from PEAR's Mail_mimeDecode::. */ 451 if (($pos = strpos($data, ';')) === false) { 452 $ret['val'] = trim($data); 453 return $ret; 454 } 455 456 $ret['val'] = trim(substr($data, 0, $pos)); 457 $data = trim(substr($data, ++$pos)); 458 $params = $tmp = array(); 459 460 if (strlen($data) > 0) { 461 /* This splits on a semi-colon, if there's no preceeding 462 * backslash. */ 463 preg_match_all($splitRegex, $data, $matches); 464 465 for ($i = 0, $cnt = count($matches[0]); $i < $cnt; ++$i) { 466 $param = $matches[0][$i]; 467 while (substr($param, -2) == '\;') { 468 $param .= $matches[0][++$i]; 469 } 470 $tmp[] = $param; 471 } 472 473 for ($i = 0, $cnt = count($tmp); $i < $cnt; ++$i) { 474 $pos = strpos($tmp[$i], '='); 475 $p_name = trim(substr($tmp[$i], 0, $pos), "'\";\t\\ "); 476 $p_val = trim(str_replace('\;', ';', substr($tmp[$i], $pos + 1)), "'\";\t\\ "); 477 if (strlen($p_val) && ($p_val[0] == '"')) { 478 $p_val = substr($p_val, 1, -1); 479 } 480 481 $params[$p_name] = $p_val; 482 } 483 } 484 /* End of code adapted from PEAR's Mail_mimeDecode::. */ 485 } 486 487 /* Sort the params list. Prevents us from having to manually keep 488 * track of continuation values below. */ 489 uksort($params, 'strnatcasecmp'); 490 491 foreach ($params as $name => $val) { 492 /* Asterisk at end indicates encoded value. */ 493 if (substr($name, -1) == '*') { 494 $name = substr($name, 0, -1); 495 $encode = true; 496 } else { 497 $encode = false; 498 } 499 500 /* This asterisk indicates continuation parameter. */ 501 if (($pos = strrpos($name, '*')) !== false) { 502 $name = substr($name, 0, $pos); 503 } 504 505 if (!isset($ret['params'][$name]) || 506 ($encode && !isset($convert[$name]))) { 507 $ret['params'][$name] = ''; 508 } 509 510 $ret['params'][$name] .= $val; 511 512 if ($encode) { 513 $convert[$name] = true; 514 } 515 } 516 517 foreach (array_keys($convert) as $name) { 518 $val = $ret['params'][$name]; 519 $quote = strpos($val, "'"); 520 $orig_charset = substr($val, 0, $quote); 521 if (self::$decodeWindows1252 && 522 (Horde_String::lower($orig_charset) == 'iso-8859-1')) { 523 $orig_charset = 'windows-1252'; 524 } 525 /* Ignore language. */ 526 $quote = strpos($val, "'", $quote + 1); 527 substr($val, $quote + 1); 528 $ret['params'][$name] = Horde_String::convertCharset(urldecode(substr($val, $quote + 1)), $orig_charset, 'UTF-8'); 529 } 530 531 /* MIME parameters are supposed to be encoded via RFC 2231, but many 532 * mailers do RFC 2045 encoding instead. However, if we see at least 533 * one RFC 2231 encoding, then assume the sending mailer knew what 534 * it was doing. */ 535 if (empty($convert)) { 536 foreach (array_diff(array_keys($ret['params']), array_keys($convert)) as $name) { 537 $ret['params'][$name] = self::decode($ret['params'][$name]); 538 } 539 } 540 541 return $ret; 542 } 543 544 /** 545 * Generates a Message-ID string conforming to RFC 2822 [3.6.4] and the 546 * standards outlined in 'draft-ietf-usefor-message-id-01.txt'. 547 * 548 * @param string A message ID string. 549 */ 550 static public function generateMessageId() 551 { 552 return '<' . strval(new Horde_Support_Guid(array('prefix' => 'Horde'))) . '>'; 553 } 554 555 /** 556 * Performs MIME ID "arithmetic" on a given ID. 557 * 558 * @param string $id The MIME ID string. 559 * @param string $action One of the following: 560 * - down: ID of child. Note: down will first traverse to "$id.0" if 561 * given an ID *NOT* of the form "$id.0". If given an ID of the 562 * form "$id.0", down will traverse to "$id.1". This behavior 563 * can be avoided if 'norfc822' option is set. 564 * - next: ID of next sibling. 565 * - prev: ID of previous sibling. 566 * - up: ID of parent. Note: up will first traverse to "$id.0" if 567 * given an ID *NOT* of the form "$id.0". If given an ID of the 568 * form "$id.0", down will traverse to "$id". This behavior can be 569 * avoided if 'norfc822' option is set. 570 * @param array $options Additional options: 571 * - count: (integer) How many levels to traverse. 572 * DEFAULT: 1 573 * - norfc822: (boolean) Don't traverse rfc822 sub-levels 574 * DEFAULT: false 575 * 576 * @return mixed The resulting ID string, or null if that ID can not 577 * exist. 578 */ 579 static public function mimeIdArithmetic($id, $action, $options = array()) 580 { 581 $pos = strrpos($id, '.'); 582 $end = ($pos === false) ? $id : substr($id, $pos + 1); 583 584 switch ($action) { 585 case 'down': 586 if ($end == '0') { 587 $id = ($pos === false) ? 1 : substr_replace($id, '1', $pos + 1); 588 } else { 589 $id .= empty($options['norfc822']) ? '.0' : '.1'; 590 } 591 break; 592 593 case 'next': 594 ++$end; 595 $id = ($pos === false) ? $end : substr_replace($id, $end, $pos + 1); 596 break; 597 598 case 'prev': 599 if (($end == '0') || 600 (empty($options['norfc822']) && ($end == '1'))) { 601 $id = null; 602 } elseif ($pos === false) { 603 $id = --$end; 604 } else { 605 $id = substr_replace($id, --$end, $pos + 1); 606 } 607 break; 608 609 case 'up': 610 if ($pos === false) { 611 $id = ($end == '0') ? null : '0'; 612 } elseif (!empty($options['norfc822']) || ($end == '0')) { 613 $id = substr($id, 0, $pos); 614 } else { 615 $id = substr_replace($id, '0', $pos + 1); 616 } 617 break; 618 } 619 620 return (!is_null($id) && !empty($options['count']) && --$options['count']) 621 ? self::mimeIdArithmetic($id, $action, $options) 622 : $id; 623 } 624 625 /** 626 * Determines if a given MIME ID lives underneath a base ID. 627 * 628 * @param string $base The base MIME ID. 629 * @param string $id The MIME ID to query. 630 * 631 * @return boolean Whether $id lives underneath $base. 632 */ 633 static public function isChild($base, $id) 634 { 635 $base = (substr($base, -2) == '.0') 636 ? substr($base, 0, -1) 637 : rtrim($base, '.') . '.'; 638 639 return ((($base == 0) && ($id != 0)) || 640 (strpos(strval($id), strval($base)) === 0)); 641 } 642 643 /** 644 * Scans $input for uuencoded data and converts it to unencoded data. 645 * 646 * @param string $input The input data 647 * 648 * @return array A list of arrays, with each array corresponding to 649 * a file in the input and containing the following keys: 650 * - data: (string) Unencoded data. 651 * - name: (string) Filename. 652 * - perms: (string) Octal permissions. 653 */ 654 static public function uudecode($input) 655 { 656 $data = array(); 657 658 /* Find all uuencoded sections. */ 659 if (preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches, PREG_SET_ORDER)) { 660 reset($matches); 661 while (list(,$v) = each($matches)) { 662 $data[] = array( 663 'data' => self::_uudecode($v[3]), 664 'name' => $v[2], 665 'perm' => $v[1] 666 ); 667 } 668 } 669 670 return $data; 671 } 672 673 /** 674 * PHP 5's built-in convert_uudecode() is broken. Need this wrapper. 675 * 676 * @param string $input UUencoded input. 677 * 678 * @return string Decoded string. 679 */ 680 static protected function _uudecode($input) 681 { 682 $decoded = ''; 683 684 foreach (explode("\n", $input) as $line) { 685 $c = count($bytes = unpack('c*', substr(trim($line,"\r\n\t"), 1))); 686 687 while ($c % 4) { 688 $bytes[++$c] = 0; 689 } 690 691 foreach (array_chunk($bytes, 4) as $b) { 692 $b0 = ($b[0] == 0x60) ? 0 : $b[0] - 0x20; 693 $b1 = ($b[1] == 0x60) ? 0 : $b[1] - 0x20; 694 $b2 = ($b[2] == 0x60) ? 0 : $b[2] - 0x20; 695 $b3 = ($b[3] == 0x60) ? 0 : $b[3] - 0x20; 696 697 $b0 <<= 2; 698 $b0 |= ($b1 >> 4) & 0x03; 699 $b1 <<= 4; 700 $b1 |= ($b2 >> 2) & 0x0F; 701 $b2 <<= 6; 702 $b2 |= $b3 & 0x3F; 703 704 $decoded .= pack('c*', $b0, $b1, $b2); 705 } 706 } 707 708 return rtrim($decoded, "\0"); 709 } 710 711 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |