Files
Xboard/app/Protocols/Surfboard.php
xboard 508caebdcd refactor: refactor subscription delivery logic, change payment return_url to origin_url concatenation
- Unify protocol filter configuration to client.type.field (dot-path, three-segment) format, support strict whitelist mode
- Refactor AbstractProtocol and all protocol classes for more flexible and maintainable subscription delivery
- Change payment callback logic: use origin_url concatenation instead of return_url for more accurate redirects
2025-07-18 15:42:58 +08:00

194 lines
7.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Protocols;
use App\Utils\Helper;
use Illuminate\Support\Facades\File;
use App\Support\AbstractProtocol;
use App\Models\Server;
class Surfboard extends AbstractProtocol
{
public $flags = ['surfboard'];
public $allowedProtocols = [
Server::TYPE_SHADOWSOCKS,
Server::TYPE_VMESS,
Server::TYPE_TROJAN,
];
const CUSTOM_TEMPLATE_FILE = 'resources/rules/custom.surfboard.conf';
const DEFAULT_TEMPLATE_FILE = 'resources/rules/default.surfboard.conf';
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$appName = admin_setting('app_name', 'XBoard');
$proxies = '';
$proxyGroup = '';
foreach ($servers as $item) {
if (
$item['type'] === Server::TYPE_SHADOWSOCKS
&& in_array(data_get($item, 'protocol_settings.cipher'), [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
// [Proxy]
$proxies .= self::buildShadowsocks($item['password'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
if ($item['type'] === Server::TYPE_VMESS) {
// [Proxy]
$proxies .= self::buildVmess($item['password'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
if ($item['type'] === Server::TYPE_TROJAN) {
// [Proxy]
$proxies .= self::buildTrojan($item['password'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
}
$config = admin_setting('subscribe_template_surfboard', File::exists(base_path(self::CUSTOM_TEMPLATE_FILE))
? File::get(base_path(self::CUSTOM_TEMPLATE_FILE))
: File::get(base_path(self::DEFAULT_TEMPLATE_FILE)));
// Subscription link
$subsURL = Helper::getSubscribeUrl($user['token']);
$subsDomain = request()->header('Host');
$config = str_replace('$subs_link', $subsURL, $config);
$config = str_replace('$subs_domain', $subsDomain, $config);
$config = str_replace('$proxies', $proxies, $config);
$config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
$upload = round($user['u'] / (1024 * 1024 * 1024), 2);
$download = round($user['d'] / (1024 * 1024 * 1024), 2);
$useTraffic = $upload + $download;
$totalTraffic = round($user['transfer_enable'] / (1024 * 1024 * 1024), 2);
$unusedTraffic = $totalTraffic - $useTraffic;
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
$subscribeInfo = "title={$appName}订阅信息, content=上传流量:{$upload}GB\\n下载流量{$download}GB\\n剩余流量: { $unusedTraffic }GB\\n套餐流量{$totalTraffic}GB\\n到期时间{$expireDate}";
$config = str_replace('$subscribe_info', $subscribeInfo, $config);
return response($config, 200)
->header('content-disposition', "attachment;filename*=UTF-8''" . rawurlencode($appName) . ".conf");
}
public static function buildShadowsocks($password, $server)
{
$protocol_settings = $server['protocol_settings'];
$config = [
"{$server['name']}=ss",
"{$server['host']}",
"{$server['port']}",
"encrypt-method={$protocol_settings['cipher']}",
"password={$password}",
'tfo=true',
'udp-relay=true'
];
if (data_get($protocol_settings, 'plugin') && data_get($protocol_settings, 'plugin_opts')) {
$plugin = data_get($protocol_settings, 'plugin');
$pluginOpts = data_get($protocol_settings, 'plugin_opts', '');
// 解析插件选项
$parsedOpts = collect(explode(';', $pluginOpts))
->filter()
->mapWithKeys(function ($pair) {
if (!str_contains($pair, '=')) {
return [];
}
[$key, $value] = explode('=', $pair, 2);
return [trim($key) => trim($value)];
})
->all();
switch ($plugin) {
case 'obfs':
$config[] = "obfs={$parsedOpts['obfs']}";
if (isset($parsedOpts['obfs-host'])) {
$config[] = "obfs-host={$parsedOpts['obfs-host']}";
}
if (isset($parsedOpts['path'])) {
$config[] = "obfs-uri={$parsedOpts['path']}";
}
break;
}
}
$config = array_filter($config);
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
public static function buildVmess($uuid, $server)
{
$protocol_settings = $server['protocol_settings'];
$config = [
"{$server['name']}=vmess",
"{$server['host']}",
"{$server['port']}",
"username={$uuid}",
"vmess-aead=true",
'tfo=true',
'udp-relay=true'
];
if (data_get($protocol_settings, 'tls')) {
array_push($config, 'tls=true');
if (data_get($protocol_settings, 'tls_settings')) {
$tlsSettings = data_get($protocol_settings, 'tls_settings');
if (!!data_get($tlsSettings, 'allowInsecure'))
array_push($config, 'skip-cert-verify=' . ($tlsSettings['allowInsecure'] ? 'true' : 'false'));
if (!!data_get($tlsSettings, 'serverName'))
array_push($config, "sni={$tlsSettings['serverName']}");
}
}
if (data_get($protocol_settings, 'network') === 'ws') {
array_push($config, 'ws=true');
if (data_get($protocol_settings, 'network_settings')) {
$wsSettings = data_get($protocol_settings, 'network_settings');
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
array_push($config, "ws-path={$wsSettings['path']}");
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
array_push($config, "ws-headers=Host:{$wsSettings['headers']['Host']}");
}
}
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
public static function buildTrojan($password, $server)
{
$protocol_settings = $server['protocol_settings'];
$config = [
"{$server['name']}=trojan",
"{$server['host']}",
"{$server['port']}",
"password={$password}",
$protocol_settings['server_name'] ? "sni={$protocol_settings['server_name']}" : "",
'tfo=true',
'udp-relay=true'
];
if (data_get($protocol_settings, 'allow_insecure')) {
array_push($config, !!data_get($protocol_settings, 'allow_insecure') ? 'skip-cert-verify=true' : 'skip-cert-verify=false');
}
$config = array_filter($config);
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
}