NNTmux /
newznab-tmux
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * This program is free software: you can redistribute it and/or modify |
||
| 5 | * it under the terms of the GNU General Public License as published by |
||
| 6 | * the Free Software Foundation, either version 3 of the License, or |
||
| 7 | * (at your option) any later version. |
||
| 8 | * This program is distributed in the hope that it will be useful, |
||
| 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
| 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
| 11 | * GNU General Public License for more details. |
||
| 12 | * You should have received a copy of the GNU General Public License |
||
| 13 | * along with this program (see LICENSE.txt in the base directory. If |
||
| 14 | * not, see:. |
||
| 15 | * |
||
| 16 | * @link <http://www.gnu.org/licenses/>. |
||
| 17 | * |
||
| 18 | * @author niel |
||
| 19 | * @copyright 2016 nZEDb |
||
| 20 | */ |
||
| 21 | |||
| 22 | namespace App\Extensions\util; |
||
| 23 | |||
| 24 | /** |
||
| 25 | * Class PhpYenc. |
||
| 26 | * Optimized yEnc encoder/decoder implementation. |
||
| 27 | */ |
||
| 28 | class PhpYenc |
||
| 29 | { |
||
| 30 | /** |
||
| 31 | * Pre-computed decode translation table for non-escaped characters. |
||
| 32 | * Maps yEnc encoded byte to decoded byte: (byte - 42) % 256 |
||
| 33 | * @var array<string, string>|null |
||
| 34 | */ |
||
| 35 | private static ?array $decodeTable = null; |
||
| 36 | |||
| 37 | /** |
||
| 38 | * Pre-computed decode translation table for escaped characters. |
||
| 39 | * Maps yEnc escaped byte to decoded byte: ((byte - 64) - 42) % 256 |
||
| 40 | * @var array<string, string>|null |
||
| 41 | */ |
||
| 42 | private static ?array $escapeDecodeTable = null; |
||
| 43 | |||
| 44 | /** |
||
| 45 | * Initialize the decode translation tables (lazy initialization). |
||
| 46 | */ |
||
| 47 | private static function initDecodeTables(): void |
||
| 48 | { |
||
| 49 | if (self::$decodeTable !== null) { |
||
| 50 | return; |
||
| 51 | } |
||
| 52 | |||
| 53 | self::$decodeTable = []; |
||
| 54 | self::$escapeDecodeTable = []; |
||
| 55 | |||
| 56 | for ($i = 0; $i < 256; $i++) { |
||
| 57 | $char = \chr($i); |
||
| 58 | // Standard decode: (byte - 42) % 256 |
||
| 59 | self::$decodeTable[$char] = \chr(($i - 42 + 256) % 256); |
||
| 60 | // Escape decode: ((byte - 64) - 42) % 256 |
||
| 61 | self::$escapeDecodeTable[$char] = \chr((($i - 64 - 42) + 512) % 256); |
||
| 62 | } |
||
| 63 | } |
||
| 64 | |||
| 65 | public static function decode(&$text, bool $ignore = false): bool|string |
||
|
0 ignored issues
–
show
|
|||
| 66 | { |
||
| 67 | $crc = ''; |
||
| 68 | // Extract the yEnc string itself. |
||
| 69 | if (preg_match( |
||
| 70 | '/=ybegin.*size=(\d+).*\r\n(.*)(?:\r\n)?=yend.*size=(\d+)(.*)/ims', |
||
| 71 | $text, |
||
| 72 | $encoded |
||
| 73 | )) { |
||
| 74 | if (preg_match('/crc32=([0-9a-fA-F]+)/i', $encoded[4], $trailer)) { |
||
| 75 | $crc = trim($trailer[1]); |
||
| 76 | } |
||
| 77 | |||
| 78 | $headerSize = (int) $encoded[1]; |
||
| 79 | $trailerSize = (int) $encoded[3]; |
||
| 80 | $encoded = $encoded[2]; |
||
| 81 | } else { |
||
| 82 | return false; |
||
| 83 | } |
||
| 84 | |||
| 85 | // Make sure the header and trailer file sizes match up. |
||
| 86 | if ($headerSize !== $trailerSize) { |
||
| 87 | $message = 'Header and trailer file sizes do not match. This is a violation of the yEnc specification.'; |
||
| 88 | throw new \RuntimeException($message); |
||
| 89 | } |
||
| 90 | |||
| 91 | // Remove line breaks from the string (use str_replace with array for speed). |
||
| 92 | $encoded = str_replace(["\r\n", "\r", "\n"], '', $encoded); |
||
| 93 | |||
| 94 | // Fast decode using optimized method |
||
| 95 | $decoded = self::fastDecode($encoded); |
||
| 96 | |||
| 97 | // Make sure the decoded file size is the same as the size specified in the header. |
||
| 98 | $decodedLength = \strlen($decoded); |
||
| 99 | if ($decodedLength !== $headerSize) { |
||
| 100 | $message = 'Header file size ('.$headerSize.') and actual file size ('.$decodedLength.') do not match. The file is probably corrupt.'; |
||
| 101 | |||
| 102 | throw new \RuntimeException($message); |
||
| 103 | } |
||
| 104 | |||
| 105 | // Check the CRC value |
||
| 106 | if ($crc !== '' && (strcasecmp($crc, sprintf('%X', crc32($decoded))) !== 0)) { |
||
| 107 | $message = 'CRC32 checksums do not match. The file is probably corrupt.'; |
||
| 108 | |||
| 109 | throw new \RuntimeException($message); |
||
| 110 | } |
||
| 111 | |||
| 112 | return $decoded; |
||
| 113 | } |
||
| 114 | |||
| 115 | /** |
||
| 116 | * Fast yEnc decode using strtr and optimized escape handling. |
||
| 117 | * |
||
| 118 | * @param string $encoded The encoded string (without headers/line breaks) |
||
| 119 | * @return string The decoded string |
||
| 120 | */ |
||
| 121 | private static function fastDecode(string $encoded): string |
||
| 122 | { |
||
| 123 | self::initDecodeTables(); |
||
| 124 | |||
| 125 | /** @var array<string, string> $decodeTable */ |
||
| 126 | $decodeTable = self::$decodeTable; |
||
| 127 | /** @var array<string, string> $escapeDecodeTable */ |
||
| 128 | $escapeDecodeTable = self::$escapeDecodeTable; |
||
| 129 | |||
| 130 | // Check if there are any escape sequences |
||
| 131 | $escapePos = strpos($encoded, '='); |
||
| 132 | |||
| 133 | if ($escapePos === false) { |
||
| 134 | // No escape sequences - use fast strtr translation |
||
| 135 | return strtr($encoded, $decodeTable); |
||
| 136 | } |
||
| 137 | |||
| 138 | // Handle escape sequences |
||
| 139 | // First, process escape sequences by splitting on '=' |
||
| 140 | $parts = explode('=', $encoded); |
||
| 141 | $result = strtr($parts[0], $decodeTable); |
||
| 142 | |||
| 143 | $count = \count($parts); |
||
| 144 | for ($i = 1; $i < $count; $i++) { |
||
| 145 | $part = $parts[$i]; |
||
| 146 | if ($part === '') { |
||
| 147 | continue; |
||
| 148 | } |
||
| 149 | // First character after '=' is the escaped character |
||
| 150 | $result .= $escapeDecodeTable[$part[0]]; |
||
| 151 | // Rest of the part is normal encoded data |
||
| 152 | if (isset($part[1])) { |
||
| 153 | $result .= strtr(substr($part, 1), $decodeTable); |
||
| 154 | } |
||
| 155 | } |
||
| 156 | |||
| 157 | return $result; |
||
| 158 | } |
||
| 159 | |||
| 160 | /** |
||
| 161 | * Decode a string of text encoded with yEnc. Ignores all errors. |
||
| 162 | * |
||
| 163 | * @param string $text The encoded text to decode. |
||
| 164 | * @return string The decoded yEnc string, or the input string, if it's not yEnc. |
||
| 165 | */ |
||
| 166 | public static function decodeIgnore(string &$text): string |
||
| 167 | { |
||
| 168 | if (preg_match('/^(=yBegin.*=yEnd[^$]*)$/ims', $text, $input)) { |
||
| 169 | // Extract the encoded data, removing headers and line breaks |
||
| 170 | $input = preg_replace('/(^=yBegin.*\r\n)/im', '', $input[1], 1); |
||
| 171 | $input = preg_replace('/(^=yPart.*\r\n)/im', '', $input, 1); |
||
| 172 | $input = preg_replace('/(^=yEnd.*)/im', '', $input, 1); |
||
| 173 | $input = str_replace(["\r\n", "\r", "\n"], '', trim($input)); |
||
| 174 | |||
| 175 | // Use the fast decode method |
||
| 176 | $text = self::fastDecode($input); |
||
| 177 | } |
||
| 178 | |||
| 179 | return $text; |
||
| 180 | } |
||
| 181 | |||
| 182 | public static function enabled(): bool |
||
| 183 | { |
||
| 184 | return true; |
||
| 185 | } |
||
| 186 | |||
| 187 | public static function encode($data, $filename, int $lineLength = 128, bool $crc32 = true): string |
||
| 188 | { |
||
| 189 | // yEnc 1.3 draft doesn't allow line lengths of more than 254 bytes. |
||
| 190 | if ($lineLength > 254) { |
||
| 191 | $lineLength = 254; |
||
| 192 | } |
||
| 193 | |||
| 194 | if ($lineLength < 1) { |
||
| 195 | $message = $lineLength.' is not a valid line length.'; |
||
| 196 | |||
| 197 | throw new \RuntimeException($message); |
||
| 198 | } |
||
| 199 | |||
| 200 | $encoded = ''; |
||
| 201 | $stringLength = \strlen($data); |
||
| 202 | // Encode each character of the string one at a time. |
||
| 203 | foreach ($data as $i => $iValue) { |
||
| 204 | $value = ((\ord($iValue) + 42) % 256); |
||
| 205 | |||
| 206 | // Escape NULL, TAB, LF, CR, space, . and = characters. |
||
| 207 | $encoded .= match ($value) { |
||
| 208 | 0, 10, 13, 61 => ('='.\chr(($value + 64) % 256)), |
||
| 209 | default => \chr($value), |
||
| 210 | }; |
||
| 211 | } |
||
| 212 | |||
| 213 | $encoded = |
||
| 214 | '=ybegin line='. |
||
| 215 | $lineLength. |
||
| 216 | ' size='. |
||
| 217 | $stringLength. |
||
| 218 | ' name='. |
||
| 219 | trim($filename). |
||
| 220 | "\r\n". |
||
| 221 | trim(chunk_split($encoded, $lineLength)). |
||
| 222 | "\r\n=yend size=". |
||
| 223 | $stringLength; |
||
| 224 | |||
| 225 | // Add a CRC32 checksum if desired. |
||
| 226 | if ($crc32 === true) { |
||
| 227 | $encoded .= ' crc32='.strtolower(sprintf('%X', crc32($data))); |
||
| 228 | } |
||
| 229 | |||
| 230 | return $encoded; |
||
| 231 | } |
||
| 232 | } |
||
| 233 |
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.