[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/horde/framework/Horde/ -> Mime.php (source)

   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  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1