1061 lines
37 KiB
PHP
1061 lines
37 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Services;
|
||
|
||
class ServiceFnPoolV2rayTo
|
||
{
|
||
public function singbox($cPoolV2ray)
|
||
{
|
||
$i = 0;
|
||
foreach ($cPoolV2ray as $aPoolV2ray) {
|
||
$i++;
|
||
// echo $i.":<br>";
|
||
if ($i <= 0) {
|
||
// continue;
|
||
}
|
||
|
||
if ($i >= 100) {
|
||
// continue;
|
||
}
|
||
|
||
|
||
|
||
$aPoolSingbox = $this->singbox_row($aPoolV2ray);
|
||
if (!$aPoolSingbox) continue; //测试用
|
||
t($aPoolV2ray);
|
||
t($aPoolSingbox);
|
||
|
||
echo "=======================================================";
|
||
}
|
||
exit;
|
||
}
|
||
|
||
public function singbox_row($aPoolV2ray)
|
||
{
|
||
$aNode = $aPoolV2ray;
|
||
if (isset($aPoolV2ray['protocolType'])) {
|
||
$aPoolV2ray['protocolType'] = $this->removeUtf8Bom($aPoolV2ray['protocolType']);
|
||
}
|
||
|
||
$sProtocol = $aPoolV2ray['protocolType'] ?? '';
|
||
|
||
if (isset($aPoolV2ray["config"]) && !is_array($aPoolV2ray["config"])) {
|
||
$aPoolV2ray["config"] = rawurldecode($aPoolV2ray["config"]);
|
||
}
|
||
|
||
$sModTo = 'mod_to_'.$sProtocol;
|
||
if (!method_exists($this, $sModTo)) {
|
||
return false; // 没做对应处理的直接排除
|
||
}
|
||
|
||
$aNode = $this->$sModTo($aPoolV2ray);
|
||
|
||
return $aNode;
|
||
}
|
||
|
||
private function mod_to_socks(array $aV2ray): array // 过关,可用性接近
|
||
{
|
||
$sConfigStr = $aV2ray['config'] ?? '';
|
||
if (empty($sConfigStr)) {
|
||
return $aV2ray;
|
||
}
|
||
|
||
// 1. 剥离尾部锚点 (#) 与 Query 参数 (?)
|
||
$sConfigStr = explode('#', $sConfigStr, 2)[0];
|
||
$sConfigStr = explode('?', $sConfigStr, 2)[0];
|
||
|
||
$sUsername = '';
|
||
$sPassword = '';
|
||
$sHostInfo = $sConfigStr;
|
||
|
||
// 2. 判断是否存在认证信息 (user:pass@host:port)
|
||
if (str_contains($sConfigStr, '@')) {
|
||
$aTmpParts = explode('@', $sConfigStr, 2);
|
||
$sAuthInfo = $aTmpParts[0] ?? '';
|
||
$sHostInfo = $aTmpParts[1] ?? '';
|
||
|
||
// 兼容处理:尝试 Base64 解码,若解不出或不含冒号则按明文处理
|
||
$sDecodedAuth = $this->tomBase64Decode($sAuthInfo);
|
||
if ($sDecodedAuth && str_contains($sDecodedAuth, ':')) {
|
||
$sAuthInfo = $sDecodedAuth;
|
||
}
|
||
|
||
$aAuthParts = explode(':', $sAuthInfo, 2);
|
||
$sUsername = rawurldecode($aAuthParts[0] ?? '');
|
||
$sPassword = rawurldecode($aAuthParts[1] ?? '');
|
||
}
|
||
|
||
// 3. 提取地址与端口
|
||
$aHostParts = explode(':', $sHostInfo, 2);
|
||
$sServer = $aHostParts[0] ?? '';
|
||
$iPort = (int) ($aHostParts[1] ?? 1080);
|
||
|
||
// 4. 组装 Sing-box Socks5 节点结构
|
||
$aNode = [
|
||
'type' => 'socks',
|
||
'tag' => (string) ($aV2ray['country'] ?? 'Socks_Node'),
|
||
'server' => $sServer,
|
||
'server_port' => $iPort,
|
||
];
|
||
|
||
// 只有当存在账号密码时才写入,避免空字符串影响 Sing-box 鉴权逻辑
|
||
if ($sUsername !== '') {
|
||
$aNode['username'] = $sUsername;
|
||
}
|
||
if ($sPassword !== '') {
|
||
$aNode['password'] = $sPassword;
|
||
}
|
||
|
||
$aV2ray['config'] = $aNode;
|
||
|
||
return $aV2ray;
|
||
}
|
||
|
||
private function mod_to_trojan(array $aV2ray): array // 过大关,飙绿,反而比原始v2ray可用更多了
|
||
{
|
||
$sConfigStr = $aV2ray['config'] ?? '';
|
||
if (empty($sConfigStr)) {
|
||
return $aV2ray;
|
||
}
|
||
|
||
// 1. 拆分密码与地址
|
||
$aTmpParts = explode('@', $sConfigStr, 2);
|
||
$sPassword = $aTmpParts[0] ?? '';
|
||
$sRestStr = $aTmpParts[1] ?? '';
|
||
|
||
// 2. 拆分地址端口与 Query 参数
|
||
$aTmpParts = explode('?', $sRestStr, 2);
|
||
$sAddrFull = $aTmpParts[0] ?? '';
|
||
$sQueryParamsStr = $aTmpParts[1] ?? '';
|
||
|
||
// 剥离尾部锚点 (#) 影响
|
||
$sAddrFull = explode('#', $sAddrFull, 2)[0];
|
||
$sQueryParamsStr = explode('#', $sQueryParamsStr, 2)[0];
|
||
|
||
// 3. 提取 Server IP/域名 及 Port
|
||
$aTmpParts = explode(':', $sAddrFull, 2);
|
||
$sServer = $aTmpParts[0] ?? '';
|
||
$iPort = (int) ($aTmpParts[1] ?? 443);
|
||
|
||
// 4. 解析 Query 查询参数
|
||
$aCustom = [];
|
||
if (!empty($sQueryParamsStr)) {
|
||
parse_str($sQueryParamsStr, $aCustom);
|
||
}
|
||
|
||
// 5. 组装 Trojan 基础节点
|
||
$aNode = [
|
||
'type' => 'trojan',
|
||
'tag' => (string) ($aV2ray['country'] ?? 'Trojan_Node'),
|
||
'server' => $sServer,
|
||
'server_port' => $iPort,
|
||
'password' => $sPassword,
|
||
];
|
||
|
||
// 6. Trojan 协议原生必须开启 TLS
|
||
$aTls = [
|
||
'enabled' => true,
|
||
];
|
||
|
||
// SNI 与 Host 逻辑对齐
|
||
$sSni = $aCustom['sni'] ?? $aCustom['host'] ?? '';
|
||
if (!empty($sSni)) {
|
||
$aTls['server_name'] = $this->parseServerName($sSni, $sSni);
|
||
}
|
||
|
||
if (!empty($aCustom['alpn'])) {
|
||
$aTls['alpn'] = array_filter(explode(',', $aCustom['alpn']));
|
||
}
|
||
|
||
// 正确解析 allowInsecure 字符串
|
||
if (isset($aCustom['allowInsecure'])) {
|
||
$sInsecure = (string) $aCustom['allowInsecure'];
|
||
$aTls['insecure'] = in_array(strtolower($sInsecure), ['1', 'true', 'yes'], true);
|
||
}
|
||
|
||
// uTLS 指纹伪装
|
||
if (!empty($aCustom['fp'])) {
|
||
$sFingerprint = $aCustom['fp'] === 'random' ? 'chrome' : $aCustom['fp'];
|
||
$aTls['utls'] = [
|
||
'enabled' => true,
|
||
'fingerprint' => $sFingerprint,
|
||
];
|
||
}
|
||
|
||
$aNode['tls'] = $aTls;
|
||
|
||
// 7. 构建 Transport 传输层配置
|
||
$sNetType = $aCustom['type'] ?? 'tcp';
|
||
$aTransport = [];
|
||
|
||
if ($sNetType === 'ws') {
|
||
$aTransport['type'] = 'ws';
|
||
if (!empty($aCustom['path'])) {
|
||
$aTransport['path'] = rawurldecode($aCustom['path']);
|
||
}
|
||
// WS 报头主机名兜底
|
||
$sHost = $aCustom['host'] ?? $aTls['server_name'] ?? '';
|
||
if (!empty($sHost)) {
|
||
$aTransport['headers'] = ['Host' => $sHost];
|
||
}
|
||
} elseif ($sNetType === 'grpc') {
|
||
$aTransport['type'] = 'grpc';
|
||
if (!empty($aCustom['serviceName'])) {
|
||
$aTransport['service_name'] = $aCustom['serviceName'];
|
||
}
|
||
}
|
||
|
||
if (!empty($aTransport)) {
|
||
$aNode['transport'] = $aTransport;
|
||
}
|
||
|
||
$aV2ray['config'] = $aNode;
|
||
|
||
return $aV2ray;
|
||
}
|
||
|
||
private function mod_to_hysteria2(array $aV2ray): array // 过关,可用性差不多
|
||
{
|
||
$sConfigStr = $aV2ray['config'] ?? '';
|
||
if (empty($sConfigStr)) {
|
||
return $aV2ray;
|
||
}
|
||
|
||
// 1. 拆分 Password 与 后续地址信息
|
||
$aTmpParts = explode('@', $sConfigStr, 2);
|
||
$sPassword = $aTmpParts[0] ?? '';
|
||
$sRestStr = $aTmpParts[1] ?? '';
|
||
|
||
// 2. 剥离尾部锚点 (#) 与 拆分 Query 参数 (?)
|
||
$sRestStr = explode('#', $sRestStr, 2)[0];
|
||
$aTmpParts = explode('?', $sRestStr, 2);
|
||
$sAddrFull = $aTmpParts[0] ?? '';
|
||
$sQueryParamsStr = $aTmpParts[1] ?? '';
|
||
|
||
// 3. 提取 Server IP/域名 及 Port
|
||
$aTmpParts = explode(':', $sAddrFull, 2);
|
||
$sServer = $aTmpParts[0] ?? '';
|
||
$iPort = (int) ($aTmpParts[1] ?? 443);
|
||
|
||
// 4. 原生解析 Query 查询参数
|
||
$aQueryParams = [];
|
||
if (!empty($sQueryParamsStr)) {
|
||
parse_str($sQueryParamsStr, $aQueryParams);
|
||
}
|
||
|
||
// 5. 组装 Hysteria2 基础节点结构
|
||
$aNode = [
|
||
'type' => 'hysteria2',
|
||
'tag' => (string) ($aV2ray['country'] ?? 'Hy2_Node'),
|
||
'server' => $sServer,
|
||
'server_port' => $iPort,
|
||
'password' => rawurldecode($sPassword),
|
||
];
|
||
|
||
// 6. Hysteria2 强制开启 TLS 配置
|
||
$aTls = [
|
||
'enabled' => true,
|
||
];
|
||
|
||
$sSni = $aQueryParams['sni'] ?? $aQueryParams['host'] ?? '';
|
||
if (!empty($sSni)) {
|
||
$aTls['server_name'] = $this->parseServerName($sSni, $sSni);
|
||
}
|
||
|
||
if (!empty($aQueryParams['alpn'])) {
|
||
$aTls['alpn'] = array_filter(explode(',', $aQueryParams['alpn']));
|
||
}
|
||
|
||
// 安全解析 insecure
|
||
if (isset($aQueryParams['insecure'])) {
|
||
$sInsecure = (string) $aQueryParams['insecure'];
|
||
$aTls['insecure'] = in_array(strtolower($sInsecure), ['1', 'true', 'yes'], true);
|
||
}
|
||
|
||
$aNode['tls'] = $aTls;
|
||
|
||
// 7. 构建 Obfs 混淆配置
|
||
$sObfsType = $aQueryParams['obfs'] ?? '';
|
||
$sObfsPwd = $aQueryParams['obfs-password'] ?? $aQueryParams['obfs_password'] ?? '';
|
||
|
||
if (!empty($sObfsType) || !empty($sObfsPwd)) {
|
||
$aNode['obfs'] = [
|
||
'type' => !empty($sObfsType) ? $sObfsType : 'salamander',
|
||
'password' => $sObfsPwd,
|
||
];
|
||
}
|
||
|
||
// 8. 支持带宽速率限制参数(若存在)
|
||
if (!empty($aQueryParams['up'])) {
|
||
$aNode['up_mbps'] = (int) $aQueryParams['up'];
|
||
}
|
||
if (!empty($aQueryParams['down'])) {
|
||
$aNode['down_mbps'] = (int) $aQueryParams['down'];
|
||
}
|
||
|
||
$aV2ray['config'] = $aNode;
|
||
|
||
return $aV2ray;
|
||
}
|
||
|
||
private function mod_to_ss(array $aV2ray) // 过关,两边都不可用,但无报错
|
||
{
|
||
$sConfigStr = $aV2ray['config'] ?? '';
|
||
if (empty($sConfigStr)) {
|
||
return false;
|
||
}
|
||
|
||
$sMethod = '';
|
||
$sPassword = '';
|
||
$sServer = '';
|
||
$iPort = 0;
|
||
|
||
// 1. 先尝试将整体进行 Base64 解码(兼容老的全 Base64 格式:ss://BASE64#tag)
|
||
$sDecodedAll = $this->tomBase64Decode(explode('#', $sConfigStr)[0] ?? '');
|
||
if ($sDecodedAll && str_contains($sDecodedAll, '@') && str_contains($sDecodedAll, ':')) {
|
||
// 解码成功且包含标准结构
|
||
$sConfigStr = $sDecodedAll;
|
||
}
|
||
|
||
// 2. 剥离 URL 锚点 (#) 与 Query 参数 (?)
|
||
$sConfigStr = explode('#', $sConfigStr, 2)[0];
|
||
$aUrlParts = explode('?', $sConfigStr, 2);
|
||
$sMainInfo = $aUrlParts[0]; // 剩下 method:password@host:port 或 Base64(method:password)@host:port
|
||
|
||
// 3. 拆分 账号信息 与 地址端口
|
||
$aTmpParts = explode('@', $sMainInfo, 2);
|
||
if (count($aTmpParts) < 2) {
|
||
return false;
|
||
}
|
||
|
||
$sUserInfo = $aTmpParts[0];
|
||
$sHostInfo = $aTmpParts[1];
|
||
|
||
// 4. 解析账号信息 (method:password)
|
||
// 先尝试 Base64 解码,解不出来则说明本身就是明文
|
||
$sDecodedUser = $this->tomBase64Decode($sUserInfo);
|
||
if ($sDecodedUser && str_contains($sDecodedUser, ':')) {
|
||
$sUserInfo = $sDecodedUser;
|
||
}
|
||
|
||
$aUserParts = explode(':', $sUserInfo, 2);
|
||
$sMethod = $aUserParts[0] ?? '';
|
||
$sPassword = $aUserParts[1] ?? '';
|
||
|
||
// 5. 解析地址与端口 (host:port)
|
||
$aHostParts = explode(':', $sHostInfo, 2);
|
||
$sServer = $aHostParts[0] ?? '';
|
||
$iPort = (int) ($aHostParts[1] ?? 0);
|
||
|
||
// 6. 核心加密算法白名单校验
|
||
$aAllowedCiphers = [
|
||
'2022-blake3-aes-128-gcm',
|
||
'2022-blake3-aes-256-gcm',
|
||
'2022-blake3-chacha20-poly1305',
|
||
'aes-128-gcm',
|
||
'aes-192-gcm',
|
||
'aes-256-gcm',
|
||
'chacha20-ietf-poly1305',
|
||
'xchacha20-ietf-poly1305',
|
||
];
|
||
|
||
if (!in_array(strtolower($sMethod), $aAllowedCiphers, true) || empty($sServer) || $iPort <= 0) {
|
||
return false;
|
||
}
|
||
|
||
// 7. 组装 Sing-box Shadowsocks 节点结构
|
||
$aNode = [
|
||
'type' => 'shadowsocks',
|
||
'tag' => (string) ($aV2ray['country'] ?? 'SS_Node'),
|
||
'server' => $sServer,
|
||
'server_port' => $iPort,
|
||
'method' => strtolower($sMethod),
|
||
'password' => $sPassword,
|
||
];
|
||
|
||
$aV2ray['config'] = $aNode;
|
||
|
||
return $aV2ray;
|
||
}
|
||
|
||
private function mod_to_vmess(array $aV2ray): array // 过关,都不可用,但是无报错
|
||
{
|
||
// 1. 类型安全防御:确保 $aConfig 一定是数组
|
||
$aConfig = $aV2ray['config'] ?? [];
|
||
if (is_string($aConfig)) {
|
||
$aConfig = json_decode($aConfig, true) ?? [];
|
||
}
|
||
if (!is_array($aConfig)) {
|
||
$aConfig = [];
|
||
}
|
||
|
||
// 2. 基础节点属性映射
|
||
$aNode = [
|
||
'type' => 'vmess',
|
||
'tag' => (string) ($aV2ray['country'] ?? ''),
|
||
'server' => (string) ($aConfig['add'] ?? ''),
|
||
'server_port' => (int) ($aConfig['port'] ?? 0),
|
||
'uuid' => (string) ($aConfig['id'] ?? ''),
|
||
'security' => !empty($aConfig['scy']) ? (string) $aConfig['scy'] : 'auto',
|
||
'alter_id' => (int) ($aConfig['aid'] ?? 0),
|
||
];
|
||
|
||
// 3. 传输层 (Transport) 映射逻辑
|
||
$sV2rayNet = strtolower((string) ($aConfig['net'] ?? 'tcp'));
|
||
$sV2rayType = strtolower((string) ($aConfig['type'] ?? 'none'));
|
||
$sRawPath = (string) ($aConfig['path'] ?? '');
|
||
$sPath = !empty($sRawPath) ? rawurldecode($sRawPath) : '/';
|
||
$sHost = (string) ($aConfig['host'] ?? '');
|
||
|
||
$aTransport = [];
|
||
|
||
// 分支 A:TCP + HTTP 伪装
|
||
if ($sV2rayNet === 'tcp' && $sV2rayType === 'http') {
|
||
$aTransport['type'] = 'http';
|
||
$aTransport['path'] = $sPath;
|
||
if (!empty($sHost)) {
|
||
$aTransport['host'] = array_map('trim', explode(',', $sHost));
|
||
}
|
||
}
|
||
// 分支 B:WebSocket 传输
|
||
elseif ($sV2rayNet === 'ws') {
|
||
$aTransport['type'] = 'ws';
|
||
$aTransport['path'] = $sPath;
|
||
if (!empty($sHost)) {
|
||
$aTransport['headers'] = ['Host' => $sHost];
|
||
}
|
||
}
|
||
// 分支 C:gRPC 传输
|
||
elseif ($sV2rayNet === 'grpc') {
|
||
$aTransport['type'] = 'grpc';
|
||
$aTransport['service_name'] = $sRawPath;
|
||
}
|
||
// 分支 D:其他非 TCP 传输(如 mkcp, quic)
|
||
elseif (!in_array($sV2rayNet, ['tcp', 'none', 'raw', ''], true)) {
|
||
$aTransport['type'] = $sV2rayNet;
|
||
}
|
||
|
||
if (!empty($aTransport)) {
|
||
$aNode['transport'] = $aTransport;
|
||
}
|
||
|
||
// 4. TLS 配置映射
|
||
$sTlsType = strtolower((string) ($aConfig['tls'] ?? ''));
|
||
if ($sTlsType === 'tls') {
|
||
$sServerName = !empty($sHost) ? $sHost : $aNode['server'];
|
||
|
||
// 兼容类内 parseServerName 方法
|
||
if (method_exists($this, 'parseServerName')) {
|
||
$sServerName = $this->parseServerName($sServerName, $sServerName);
|
||
}
|
||
|
||
$aNode['tls'] = [
|
||
'enabled' => true,
|
||
'server_name' => $sServerName,
|
||
];
|
||
}
|
||
|
||
// 5. 替换配置并输出同等结构
|
||
$aV2ray['config'] = $aNode;
|
||
|
||
return $aV2ray;
|
||
}
|
||
|
||
private function mod_to_vless(array $aV2ray): array // 过关,可用性跟没转前差不多
|
||
{
|
||
$sConfigStr = $aV2ray['config'] ?? '';
|
||
|
||
// 1. 拆分 UUID 与 后续配置 (uuid@host:port?query)
|
||
$aTmpParts = explode('@', $sConfigStr, 2);
|
||
$sUuid = $aTmpParts[0] ?? '';
|
||
$sRestStr = $aTmpParts[1] ?? '';
|
||
|
||
// 2. 拆分 地址端口 与 Query 查询参数
|
||
$aTmpParts = explode('?', $sRestStr, 2);
|
||
$sAddrFull = $aTmpParts[0] ?? '';
|
||
$sQueryParamsStr = $aTmpParts[1] ?? '';
|
||
|
||
// 3. 提取 Server IP/域名 及 Port
|
||
$aTmpParts = explode(':', $sAddrFull, 2);
|
||
$sServer = $aTmpParts[0] ?? '';
|
||
$iPort = (int) ($aTmpParts[1] ?? 443);
|
||
|
||
// 4. 解析 Query 查询参数
|
||
$aQueryParams = [];
|
||
if (!empty($sQueryParamsStr)) {
|
||
parse_str($sQueryParamsStr, $aQueryParams);
|
||
}
|
||
|
||
// 5. 组装 Sing-box VLESS 基础节点属性
|
||
$aNode = [
|
||
'type' => 'vless',
|
||
'tag' => (string) ($aV2ray['country'] ?? 'VLESS_Node'),
|
||
'server' => $sServer,
|
||
'server_port' => $iPort,
|
||
'uuid' => $sUuid,
|
||
];
|
||
|
||
if (isset($aQueryParams['packetEncoding']) && $aQueryParams['packetEncoding'] !== 'none') {
|
||
$aNode['packet_encoding'] = $aQueryParams['packetEncoding'];
|
||
}
|
||
|
||
if (!empty($aQueryParams['flow'])) {
|
||
$aNode['flow'] = $aQueryParams['flow'];
|
||
}
|
||
|
||
// 6. 构建 Transport 传输层配置
|
||
$aTransport = [];
|
||
$sRawType = $aQueryParams['type'] ?? 'tcp';
|
||
$sHeaderType = $aQueryParams['headerType'] ?? '';
|
||
|
||
if ($sRawType === 'ws') {
|
||
$aTransport['type'] = 'ws';
|
||
if (!empty($aQueryParams['path'])) {
|
||
$aTransport['path'] = rawurldecode($aQueryParams['path']);
|
||
}
|
||
if (!empty($aQueryParams['host'])) {
|
||
$aTransport['headers'] = ['Host' => $aQueryParams['host']];
|
||
}
|
||
} elseif ($sRawType === 'grpc') {
|
||
$aTransport['type'] = 'grpc';
|
||
if (!empty($aQueryParams['serviceName'])) {
|
||
$aTransport['service_name'] = $aQueryParams['serviceName'];
|
||
}
|
||
} elseif ($sHeaderType === 'http' || $sRawType === 'http') {
|
||
$aTransport['type'] = 'http';
|
||
if (!empty($aQueryParams['path'])) {
|
||
$aTransport['path'] = rawurldecode($aQueryParams['path']);
|
||
}
|
||
if (!empty($aQueryParams['host'])) {
|
||
$aTransport['host'] = explode(',', $aQueryParams['host']);
|
||
}
|
||
}
|
||
|
||
if (!empty($aTransport)) {
|
||
$aNode['transport'] = $aTransport;
|
||
}
|
||
|
||
// 7. 构建 TLS / Reality / uTLS 配置
|
||
$aTls = [];
|
||
$sSecurity = $aQueryParams['security'] ?? '';
|
||
|
||
// 判断是否开启 TLS / Reality
|
||
if (in_array($sSecurity, ['tls', 'reality'])) {
|
||
$aTls['enabled'] = true;
|
||
|
||
if (!empty($aQueryParams['sni'])) {
|
||
$aTls['server_name'] = $this->parseServerName($aQueryParams['sni'], $aQueryParams['sni']);
|
||
}
|
||
|
||
if (!empty($aQueryParams['alpn'])) {
|
||
$aTls['alpn'] = array_filter(explode(',', $aQueryParams['alpn']));
|
||
}
|
||
|
||
// uTLS 伪装
|
||
if (!empty($aQueryParams['fp'])) {
|
||
$sFingerprint = $aQueryParams['fp'] === 'random' ? 'chrome' : $aQueryParams['fp'];
|
||
$aTls['utls'] = [
|
||
'enabled' => true,
|
||
'fingerprint' => $sFingerprint,
|
||
];
|
||
}
|
||
|
||
// Reality 特性
|
||
if ($sSecurity === 'reality') {
|
||
$aReality = ['enabled' => true];
|
||
if (!empty($aQueryParams['pbk'])) {
|
||
$aReality['public_key'] = $aQueryParams['pbk'];
|
||
}
|
||
if (isset($aQueryParams['sid'])) {
|
||
$aReality['short_id'] = (string) $aQueryParams['sid'];
|
||
}
|
||
$aTls['reality'] = $aReality;
|
||
}
|
||
|
||
$aNode['tls'] = $aTls;
|
||
}
|
||
|
||
$aV2ray['config'] = $aNode;
|
||
|
||
return $aV2ray;
|
||
}
|
||
|
||
private function tomBase64Decode(mixed $xInput): string
|
||
{
|
||
// 1. 防御性类型检查
|
||
if (!is_string($xInput) || $xInput === '') {
|
||
return (string) $xInput;
|
||
}
|
||
|
||
// 2. 开启严格模式解码
|
||
$sDecoded = base64_decode($xInput, true);
|
||
|
||
if ($sDecoded === false) {
|
||
return $xInput;
|
||
}
|
||
|
||
// 3. 反向验证:确保它不是像 humanity 这种恰好能被解码的纯普通文本
|
||
if (base64_encode($sDecoded) !== $xInput) {
|
||
return $xInput;
|
||
}
|
||
|
||
// 4. 核心:校验解码后的数据是否为合法 UTF-8 文本,且不包含不可打印控制字符(\x00-\x08, \x0B-\x0C, \x0E-\x1F)
|
||
$bIsValidUtf8 = mb_check_encoding($sDecoded, 'UTF-8');
|
||
$bHasControlChars = (bool) preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', $sDecoded);
|
||
|
||
if ($bIsValidUtf8 && !$bHasControlChars) {
|
||
return $sDecoded; // 解码出来是合法的正常文本(包含中文、英文、数字等)
|
||
}
|
||
|
||
// 解码出来包含真正的二进制控制符(乱码),说明输入原本就是纯字符串,返回原值
|
||
return rawurldecode($xInput);
|
||
}
|
||
|
||
public function test_allXieyi($cPoolV2ray)
|
||
{
|
||
$aXieyi = [];
|
||
foreach ($cPoolV2ray as $aV2ray) {
|
||
$sXieyi = $aV2ray["protocolType"];
|
||
$sXieyi = $this->removeUtf8Bom($sXieyi);
|
||
|
||
$iXieyi = $aXieyi[$sXieyi] ?? 0;
|
||
$iXieyi++;
|
||
|
||
$aXieyi[$sXieyi] = $iXieyi;
|
||
}
|
||
|
||
return $aXieyi;
|
||
}
|
||
|
||
private function removeUtf8Bom(string $sInput): string
|
||
{
|
||
if (str_starts_with($sInput, "\xEF\xBB\xBF")) {
|
||
return substr($sInput, 3);
|
||
}
|
||
|
||
return $sInput;
|
||
}
|
||
|
||
private function parseServerName(string $sSni, string $sFallbackServer): string
|
||
{
|
||
$sSni = trim($sSni);
|
||
$sSni = rawurldecode($sSni);
|
||
if (empty($sSni)) {
|
||
return $sFallbackServer;
|
||
}
|
||
|
||
// 针对包含 http:// 或 https:// 的情况进行解析
|
||
if (preg_match('#^https?://#i', $sSni)) {
|
||
$aUrl = parse_url($sSni);
|
||
$sSni = $aUrl['host'] ?? '';
|
||
}
|
||
|
||
// 剔除可能残余的路径与端口号 (例如 domain.com/path 或 domain.com:8080)
|
||
if (str_contains($sSni, '/')) {
|
||
$sSni = explode('/', $sSni)[0];
|
||
}
|
||
if (str_contains($sSni, ':')) {
|
||
$sSni = explode(':', $sSni)[0];
|
||
}
|
||
|
||
// 若提取后仍为空,回退到节点自身的 server 字段
|
||
return !empty($sSni) ? $sSni : $sFallbackServer;
|
||
}
|
||
|
||
// private function mod_to_vless($aV2ray)
|
||
// {
|
||
// $sConfig = $aV2ray['config'] ?? '';
|
||
//
|
||
// $aTmp = explode("@", $sConfig, 2);
|
||
// $sPwd = $aTmp[0] ?? ''; // *
|
||
// $sConfig = $aTmp[1] ?? '';
|
||
//
|
||
// $aTmp = explode("?", $sConfig, 2);
|
||
// $sAddrFull = $aTmp[0];
|
||
// $sConfig = $aTmp[1];
|
||
//
|
||
// $aTmp = explode(":", $sAddrFull, 2);
|
||
// $sAddr = $aTmp[0] ?? ''; // *
|
||
// $sPort = $aTmp[1] ?? ''; // *
|
||
//
|
||
// $aCustomTmp = explode("&", $sConfig);
|
||
// $aCustom = [];
|
||
// foreach ($aCustomTmp as $aCustomRow) {
|
||
// $aTmp = explode("=", $aCustomRow, 2);
|
||
// $k = $aTmp[0] ?? '';
|
||
// $v = $aTmp[1] ?? '';
|
||
// $aCustom[$k] = $v; // *
|
||
// }
|
||
//
|
||
// $aNode = [
|
||
// 'type' => 'vless',
|
||
// 'tag' => $aV2ray['country'] ?? '',
|
||
// 'server' => $sAddr,
|
||
// 'server_port' => (int) $sPort,
|
||
// 'uuid' => $sPwd,
|
||
// ];
|
||
// if (isset($aCustom["packetEncoding"]) && $aCustom["packetEncoding"] != "none") {
|
||
// $aNode["packet_encoding"] = $aCustom["packetEncoding"];
|
||
// }
|
||
// if (isset($aCustom["flow"])) {
|
||
// $aNode["flow"] = $aCustom["flow"];
|
||
// }
|
||
//
|
||
//
|
||
// $aTransport = [];
|
||
// if (isset($aCustom["type"]) && $aCustom["type"] != "raw") {
|
||
// $aTransport["type"] = $aCustom["type"];
|
||
// }
|
||
// if (isset($aCustom["headerType"]) && $aCustom["headerType"] == "http") {
|
||
// $aTransport["type"] = "http"; // 整个http覆盖默认tcp
|
||
// }
|
||
// if (isset($aCustom["path"])) {
|
||
// $aTransport["path"] = rawurldecode($aCustom["path"]);
|
||
// }
|
||
// if (isset($aCustom["host"])) {
|
||
// $aTransport["headers"]["Host"] = $aCustom["host"];
|
||
// }
|
||
// if (isset($aCustom["serviceName"])) {
|
||
// $aTransport["service_name"] = $aCustom["serviceName"];
|
||
// }
|
||
// if (!empty($aTransport)) {
|
||
// if (empty($aTransport["type"])) {
|
||
// $aTransport["type"] = "tcp";
|
||
// }
|
||
// $aNode["transport"] = $aTransport;
|
||
// }
|
||
//
|
||
//
|
||
// $aTls = []; // 本体
|
||
// $aUtls = []; // 二层
|
||
// $aReality = []; // 二层
|
||
//
|
||
// if (isset($aCustom["fp"])) { // 二层的utls构建
|
||
// if ($aCustom["fp"] == "random") {
|
||
// $aCustom["fp"] = "chrome";
|
||
// }
|
||
// $aUtls["fingerprint"] = $aCustom["fp"];
|
||
// }
|
||
// if (!empty($aUtls)) { // 二层的utls封装进tls
|
||
// $aUtls["enabled"] = true;
|
||
// $aTls["utls"] = $aUtls;
|
||
// }
|
||
// if (isset($aCustom["security"])) {
|
||
// $sSecurity = $aCustom["security"];
|
||
// if ($sSecurity == "reality") { // 二层的reality构建
|
||
// if (isset($aCustom["pbk"])) {
|
||
// $aReality["public_key"] = $aCustom["pbk"];
|
||
// }
|
||
// if (isset($aCustom["sid"])) {
|
||
// $aReality["short_id"] = (string) $aCustom["sid"];
|
||
// }
|
||
// }
|
||
// }
|
||
// if (!empty($aReality)) { // 二层的reality封建进tls
|
||
// $aReality["enabled"] = true;
|
||
// $aTls["reality"] = $aReality;
|
||
// }
|
||
//
|
||
// if (isset($aCustom["alpn"])) {
|
||
// $sTmp = $aCustom["alpn"] ?? "";
|
||
// $aTmp = explode(",", $sTmp);
|
||
// $aTls["alpn"] = $aTmp;
|
||
// }
|
||
// if (isset($aCustom["sni"])) { // 本体的tls构建
|
||
// $aTls["server_name"] = $this->parseServerName($aCustom["sni"], $aCustom["sni"]);
|
||
// }
|
||
// if (isset($aCustom["encryption"]) && $aCustom["encryption"] != "none") { // 本体的tls构建
|
||
// $aTls["encryption"] = $aCustom["encryption"];
|
||
// }
|
||
// if (!empty($aTls)) { // 本体的tls封装进node
|
||
// $aTls["enabled"] = true;
|
||
// $aNode["tls"] = $aTls;
|
||
// }
|
||
//
|
||
// $aV2ray["config"] = $aNode;
|
||
//
|
||
// return $aV2ray;
|
||
// } // 转换结果有问题,像之前的mod_to_vmess那样给我弄好
|
||
|
||
// private function mod_to_vmess($aV2ray)
|
||
// {
|
||
// $aNode = [];
|
||
// $aConfig = $aV2ray["config"];
|
||
//
|
||
// $aNode["type"] = "vmess";
|
||
// $aNode["tag"] = $aV2ray['country'] ?? '';
|
||
// $aNode["server"] = $aConfig["add"] ?? '';
|
||
// $aNode["server_port"] = $aConfig["port"] ?? ''; // Cannot access offset of type string on string
|
||
// $aNode["server_port"] = (int) $aNode["server_port"];
|
||
// $aNode["uuid"] = $aConfig["id"] ?? '';
|
||
// $aNode["security"] = $aConfig["scy"] ?? 'auto';
|
||
// $aNode["alter_id"] = $aConfig["aid"] ?? 0; // 猜的
|
||
// $aNode["alter_id"] = (int) $aNode["alter_id"];
|
||
//
|
||
// $aTransport = [];
|
||
// if (isset($aConfig["net"]) && $aConfig["net"] != "none" && $aConfig["net"] != "raw") {
|
||
// $aTransport["type"] = $aConfig["net"];
|
||
// }
|
||
// if (isset($aConfig["path"])) {
|
||
// $aTransport["path"] = rawurldecode($aConfig["path"]);
|
||
// }
|
||
// if (isset($aConfig["host"])) {
|
||
// $aTransport["headers"]["Host"] = $aConfig["host"];
|
||
// }
|
||
// if (!empty($aTransport)) {
|
||
// if (empty($aTransport["type"])) {
|
||
// $aTransport["type"] = "tcp";
|
||
// }
|
||
// $aNode["transport"] = $aTransport;
|
||
// }
|
||
//
|
||
// $aTls = [];
|
||
// if (isset($aConfig["tls"])) {
|
||
// if ($aConfig["tls"] == "tls") {
|
||
// $aTls["server_name"] = !empty($aConfig['host']) ? $aConfig['host'] : $aConfig['add'];
|
||
// $aTls["server_name"] = $this->parseServerName($aTls["server_name"], $aTls["server_name"]);
|
||
// }
|
||
// }
|
||
// if (!empty($aTls)) {
|
||
// $aTls["enabled"] = true;
|
||
// $aNode["tls"] = $aTls;
|
||
// }
|
||
//
|
||
// $aV2ray["config"] = $aNode;
|
||
//
|
||
// return $aV2ray;
|
||
// } // 这是原版代码,不行。你给我重写。传入和返回值一致即可
|
||
|
||
// private function mod_to_ss__wo($aV2ray)
|
||
// {
|
||
// $sConfig = $aV2ray["config"] ?? '';
|
||
//
|
||
// $aTmp = explode("@", $sConfig, 2);
|
||
// $sPwd = $aTmp[0] ?? '';
|
||
// $sConfig = $aTmp[1] ?? '';
|
||
//
|
||
// $sPwd = $this->tomBase64Decode($sPwd);
|
||
//
|
||
// $aTmp = explode(":", $sPwd, 2);
|
||
// $sPwdType = $aTmp[0] ?? '';
|
||
// $sPwd = $aTmp[1] ?? '';
|
||
//
|
||
// $aTmp = explode(":", $sConfig, 2);
|
||
// $sAddr = $aTmp[0] ?? '';
|
||
// $iPort = $aTmp[1] ?? '';
|
||
//
|
||
// // 推荐的现代化 SS 算法白名单
|
||
// $aAllowedCiphers = [
|
||
// '2022-blake3-aes-128-gcm',
|
||
// '2022-blake3-aes-256-gcm',
|
||
// '2022-blake3-chacha20-poly1305',
|
||
// 'aes-128-gcm',
|
||
// 'aes-192-gcm',
|
||
// 'aes-256-gcm',
|
||
// 'chacha20-ietf-poly1305',
|
||
// 'xchacha20-ietf-poly1305',
|
||
// ];
|
||
//
|
||
// if (!in_array($sPwdType, $aAllowedCiphers, true)) {
|
||
// return false;
|
||
// }
|
||
//
|
||
// $aNode = [
|
||
// "type" => "shadowsocks",
|
||
// "tag" => $aV2ray['country'] ?? '',
|
||
// "server" => $sAddr,
|
||
// "server_port" => (int) $iPort,
|
||
// "method" => $sPwdType,
|
||
// "password" => $sPwd,
|
||
// ];
|
||
//
|
||
// $aV2ray["config"] = $aNode;
|
||
//
|
||
// return $aV2ray;
|
||
// } // 看看
|
||
|
||
// private function mod_to_socks__($aV2ray)
|
||
// {
|
||
// $sConfig = $aV2ray["config"] ?? '';
|
||
//
|
||
// $aTmp = explode("@", $sConfig, 2);
|
||
// $sPwd = $aTmp[0] ?? '';
|
||
// $sConfig = $aTmp[1] ?? '';
|
||
//
|
||
// $sPwd = $this->tomBase64Decode($sPwd);
|
||
// $aTmp = explode(":", $sPwd, 2);
|
||
// $sUser = $aTmp[0] ?? '';
|
||
// $sPwd = $aTmp[1] ?? '';
|
||
//
|
||
// $aTmp = explode(":", $sConfig, 2);
|
||
// $sAddr = $aTmp[0] ?? '';
|
||
// $iPort = $aTmp[1] ?? '';
|
||
//
|
||
// $aNode = [
|
||
// "type" => "socks",
|
||
// "tag" => $aV2ray['country'] ?? '',
|
||
// "server"=> $sAddr,
|
||
// "server_port" => (int) $iPort,
|
||
// "username" => $sUser,
|
||
// "password" => $sPwd
|
||
// ];
|
||
//
|
||
// $aV2ray["config"] = $aNode;
|
||
//
|
||
// return $aV2ray;
|
||
// } // 看看
|
||
|
||
// private function mod_to_trojan($aV2ray)
|
||
// {
|
||
// $sConfig = $aV2ray["config"] ?? '';
|
||
//
|
||
// $aTmp = explode("@", $sConfig, 2);
|
||
// $sPwd = $aTmp[0] ?? '';
|
||
// $sConfig = $aTmp[1] ?? '';
|
||
//
|
||
// $aTmp = explode("?", $sConfig, 2);
|
||
// $sAddrFull = $aTmp[0] ?? '';
|
||
// $sConfig = $aTmp[1] ?? '';
|
||
//
|
||
// $aTmp = explode(":", $sAddrFull, 2);
|
||
// $sAddr = $aTmp[0] ?? '';
|
||
// $iPort = $aTmp[1] ?? '';
|
||
//
|
||
// $aNode = [
|
||
// "type" => "trojan",
|
||
// "tag" => $aV2ray['country'] ?? '',
|
||
// "server"=> $sAddr,
|
||
// "server_port" => (int) $iPort,
|
||
// "password" => $sPwd
|
||
// ];
|
||
//
|
||
// $aCustomYuan = explode("&", $sConfig);
|
||
// $aCustom = [];
|
||
// foreach ($aCustomYuan as $sRow) {
|
||
// $aRow = explode("=", $sRow, 2);
|
||
// $k = $aRow[0] ?? '';
|
||
// $v = $aRow[1] ?? '';
|
||
// $aCustom[$k] = $v;
|
||
// }
|
||
//
|
||
// $aTls = []; // 层1的tls
|
||
// $aUtls = []; // 层2的utls
|
||
//
|
||
// if (isset($aCustom["sni"])) { // 写层1的tls
|
||
// $aTls["server_name"] = $this->parseServerName($aCustom["sni"], $aCustom["sni"]);
|
||
// }
|
||
// if (isset($aCustom["host"])) { // 写层1的tls -- host覆盖sni=host优先
|
||
// $aTls["server_name"] = $this->parseServerName($aCustom["host"], $aCustom["host"]);
|
||
// }
|
||
// if (isset($aCustom["alpn"])) { // 写层1的tls
|
||
// $aTls["alpn"] = explode(",", $aCustom["alpn"]);
|
||
// }
|
||
// if (isset($aCustom["allowInsecure"])) { // 写层1的tls
|
||
// $aTls["insecure"] = (bool) $aCustom["allowInsecure"];
|
||
// }
|
||
//
|
||
// if (isset($aCustom["fp"])) { // 写层2的utls
|
||
// $aUtls["fingerprint"] = $aCustom["fp"];
|
||
// }
|
||
// if (!empty($aUtls)) { // 层2的utls并入tls
|
||
// $aUtls["enabled"] = true;
|
||
// $aTls["utls"] = $aUtls;
|
||
// }
|
||
//
|
||
// if (!empty($aTls)) { // 层1的tlstls并入node
|
||
// $aTls["enabled"] = true;
|
||
// $aNode["tls"] = $aTls;
|
||
// }
|
||
//
|
||
// $aTransport = [];
|
||
// if ($aCustom["type"] == "ws" && isset($aTls["server_name"])) {
|
||
// $aTransport["headers"]["Host"] = $aTls["server_name"];
|
||
// }
|
||
//
|
||
//// if (isset($aCustom["type"]) && $aCustom["type"] != "tcp" && $aCustom["type"] != "none" && $aCustom["type"] != "raw") {
|
||
//// $aTransport["type"] = $aCustom["type"];
|
||
//// if ($aCustom["type"] == "ws" && isset($aTls["server_name"])) {
|
||
//// $aTransport["headers"]["Host"] = $aTls["server_name"];
|
||
//// }
|
||
//// }
|
||
// if (isset($aCustom["path"])) {
|
||
// $aTransport["path"] = rawurldecode($aCustom["path"]);
|
||
// }
|
||
// if (!empty($aTransport)) {
|
||
// if (isset($aCustom["type"]) && $aCustom["type"] != "none" && $aCustom["type"] != "raw") {
|
||
// $aTransport["type"] = $aCustom["type"];
|
||
// }
|
||
//// if (empty($aTransport["type"])) {
|
||
//// $aTransport["type"] = "tcp";
|
||
//// }
|
||
// $aNode["transport"] = $aTransport;
|
||
// }
|
||
//
|
||
// $aV2ray["config"] = $aNode;
|
||
//
|
||
// return $aV2ray;
|
||
// } // 看看
|
||
|
||
// private function mod_to_hysteria2($aV2ray)
|
||
// {
|
||
// $sConfig = $aV2ray["config"] ?? '';
|
||
//
|
||
// $aTmp = explode("@", $sConfig, 2);
|
||
// $sPwd = $aTmp[0] ?? '';
|
||
// $sConfig = $aTmp[1] ?? '';
|
||
//
|
||
// $aTmp = explode("?", $sConfig, 2);
|
||
// $sAddrFull = $aTmp[0] ?? '';
|
||
// $sConfig = $aTmp[1] ?? '';
|
||
// $aTmp = explode(":", $sAddrFull, 2);
|
||
// $sAddr = $aTmp[0] ?? '';
|
||
// $iPort = $aTmp[1] ?? '';
|
||
//
|
||
// $cCustom = explode("&", $sConfig);
|
||
// $aCustom = [];
|
||
// foreach ($cCustom as $sCustom) {
|
||
// $aTmp = explode("=", $sCustom);
|
||
// $k = $aTmp[0] ?? '';
|
||
// $v = $aTmp[1] ?? '';
|
||
// $aCustom[$k] = $v;
|
||
// }
|
||
//
|
||
// $aNode = [
|
||
// "type" => "hysteria2",
|
||
// "tag" => $aV2ray['country'] ?? '',
|
||
// "server"=> $sAddr,
|
||
// "server_port" => (int) $iPort,
|
||
// "password" => $sPwd
|
||
// ];
|
||
//
|
||
// $aTls = [];
|
||
// if (isset($aCustom['sni'])) {
|
||
// $aTls['server_name'] = $this->parseServerName($aCustom['sni'], $aCustom['sni']);
|
||
// }
|
||
// if (isset($aCustom['insecure'])) {
|
||
// $aTls['insecure'] = (bool) $aCustom['insecure'] ?? 0;
|
||
// }
|
||
// if (!empty($aTls)) {
|
||
// $aTls['enabled'] = true;
|
||
// $aNode["tls"] = $aTls;
|
||
// }
|
||
//
|
||
// $aObfs = [];
|
||
// if (isset($aCustom["obfs"])) {
|
||
// $aObfs["type"] = $aCustom["obfs"];
|
||
// }
|
||
// if (isset($aCustom["obfs-password"])) {
|
||
// $aObfs["password"] = $aCustom["obfs-password"];
|
||
// }
|
||
// if (!empty($aObfs)) {
|
||
// $aNode["obfs"] = $aObfs;
|
||
// }
|
||
//
|
||
// $aV2ray["config"] = $aNode;
|
||
//
|
||
// return $aV2ray;
|
||
// } // 看看
|
||
}
|