1927933f5SAndreas Gohr<?php 2927933f5SAndreas Gohrdeclare(strict_types=1); 3927933f5SAndreas Gohrnamespace ParagonIE\ConstantTime; 4927933f5SAndreas Gohr 5927933f5SAndreas Gohruse InvalidArgumentException; 6*8e88a29bSAndreas Gohruse Override; 7927933f5SAndreas Gohruse RangeException; 8*8e88a29bSAndreas Gohruse SensitiveParameter; 9*8e88a29bSAndreas Gohruse SodiumException; 10927933f5SAndreas Gohruse TypeError; 11*8e88a29bSAndreas Gohruse function extension_loaded; 12*8e88a29bSAndreas Gohruse function pack; 13*8e88a29bSAndreas Gohruse function rtrim; 14*8e88a29bSAndreas Gohruse function sodium_base642bin; 15*8e88a29bSAndreas Gohruse function sodium_bin2base64; 16*8e88a29bSAndreas Gohruse function strlen; 17*8e88a29bSAndreas Gohruse function substr; 18*8e88a29bSAndreas Gohruse function unpack; 19*8e88a29bSAndreas Gohruse const SODIUM_BASE64_VARIANT_ORIGINAL; 20*8e88a29bSAndreas Gohruse const SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING; 21*8e88a29bSAndreas Gohruse const SODIUM_BASE64_VARIANT_URLSAFE; 22*8e88a29bSAndreas Gohruse const SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING; 23927933f5SAndreas Gohr 24927933f5SAndreas Gohr/** 25927933f5SAndreas Gohr * Copyright (c) 2016 - 2022 Paragon Initiative Enterprises. 26927933f5SAndreas Gohr * Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com) 27927933f5SAndreas Gohr * 28927933f5SAndreas Gohr * Permission is hereby granted, free of charge, to any person obtaining a copy 29927933f5SAndreas Gohr * of this software and associated documentation files (the "Software"), to deal 30927933f5SAndreas Gohr * in the Software without restriction, including without limitation the rights 31927933f5SAndreas Gohr * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 32927933f5SAndreas Gohr * copies of the Software, and to permit persons to whom the Software is 33927933f5SAndreas Gohr * furnished to do so, subject to the following conditions: 34927933f5SAndreas Gohr * 35927933f5SAndreas Gohr * The above copyright notice and this permission notice shall be included in all 36927933f5SAndreas Gohr * copies or substantial portions of the Software. 37927933f5SAndreas Gohr * 38927933f5SAndreas Gohr * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 39927933f5SAndreas Gohr * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 40927933f5SAndreas Gohr * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 41927933f5SAndreas Gohr * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 42927933f5SAndreas Gohr * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 43927933f5SAndreas Gohr * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 44927933f5SAndreas Gohr * SOFTWARE. 45927933f5SAndreas Gohr */ 46927933f5SAndreas Gohr 47927933f5SAndreas Gohr/** 48927933f5SAndreas Gohr * Class Base64 49927933f5SAndreas Gohr * [A-Z][a-z][0-9]+/ 50927933f5SAndreas Gohr * 51927933f5SAndreas Gohr * @package ParagonIE\ConstantTime 52927933f5SAndreas Gohr */ 53927933f5SAndreas Gohrabstract class Base64 implements EncoderInterface 54927933f5SAndreas Gohr{ 55927933f5SAndreas Gohr /** 56927933f5SAndreas Gohr * Encode into Base64 57927933f5SAndreas Gohr * 58927933f5SAndreas Gohr * Base64 character set "[A-Z][a-z][0-9]+/" 59927933f5SAndreas Gohr * 60927933f5SAndreas Gohr * @param string $binString 61927933f5SAndreas Gohr * @return string 62927933f5SAndreas Gohr * 63927933f5SAndreas Gohr * @throws TypeError 64927933f5SAndreas Gohr */ 65*8e88a29bSAndreas Gohr #[Override] 66850e6620SAndreas Gohr public static function encode( 67*8e88a29bSAndreas Gohr #[SensitiveParameter] 68850e6620SAndreas Gohr string $binString 69850e6620SAndreas Gohr ): string { 70*8e88a29bSAndreas Gohr if (extension_loaded('sodium')) { 71*8e88a29bSAndreas Gohr $variant = match(static::class) { 72*8e88a29bSAndreas Gohr Base64::class => SODIUM_BASE64_VARIANT_ORIGINAL, 73*8e88a29bSAndreas Gohr Base64UrlSafe::class => SODIUM_BASE64_VARIANT_URLSAFE, 74*8e88a29bSAndreas Gohr default => 0, 75*8e88a29bSAndreas Gohr }; 76*8e88a29bSAndreas Gohr if ($variant > 0) { 77*8e88a29bSAndreas Gohr try { 78*8e88a29bSAndreas Gohr return sodium_bin2base64($binString, $variant); 79*8e88a29bSAndreas Gohr } catch (SodiumException $ex) { 80*8e88a29bSAndreas Gohr throw new RangeException($ex->getMessage(), $ex->getCode(), $ex); 81*8e88a29bSAndreas Gohr } 82*8e88a29bSAndreas Gohr } 83*8e88a29bSAndreas Gohr } 84927933f5SAndreas Gohr return static::doEncode($binString, true); 85927933f5SAndreas Gohr } 86927933f5SAndreas Gohr 87927933f5SAndreas Gohr /** 88927933f5SAndreas Gohr * Encode into Base64, no = padding 89927933f5SAndreas Gohr * 90927933f5SAndreas Gohr * Base64 character set "[A-Z][a-z][0-9]+/" 91927933f5SAndreas Gohr * 92927933f5SAndreas Gohr * @param string $src 93927933f5SAndreas Gohr * @return string 94927933f5SAndreas Gohr * 95927933f5SAndreas Gohr * @throws TypeError 96*8e88a29bSAndreas Gohr * @api 97927933f5SAndreas Gohr */ 98850e6620SAndreas Gohr public static function encodeUnpadded( 99*8e88a29bSAndreas Gohr #[SensitiveParameter] 100850e6620SAndreas Gohr string $src 101850e6620SAndreas Gohr ): string { 102*8e88a29bSAndreas Gohr if (extension_loaded('sodium')) { 103*8e88a29bSAndreas Gohr $variant = match(static::class) { 104*8e88a29bSAndreas Gohr Base64::class => SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING, 105*8e88a29bSAndreas Gohr Base64UrlSafe::class => SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, 106*8e88a29bSAndreas Gohr default => 0, 107*8e88a29bSAndreas Gohr }; 108*8e88a29bSAndreas Gohr if ($variant > 0) { 109*8e88a29bSAndreas Gohr try { 110*8e88a29bSAndreas Gohr return sodium_bin2base64($src, $variant); 111*8e88a29bSAndreas Gohr } catch (SodiumException $ex) { 112*8e88a29bSAndreas Gohr throw new RangeException($ex->getMessage(), $ex->getCode(), $ex); 113*8e88a29bSAndreas Gohr } 114*8e88a29bSAndreas Gohr } 115*8e88a29bSAndreas Gohr } 116927933f5SAndreas Gohr return static::doEncode($src, false); 117927933f5SAndreas Gohr } 118927933f5SAndreas Gohr 119927933f5SAndreas Gohr /** 120927933f5SAndreas Gohr * @param string $src 121927933f5SAndreas Gohr * @param bool $pad Include = padding? 122927933f5SAndreas Gohr * @return string 123927933f5SAndreas Gohr * 124927933f5SAndreas Gohr * @throws TypeError 125927933f5SAndreas Gohr */ 126850e6620SAndreas Gohr protected static function doEncode( 127*8e88a29bSAndreas Gohr #[SensitiveParameter] 128850e6620SAndreas Gohr string $src, 129850e6620SAndreas Gohr bool $pad = true 130850e6620SAndreas Gohr ): string { 131927933f5SAndreas Gohr $dest = ''; 132*8e88a29bSAndreas Gohr $srcLen = strlen($src); 133927933f5SAndreas Gohr // Main loop (no padding): 134927933f5SAndreas Gohr for ($i = 0; $i + 3 <= $srcLen; $i += 3) { 135927933f5SAndreas Gohr /** @var array<int, int> $chunk */ 136*8e88a29bSAndreas Gohr $chunk = unpack('C*', substr($src, $i, 3)); 137927933f5SAndreas Gohr $b0 = $chunk[1]; 138927933f5SAndreas Gohr $b1 = $chunk[2]; 139927933f5SAndreas Gohr $b2 = $chunk[3]; 140927933f5SAndreas Gohr 141927933f5SAndreas Gohr $dest .= 142927933f5SAndreas Gohr static::encode6Bits( $b0 >> 2 ) . 143927933f5SAndreas Gohr static::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . 144927933f5SAndreas Gohr static::encode6Bits((($b1 << 2) | ($b2 >> 6)) & 63) . 145927933f5SAndreas Gohr static::encode6Bits( $b2 & 63); 146927933f5SAndreas Gohr } 147927933f5SAndreas Gohr // The last chunk, which may have padding: 148927933f5SAndreas Gohr if ($i < $srcLen) { 149927933f5SAndreas Gohr /** @var array<int, int> $chunk */ 150*8e88a29bSAndreas Gohr $chunk = unpack('C*', substr($src, $i, $srcLen - $i)); 151927933f5SAndreas Gohr $b0 = $chunk[1]; 152927933f5SAndreas Gohr if ($i + 1 < $srcLen) { 153927933f5SAndreas Gohr $b1 = $chunk[2]; 154927933f5SAndreas Gohr $dest .= 155927933f5SAndreas Gohr static::encode6Bits($b0 >> 2) . 156927933f5SAndreas Gohr static::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . 157927933f5SAndreas Gohr static::encode6Bits(($b1 << 2) & 63); 158927933f5SAndreas Gohr if ($pad) { 159927933f5SAndreas Gohr $dest .= '='; 160927933f5SAndreas Gohr } 161927933f5SAndreas Gohr } else { 162927933f5SAndreas Gohr $dest .= 163927933f5SAndreas Gohr static::encode6Bits( $b0 >> 2) . 164927933f5SAndreas Gohr static::encode6Bits(($b0 << 4) & 63); 165927933f5SAndreas Gohr if ($pad) { 166927933f5SAndreas Gohr $dest .= '=='; 167927933f5SAndreas Gohr } 168927933f5SAndreas Gohr } 169927933f5SAndreas Gohr } 170927933f5SAndreas Gohr return $dest; 171927933f5SAndreas Gohr } 172927933f5SAndreas Gohr 173927933f5SAndreas Gohr /** 174927933f5SAndreas Gohr * decode from base64 into binary 175927933f5SAndreas Gohr * 176927933f5SAndreas Gohr * Base64 character set "./[A-Z][a-z][0-9]" 177927933f5SAndreas Gohr * 178927933f5SAndreas Gohr * @param string $encodedString 179927933f5SAndreas Gohr * @param bool $strictPadding 180927933f5SAndreas Gohr * @return string 181927933f5SAndreas Gohr * 182927933f5SAndreas Gohr * @throws RangeException 183927933f5SAndreas Gohr * @throws TypeError 184927933f5SAndreas Gohr */ 185*8e88a29bSAndreas Gohr #[Override] 186850e6620SAndreas Gohr public static function decode( 187*8e88a29bSAndreas Gohr #[SensitiveParameter] 188850e6620SAndreas Gohr string $encodedString, 189850e6620SAndreas Gohr bool $strictPadding = false 190850e6620SAndreas Gohr ): string { 191927933f5SAndreas Gohr // Remove padding 192*8e88a29bSAndreas Gohr $srcLen = strlen($encodedString); 193927933f5SAndreas Gohr if ($srcLen === 0) { 194927933f5SAndreas Gohr return ''; 195927933f5SAndreas Gohr } 196927933f5SAndreas Gohr 197927933f5SAndreas Gohr if ($strictPadding) { 198927933f5SAndreas Gohr if (($srcLen & 3) === 0) { 199927933f5SAndreas Gohr if ($encodedString[$srcLen - 1] === '=') { 200927933f5SAndreas Gohr $srcLen--; 201927933f5SAndreas Gohr if ($encodedString[$srcLen - 1] === '=') { 202927933f5SAndreas Gohr $srcLen--; 203927933f5SAndreas Gohr } 204927933f5SAndreas Gohr } 205927933f5SAndreas Gohr } 206927933f5SAndreas Gohr if (($srcLen & 3) === 1) { 207927933f5SAndreas Gohr throw new RangeException( 208927933f5SAndreas Gohr 'Incorrect padding' 209927933f5SAndreas Gohr ); 210927933f5SAndreas Gohr } 211927933f5SAndreas Gohr if ($encodedString[$srcLen - 1] === '=') { 212927933f5SAndreas Gohr throw new RangeException( 213927933f5SAndreas Gohr 'Incorrect padding' 214927933f5SAndreas Gohr ); 215927933f5SAndreas Gohr } 216*8e88a29bSAndreas Gohr if (extension_loaded('sodium')) { 217*8e88a29bSAndreas Gohr $variant = match(static::class) { 218*8e88a29bSAndreas Gohr Base64::class => SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING, 219*8e88a29bSAndreas Gohr Base64UrlSafe::class => SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, 220*8e88a29bSAndreas Gohr default => 0, 221*8e88a29bSAndreas Gohr }; 222*8e88a29bSAndreas Gohr if ($variant > 0) { 223*8e88a29bSAndreas Gohr try { 224*8e88a29bSAndreas Gohr return sodium_base642bin(substr($encodedString, 0, $srcLen), $variant); 225*8e88a29bSAndreas Gohr } catch (SodiumException $ex) { 226*8e88a29bSAndreas Gohr throw new RangeException($ex->getMessage(), $ex->getCode(), $ex); 227*8e88a29bSAndreas Gohr } 228*8e88a29bSAndreas Gohr } 229*8e88a29bSAndreas Gohr } 230927933f5SAndreas Gohr } else { 231*8e88a29bSAndreas Gohr // Just remove all padding. 232*8e88a29bSAndreas Gohr $encodedString = rtrim($encodedString, '='); 233*8e88a29bSAndreas Gohr $srcLen = strlen($encodedString); 234927933f5SAndreas Gohr } 235927933f5SAndreas Gohr 236927933f5SAndreas Gohr $err = 0; 237927933f5SAndreas Gohr $dest = ''; 238927933f5SAndreas Gohr // Main loop (no padding): 239927933f5SAndreas Gohr for ($i = 0; $i + 4 <= $srcLen; $i += 4) { 240927933f5SAndreas Gohr /** @var array<int, int> $chunk */ 241*8e88a29bSAndreas Gohr $chunk = unpack('C*', substr($encodedString, $i, 4)); 242927933f5SAndreas Gohr $c0 = static::decode6Bits($chunk[1]); 243927933f5SAndreas Gohr $c1 = static::decode6Bits($chunk[2]); 244927933f5SAndreas Gohr $c2 = static::decode6Bits($chunk[3]); 245927933f5SAndreas Gohr $c3 = static::decode6Bits($chunk[4]); 246927933f5SAndreas Gohr 247*8e88a29bSAndreas Gohr $dest .= pack( 248927933f5SAndreas Gohr 'CCC', 249927933f5SAndreas Gohr ((($c0 << 2) | ($c1 >> 4)) & 0xff), 250927933f5SAndreas Gohr ((($c1 << 4) | ($c2 >> 2)) & 0xff), 251927933f5SAndreas Gohr ((($c2 << 6) | $c3 ) & 0xff) 252927933f5SAndreas Gohr ); 253927933f5SAndreas Gohr $err |= ($c0 | $c1 | $c2 | $c3) >> 8; 254927933f5SAndreas Gohr } 255927933f5SAndreas Gohr // The last chunk, which may have padding: 256927933f5SAndreas Gohr if ($i < $srcLen) { 257927933f5SAndreas Gohr /** @var array<int, int> $chunk */ 258*8e88a29bSAndreas Gohr $chunk = unpack('C*', substr($encodedString, $i, $srcLen - $i)); 259927933f5SAndreas Gohr $c0 = static::decode6Bits($chunk[1]); 260927933f5SAndreas Gohr 261927933f5SAndreas Gohr if ($i + 2 < $srcLen) { 262927933f5SAndreas Gohr $c1 = static::decode6Bits($chunk[2]); 263927933f5SAndreas Gohr $c2 = static::decode6Bits($chunk[3]); 264*8e88a29bSAndreas Gohr $dest .= pack( 265927933f5SAndreas Gohr 'CC', 266927933f5SAndreas Gohr ((($c0 << 2) | ($c1 >> 4)) & 0xff), 267927933f5SAndreas Gohr ((($c1 << 4) | ($c2 >> 2)) & 0xff) 268927933f5SAndreas Gohr ); 269927933f5SAndreas Gohr $err |= ($c0 | $c1 | $c2) >> 8; 270927933f5SAndreas Gohr if ($strictPadding) { 271927933f5SAndreas Gohr $err |= ($c2 << 6) & 0xff; 272927933f5SAndreas Gohr } 273927933f5SAndreas Gohr } elseif ($i + 1 < $srcLen) { 274927933f5SAndreas Gohr $c1 = static::decode6Bits($chunk[2]); 275*8e88a29bSAndreas Gohr $dest .= pack( 276927933f5SAndreas Gohr 'C', 277927933f5SAndreas Gohr ((($c0 << 2) | ($c1 >> 4)) & 0xff) 278927933f5SAndreas Gohr ); 279927933f5SAndreas Gohr $err |= ($c0 | $c1) >> 8; 280927933f5SAndreas Gohr if ($strictPadding) { 281927933f5SAndreas Gohr $err |= ($c1 << 4) & 0xff; 282927933f5SAndreas Gohr } 283927933f5SAndreas Gohr } elseif ($strictPadding) { 284927933f5SAndreas Gohr $err |= 1; 285927933f5SAndreas Gohr } 286927933f5SAndreas Gohr } 287927933f5SAndreas Gohr $check = ($err === 0); 288927933f5SAndreas Gohr if (!$check) { 289927933f5SAndreas Gohr throw new RangeException( 290927933f5SAndreas Gohr 'Base64::decode() only expects characters in the correct base64 alphabet' 291927933f5SAndreas Gohr ); 292927933f5SAndreas Gohr } 293927933f5SAndreas Gohr return $dest; 294927933f5SAndreas Gohr } 295927933f5SAndreas Gohr 296927933f5SAndreas Gohr /** 297927933f5SAndreas Gohr * @param string $encodedString 298927933f5SAndreas Gohr * @return string 299*8e88a29bSAndreas Gohr * @api 300927933f5SAndreas Gohr */ 301850e6620SAndreas Gohr public static function decodeNoPadding( 302*8e88a29bSAndreas Gohr #[SensitiveParameter] 303850e6620SAndreas Gohr string $encodedString 304850e6620SAndreas Gohr ): string { 305*8e88a29bSAndreas Gohr $srcLen = strlen($encodedString); 306927933f5SAndreas Gohr if ($srcLen === 0) { 307927933f5SAndreas Gohr return ''; 308927933f5SAndreas Gohr } 309927933f5SAndreas Gohr if (($srcLen & 3) === 0) { 310850e6620SAndreas Gohr // If $strLen is not zero, and it is divisible by 4, then it's at least 4. 311850e6620SAndreas Gohr if ($encodedString[$srcLen - 1] === '=' || $encodedString[$srcLen - 2] === '=') { 312927933f5SAndreas Gohr throw new InvalidArgumentException( 313927933f5SAndreas Gohr "decodeNoPadding() doesn't tolerate padding" 314927933f5SAndreas Gohr ); 315927933f5SAndreas Gohr } 316927933f5SAndreas Gohr } 317927933f5SAndreas Gohr return static::decode( 318927933f5SAndreas Gohr $encodedString, 319927933f5SAndreas Gohr true 320927933f5SAndreas Gohr ); 321927933f5SAndreas Gohr } 322927933f5SAndreas Gohr 323927933f5SAndreas Gohr /** 324927933f5SAndreas Gohr * Uses bitwise operators instead of table-lookups to turn 6-bit integers 325927933f5SAndreas Gohr * into 8-bit integers. 326927933f5SAndreas Gohr * 327927933f5SAndreas Gohr * Base64 character set: 328927933f5SAndreas Gohr * [A-Z] [a-z] [0-9] + / 329927933f5SAndreas Gohr * 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f 330927933f5SAndreas Gohr * 331927933f5SAndreas Gohr * @param int $src 332927933f5SAndreas Gohr * @return int 333927933f5SAndreas Gohr */ 334927933f5SAndreas Gohr protected static function decode6Bits(int $src): int 335927933f5SAndreas Gohr { 336927933f5SAndreas Gohr $ret = -1; 337927933f5SAndreas Gohr 338927933f5SAndreas Gohr // if ($src > 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64 339927933f5SAndreas Gohr $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64); 340927933f5SAndreas Gohr 341927933f5SAndreas Gohr // if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70 342927933f5SAndreas Gohr $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 70); 343927933f5SAndreas Gohr 344927933f5SAndreas Gohr // if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5 345927933f5SAndreas Gohr $ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 5); 346927933f5SAndreas Gohr 347927933f5SAndreas Gohr // if ($src == 0x2b) $ret += 62 + 1; 348927933f5SAndreas Gohr $ret += (((0x2a - $src) & ($src - 0x2c)) >> 8) & 63; 349927933f5SAndreas Gohr 350927933f5SAndreas Gohr // if ($src == 0x2f) ret += 63 + 1; 351927933f5SAndreas Gohr $ret += (((0x2e - $src) & ($src - 0x30)) >> 8) & 64; 352927933f5SAndreas Gohr 353927933f5SAndreas Gohr return $ret; 354927933f5SAndreas Gohr } 355927933f5SAndreas Gohr 356927933f5SAndreas Gohr /** 357927933f5SAndreas Gohr * Uses bitwise operators instead of table-lookups to turn 8-bit integers 358927933f5SAndreas Gohr * into 6-bit integers. 359927933f5SAndreas Gohr * 360927933f5SAndreas Gohr * @param int $src 361927933f5SAndreas Gohr * @return string 362927933f5SAndreas Gohr */ 363927933f5SAndreas Gohr protected static function encode6Bits(int $src): string 364927933f5SAndreas Gohr { 365927933f5SAndreas Gohr $diff = 0x41; 366927933f5SAndreas Gohr 367927933f5SAndreas Gohr // if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6 368927933f5SAndreas Gohr $diff += ((25 - $src) >> 8) & 6; 369927933f5SAndreas Gohr 370927933f5SAndreas Gohr // if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75 371927933f5SAndreas Gohr $diff -= ((51 - $src) >> 8) & 75; 372927933f5SAndreas Gohr 373927933f5SAndreas Gohr // if ($src > 61) $diff += 0x2b - 0x30 - 10; // -15 374927933f5SAndreas Gohr $diff -= ((61 - $src) >> 8) & 15; 375927933f5SAndreas Gohr 376927933f5SAndreas Gohr // if ($src > 62) $diff += 0x2f - 0x2b - 1; // 3 377927933f5SAndreas Gohr $diff += ((62 - $src) >> 8) & 3; 378927933f5SAndreas Gohr 379*8e88a29bSAndreas Gohr return pack('C', $src + $diff); 380927933f5SAndreas Gohr } 381927933f5SAndreas Gohr} 382