95 lines
2.8 KiB
PHP
95 lines
2.8 KiB
PHP
|
|
<?php
|
|||
|
|
namespace app\common\utils\crawl\service;
|
|||
|
|
use support\Db;
|
|||
|
|
|
|||
|
|
trait UnCodeFont{
|
|||
|
|
/**
|
|||
|
|
* 是否包含 PUA 字符(U+E000~U+F8FF)
|
|||
|
|
*/
|
|||
|
|
function needFontDecode(string $text): bool
|
|||
|
|
{
|
|||
|
|
if ($text === '') return false;
|
|||
|
|
$chars = preg_split('//u', $text, -1, PREG_SPLIT_NO_EMPTY);
|
|||
|
|
if (!$chars) return false;
|
|||
|
|
|
|||
|
|
foreach ($chars as $ch) {
|
|||
|
|
$u = mb_convert_encoding($ch, 'UCS-4BE', 'UTF-8');
|
|||
|
|
if ($u === false || strlen($u) !== 4) continue;
|
|||
|
|
$cp = unpack('N', $u)[1];
|
|||
|
|
if ($cp >= 0xE000 && $cp <= 0xF8FF) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 读取某个 font_hash 的映射表,返回 [ 'e53d' => '中', ... ]
|
|||
|
|
* code 表里存的是 '' 或 ''
|
|||
|
|
*/
|
|||
|
|
function loadFontMap(string $fontHash): array
|
|||
|
|
{
|
|||
|
|
$rows = Db::table('font_map')
|
|||
|
|
->select('code', 'char')
|
|||
|
|
->where('font_hash', $fontHash)
|
|||
|
|
->get()
|
|||
|
|
->toArray();
|
|||
|
|
$rows = array_map(function($item) {
|
|||
|
|
return (array) $item;
|
|||
|
|
}, $rows);
|
|||
|
|
|
|||
|
|
$map = [];
|
|||
|
|
foreach ($rows as $r) {
|
|||
|
|
// 从 '' 提取 e53d
|
|||
|
|
if (preg_match('/&#x([0-9a-fA-F]+);/i', $r['code'], $m)) {
|
|||
|
|
$map[strtolower($m[1])] = $r['char'];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return $map;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将包含 PUA 字符的字符串,按 font_map 替换
|
|||
|
|
*/
|
|||
|
|
function decodeObfuscatedText(string $text, array $fontMap): string
|
|||
|
|
{
|
|||
|
|
$fontHash = '77fd388510a974babdf4837b26d2d33f'; // 你当前字体 md5(font_map.sql 里那一份)
|
|||
|
|
$fontMap = $this->loadFontMap($fontHash);
|
|||
|
|
// 按 Unicode 字符切分
|
|||
|
|
$chars = preg_split('//u', $text, -1, PREG_SPLIT_NO_EMPTY);
|
|||
|
|
if (!$chars) return $text;
|
|||
|
|
|
|||
|
|
$out = '';
|
|||
|
|
|
|||
|
|
foreach ($chars as $ch) {
|
|||
|
|
// 获取码点(十六进制),如 e53d
|
|||
|
|
$hex = $this->unicodeCodePointHex($ch);
|
|||
|
|
if ($hex !== null) {
|
|||
|
|
$key = strtolower($hex);
|
|||
|
|
// 只替换命中的(一般就是 E***)
|
|||
|
|
if (isset($fontMap[$key])) {
|
|||
|
|
$out .= $fontMap[$key];
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 没命中:原样保留(比如 '.' 这种正常字符)
|
|||
|
|
$out .= $ch;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取单个 Unicode 字符的码点十六进制(不带 0x),失败返回 null
|
|||
|
|
* 例:'' -> 'e53d'
|
|||
|
|
*/
|
|||
|
|
function unicodeCodePointHex(string $ch): ?string
|
|||
|
|
{
|
|||
|
|
if ($ch === '') return null;
|
|||
|
|
$u = mb_convert_encoding($ch, 'UCS-4BE', 'UTF-8');
|
|||
|
|
if ($u === false || strlen($u) !== 4) return null;
|
|||
|
|
$code = unpack('N', $u)[1]; // 32-bit
|
|||
|
|
return dechex($code);
|
|||
|
|
}
|
|||
|
|
}
|