Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   1  <?php
   2  /**
   3   * Originally based on code:
   4   *
   5   *  Copyright (C) 2000 Edmund Grimley Evans <edmundo@rano.org>
   6   *  Released under the GPL (version 2)
   7   *
   8   *  Translated from C to PHP by Thomas Bruederli <roundcube@gmail.com>
   9   *  Code extracted from the RoundCube Webmail (http://roundcube.net) project,
  10   *    SVN revision 1757
  11   *  The RoundCube project is released under the GPL (version 2)
  12   *
  13   * Copyright 2008-2017 Horde LLC (http://www.horde.org/)
  14   *
  15   * See the enclosed file LICENSE for license information (LGPL). If you
  16   * did not receive this file, see http://www.horde.org/licenses/lgpl21.
  17   *
  18   * @category  Horde
  19   * @copyright 2000 Edmund Grimley Evans <edmundo@rano.org>
  20   * @copyright 2008-2017 Horde LLC
  21   * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
  22   * @package   Imap_Client
  23   */
  24  
  25  /**
  26   * Allows conversions between UTF-8 and UTF7-IMAP (RFC 3501 [5.1.3]).
  27   *
  28   * @author    Michael Slusarz <slusarz@horde.org>
  29   * @category  Horde
  30   * @copyright 2000 Edmund Grimley Evans <edmundo@rano.org>
  31   * @copyright 2008-2017 Horde LLC
  32   * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
  33   * @package   Imap_Client
  34   */
  35  class Horde_Imap_Client_Utf7imap
  36  {
  37      /**
  38       * Lookup table for conversion.
  39       *
  40       * @var array
  41       */
  42      private static $_index64 = array(
  43          -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  44          -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  45          -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, 63, -1, -1, -1,
  46          52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
  47          -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
  48          15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
  49          -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
  50          41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
  51      );
  52  
  53      /**
  54       * Lookup table for conversion.
  55       *
  56       * @var array
  57       */
  58      private static $_base64 = array(
  59          'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
  60          'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
  61          'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
  62          'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3',
  63          '4', '5', '6', '7', '8', '9', '+', ','
  64      );
  65  
  66      /**
  67       * Is mbstring extension available?
  68       *
  69       * @var array
  70       */
  71      protected static $_mbstring = null;
  72  
  73      /**
  74       * Convert a string from UTF7-IMAP to UTF-8.
  75       *
  76       * @param string $str  The UTF7-IMAP string.
  77       *
  78       * @return string  The converted UTF-8 string.
  79       * @throws Horde_Imap_Client_Exception
  80       */
  81      public static function Utf7ImapToUtf8($str)
  82      {
  83          if ($str instanceof Horde_Imap_Client_Mailbox) {
  84              return $str->utf8;
  85          }
  86  
  87          $str = strval($str);
  88  
  89          /* Try mbstring, if available, which should be faster. Don't use the
  90           * IMAP utf7_* functions because they are known to be buggy. */
  91          if (is_null(self::$_mbstring)) {
  92              self::$_mbstring = extension_loaded('mbstring');
  93          }
  94          if (self::$_mbstring) {
  95              return @mb_convert_encoding($str, 'UTF-8', 'UTF7-IMAP');
  96          }
  97  
  98          $p = '';
  99          $ptr = &self::$_index64;
 100  
 101          for ($i = 0, $u7len = strlen($str); $u7len > 0; ++$i, --$u7len) {
 102              $u7 = $str[$i];
 103              if ($u7 === '&') {
 104                  $u7 = $str[++$i];
 105                  if (--$u7len && ($u7 === '-')) {
 106                      $p .= '&';
 107                      continue;
 108                  }
 109  
 110                  $ch = 0;
 111                  $k = 10;
 112                  for (; $u7len > 0; ++$i, --$u7len) {
 113                      $u7 = $str[$i];
 114  
 115                      if ((ord($u7) & 0x80) || ($b = $ptr[ord($u7)]) === -1) {
 116                          break;
 117                      }
 118  
 119                      if ($k > 0) {
 120                          $ch |= $b << $k;
 121                          $k -= 6;
 122                      } else {
 123                          $ch |= $b >> (-$k);
 124                          if ($ch < 0x80) {
 125                              /* Printable US-ASCII */
 126                              if ((0x20 <= $ch) && ($ch < 0x7f)) {
 127                                  throw new Horde_Imap_Client_Exception(
 128                                      Horde_Imap_Client_Translation::r("Error converting UTF7-IMAP string."),
 129                                      Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION
 130                                  );
 131                              }
 132                              $p .= chr($ch);
 133                          } else if ($ch < 0x800) {
 134                              $p .= chr(0xc0 | ($ch >> 6)) .
 135                                    chr(0x80 | ($ch & 0x3f));
 136                          } else {
 137                              $p .= chr(0xe0 | ($ch >> 12)) .
 138                                    chr(0x80 | (($ch >> 6) & 0x3f)) .
 139                                    chr(0x80 | ($ch & 0x3f));
 140                          }
 141  
 142                          $ch = ($b << (16 + $k)) & 0xffff;
 143                          $k += 10;
 144                      }
 145                  }
 146  
 147                  /* Non-zero or too many extra bits -OR-
 148                   * Base64 not properly terminated -OR-
 149                   * Adjacent Base64 sections. */
 150                  if (($ch || ($k < 6)) ||
 151                      (!$u7len || $u7 !== '-') ||
 152                      (($u7len > 2) &&
 153                       ($str[$i + 1] === '&') &&
 154                       ($str[$i + 2] !== '-'))) {
 155                      throw new Horde_Imap_Client_Exception(
 156                          Horde_Imap_Client_Translation::r("Error converting UTF7-IMAP string."),
 157                          Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION
 158                      );
 159                  }
 160              } elseif ((ord($u7) < 0x20) || (ord($u7) >= 0x7f)) {
 161                  /* Not printable US-ASCII */
 162                  throw new Horde_Imap_Client_Exception(
 163                      Horde_Imap_Client_Translation::r("Error converting UTF7-IMAP string."),
 164                      Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION
 165                  );
 166              } else {
 167                  $p .= $u7;
 168              }
 169          }
 170  
 171          return $p;
 172      }
 173  
 174      /**
 175       * Convert a string from UTF-8 to UTF7-IMAP.
 176       *
 177       * @param string $str     The UTF-8 string.
 178       * @param boolean $force  Assume $str is UTF-8 (no-autodetection)? If
 179       *                        false, attempts to auto-detect if string is
 180       *                        already in UTF7-IMAP.
 181       *
 182       * @return string  The converted UTF7-IMAP string.
 183       * @throws Horde_Imap_Client_Exception
 184       */
 185      public static function Utf8ToUtf7Imap($str, $force = true)
 186      {
 187          if ($str instanceof Horde_Imap_Client_Mailbox) {
 188              return $str->utf7imap;
 189          }
 190  
 191          $str = strval($str);
 192  
 193          /* No need to do conversion if all chars are in US-ASCII range or if
 194           * no ampersand is present. But will assume that an already encoded
 195           * ampersand means string is in UTF7-IMAP already. */
 196          if (!$force &&
 197              !preg_match('/[\x80-\xff]|&$|&(?![,+A-Za-z0-9]*-)/', $str)) {
 198              return $str;
 199          }
 200  
 201          /* Try mbstring, if available, which should be faster. Don't use the
 202           * IMAP utf7_* functions because they are known to be buggy. */
 203          if (is_null(self::$_mbstring)) {
 204              self::$_mbstring = extension_loaded('mbstring');
 205          }
 206          if (self::$_mbstring) {
 207              return @mb_convert_encoding($str, 'UTF7-IMAP', 'UTF-8');
 208          }
 209  
 210          $u8len = strlen($str);
 211          $i = 0;
 212          $base64 = false;
 213          $p = '';
 214          $ptr = &self::$_base64;
 215  
 216          while ($u8len) {
 217              $u8 = $str[$i];
 218              $c = ord($u8);
 219  
 220              if ($c < 0x80) {
 221                  $ch = $c;
 222                  $n = 0;
 223              } elseif ($c < 0xc2) {
 224                  throw new Horde_Imap_Client_Exception(
 225                      Horde_Imap_Client_Translation::r("Error converting UTF7-IMAP string."),
 226                      Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION
 227                  );
 228              } elseif ($c < 0xe0) {
 229                  $ch = $c & 0x1f;
 230                  $n = 1;
 231              } elseif ($c < 0xf0) {
 232                  $ch = $c & 0x0f;
 233                  $n = 2;
 234              } elseif ($c < 0xf8) {
 235                  $ch = $c & 0x07;
 236                  $n = 3;
 237              } elseif ($c < 0xfc) {
 238                  $ch = $c & 0x03;
 239                  $n = 4;
 240              } elseif ($c < 0xfe) {
 241                  $ch = $c & 0x01;
 242                  $n = 5;
 243              } else {
 244                  throw new Horde_Imap_Client_Exception(
 245                      Horde_Imap_Client_Translation::r("Error converting UTF7-IMAP string."),
 246                      Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION
 247                  );
 248              }
 249  
 250              if ($n > --$u8len) {
 251                  throw new Horde_Imap_Client_Exception(
 252                      Horde_Imap_Client_Translation::r("Error converting UTF7-IMAP string."),
 253                      Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION
 254                  );
 255              }
 256  
 257              ++$i;
 258  
 259              for ($j = 0; $j < $n; ++$j) {
 260                  $o = ord($str[$i + $j]);
 261                  if (($o & 0xc0) !== 0x80) {
 262                      throw new Horde_Imap_Client_Exception(
 263                          Horde_Imap_Client_Translation::r("Error converting UTF7-IMAP string."),
 264                          Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION
 265                      );
 266                  }
 267                  $ch = ($ch << 6) | ($o & 0x3f);
 268              }
 269  
 270              if (($n > 1) && !($ch >> ($n * 5 + 1))) {
 271                  throw new Horde_Imap_Client_Exception(
 272                      Horde_Imap_Client_Translation::r("Error converting UTF7-IMAP string."),
 273                      Horde_Imap_Client_Exception::UTF7IMAP_CONVERSION
 274                  );
 275              }
 276  
 277              $i += $n;
 278              $u8len -= $n;
 279  
 280              if (($ch < 0x20) || ($ch >= 0x7f)) {
 281                  if (!$base64) {
 282                      $p .= '&';
 283                      $base64 = true;
 284                      $b = 0;
 285                      $k = 10;
 286                  }
 287  
 288                  if ($ch & ~0xffff) {
 289                      $ch = 0xfffe;
 290                  }
 291  
 292                  $p .= $ptr[($b | $ch >> $k)];
 293                  $k -= 6;
 294                  for (; $k >= 0; $k -= 6) {
 295                      $p .= $ptr[(($ch >> $k) & 0x3f)];
 296                  }
 297  
 298                  $b = ($ch << (-$k)) & 0x3f;
 299                  $k += 16;
 300              } else {
 301                  if ($base64) {
 302                      if ($k > 10) {
 303                          $p .= $ptr[$b];
 304                      }
 305                      $p .= '-';
 306                      $base64 = false;
 307                  }
 308  
 309                  $p .= chr($ch);
 310                  if (chr($ch) === '&') {
 311                      $p .= '-';
 312                  }
 313              }
 314          }
 315  
 316          if ($base64) {
 317              if ($k > 10) {
 318                  $p .= $ptr[$b];
 319              }
 320              $p .= '-';
 321          }
 322  
 323          return $p;
 324      }
 325  
 326  }