535 lines
17 KiB
PHP
535 lines
17 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__($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;
|
||
}
|
||
|
||
private function mod_to_ss($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_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_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;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
}
|