Extractor::MAX_BYTES || $onDisk > Extractor::MAX_BYTES) { return self::SIZE; } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- reads a local file (the key file or an uploaded text file), not a URL $content = (string) @file_get_contents($file['tmp_name'], false, null, 0, Extractor::MAX_BYTES + 1); if ($type !== 'pdf') { $content = self::normalizeText($content); } $checked = self::validateContent($type, $content); return $checked === null ? ['name' => $name, 'type' => $type, 'content' => $content] : $checked; } /** Pure check used by read() and by tests: name, declared size and content already in hand. @return array{type:string}|string */ public static function validate(string $sanitizedName, int $declaredSize, string $content): array|string { $type = self::typeFor($sanitizedName); if ($type === null) { return self::TYPE; } if ($type === 'pdf' && !PdfSupport::available()) { return self::PDF_UNAVAILABLE; } if ($declaredSize > Extractor::MAX_BYTES) { return self::SIZE; } $error = self::validateContent($type, $content); return $error ?? ['type' => $type]; } private static function typeFor(string $name): ?string { return match (strtolower(pathinfo($name, PATHINFO_EXTENSION))) { 'txt' => 'text', 'md', 'markdown' => 'markdown', 'pdf' => 'pdf', default => null, }; } /** * Text saved by common editors: a UTF-8 byte order mark is dropped, and UTF-16 with a byte order mark (Notepad's "Unicode" option) is * converted to UTF-8. Both are unambiguous. Other encodings are not guessed: a wrong guess would index unreadable text without a word. */ private static function normalizeText(string $content): string { if (str_starts_with($content, "\xEF\xBB\xBF")) { return substr($content, 3); } $bom = substr($content, 0, 2); if (($bom === "\xFF\xFE" || $bom === "\xFE\xFF") && strlen($content) % 2 === 0) { $converted = mb_convert_encoding(substr($content, 2), 'UTF-8', $bom === "\xFF\xFE" ? 'UTF-16LE' : 'UTF-16BE'); return is_string($converted) ? $converted : $content; } return $content; } private static function validateContent(string $type, string $content): ?string { if (strlen($content) > Extractor::MAX_BYTES) { return self::SIZE; } $valid = $type === 'pdf' ? str_starts_with($content, '%PDF-') && str_contains($content, '%%EOF') : !str_contains($content, "\0") && mb_check_encoding($content, 'UTF-8'); return $valid ? null : self::BINARY; } }