Skip to content

Commit 135e14f

Browse files
committed
Refactor fetch function and enhance proxy configuration handling in index.js
- Introduced a request-specific configuration object to avoid global variable modifications. - Added URL query parameter support for overriding proxy settings, including proxy IP, SOCKS5 address, and relay options. - Implemented validation for proxy and SOCKS5 formats to ensure correct configurations. - Updated ProtocolOverWSHandler and HandleTCPOutBound functions to utilize the new configuration object. - Enhanced README.md to document the new URL query parameter features and usage examples.
1 parent 645f721 commit 135e14f

File tree

2 files changed

+190
-30
lines changed

2 files changed

+190
-30
lines changed

README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ EDtunnel is a proxy tool based on Cloudflare Workers and Pages, supporting multi
1818
- 支持自定义代理 IP 和端口
1919
- 支持 SOCKS5 代理
2020
- 提供自动配置订阅链接
21+
- 支持 URL 查询参数覆盖配置
2122
- 简单易用的部署流程
2223

2324
- Support for Cloudflare Workers and Pages deployment
2425
- Multiple UUID configuration support
2526
- Custom proxy IP and port support
2627
- SOCKS5 proxy support
2728
- Automatic configuration subscription link
29+
- URL query parameter configuration override support
2830
- Simple and easy deployment process
2931

3032
## 🚀 快速部署 | Quick Deployment
@@ -55,6 +57,60 @@ EDtunnel is a proxy tool based on Cloudflare Workers and Pages, supporting multi
5557
| `SOCKS5` | 否 (No) | `user:pass@host:port`<br>多个 (Multiple): `user1:pass1@host1:port1,user2:pass2@host2:port2` | SOCKS5代理配置 / SOCKS5 proxy configuration |
5658
| `SOCKS5_RELAY` | 否 (No) | `true` 或 (or) `false` | 启用SOCKS5流量转发 / Enable SOCKS5 traffic relay |
5759

60+
### URL查询参数配置 | URL Query Parameter Configuration
61+
62+
您可以使用URL查询参数直接覆盖环境变量配置,这些参数的优先级高于环境变量。出于安全考虑,UUID 不能通过 URL 查询参数设置。
63+
64+
You can use URL query parameters to directly override environment variable configurations. These parameters have higher priority than environment variables. For security reasons, UUID cannot be set via URL query parameters.
65+
66+
| 查询参数 (Query Parameter) | 对应环境变量 (Corresponding ENV) | 示例 (Example) | 说明 (Description) |
67+
|--------------------------|--------------------------------|---------------|-------------------|
68+
| `proxyip` | `PROXYIP` | `?proxyip=1.1.1.1:443` | 覆盖代理IP和端口 / Override proxy IP and port |
69+
| `socks5` | `SOCKS5` | `?socks5=user:pass@host:port` | 覆盖SOCKS5代理配置 / Override SOCKS5 proxy configuration |
70+
| `socks5_relay` | `SOCKS5_RELAY` | `?socks5_relay=true` | 覆盖SOCKS5转发设置 / Override SOCKS5 relay setting |
71+
72+
> **安全说明**: UUID 必须通过环境变量或配置文件设置,不能通过 URL 参数设置,以防止未授权修改用户身份。
73+
> **Security Note**: UUID must be set via environment variables or configuration files, not through URL parameters, to prevent unauthorized identity modifications.
74+
75+
#### 使用示例 | Usage Examples:
76+
77+
1. 临时更改代理IP | Temporarily change proxy IP:
78+
```
79+
https://your-domain.workers.dev/?proxyip=another-proxy-ip:port
80+
```
81+
82+
2. 组合多个参数 | Combine multiple parameters:
83+
```
84+
https://your-domain.workers.dev/?proxyip=1.1.1.1:443&socks5_relay=true
85+
```
86+
87+
3. 应用于特定路径 | Apply to specific paths:
88+
```
89+
https://your-domain.workers.dev/sub/your-uuid?proxyip=1.1.1.1:443
90+
```
91+
92+
#### 特性说明 | Feature Notes:
93+
94+
- 优先级:URL参数 > 环境变量 > 默认值
95+
- 临时性:这些更改仅对当前请求有效,不会永久修改配置
96+
- 可组合:可以组合多个参数实现复杂配置调整
97+
- 适用场景:快速测试、临时切换配置、第三方系统动态调用
98+
99+
- Priority: URL parameters > Environment Variables > Default Values
100+
- Temporary: These changes only apply to the current request and do not permanently modify configurations
101+
- Combinable: Multiple parameters can be combined for complex configuration adjustments
102+
- Use cases: Quick testing, temporary configuration switching, dynamic calls from third-party systems
103+
104+
#### URL格式注意事项 | URL Format Notes:
105+
106+
- 确保查询参数使用正确的格式: `?参数名=值`。问号 `?` 不应被URL编码(`%3F`)。
107+
- 如果您看到像 `/%3Fproxyip=value` 这样的URL,这不会正确工作,应改为 `/?proxyip=value`
108+
- 本项目现已支持处理编码在路径中的查询参数,但建议使用标准格式以确保最佳兼容性。
109+
110+
- Ensure query parameters use the correct format: `?parameter=value`. The question mark `?` should not be URL encoded (`%3F`).
111+
- If you see URLs like `/%3Fproxyip=value`, this won't work correctly. Use `/?proxyip=value` instead.
112+
- This project now supports handling query parameters encoded in the path, but using the standard format is recommended for best compatibility.
113+
58114
### 非443端口配置 | Non-443 Port Configuration
59115

60116
1. 访问 (Visit) `https://proxyip.edtunnel.best/`

index.js

Lines changed: 134 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -66,28 +66,79 @@ export default {
6666
async fetch(request, env, _ctx) {
6767
try {
6868
const { UUID, PROXYIP, SOCKS5, SOCKS5_RELAY } = env;
69-
userID = UUID || userID;
70-
socks5Address = SOCKS5 || socks5Address;
71-
socks5Relay = SOCKS5_RELAY || socks5Relay;
69+
const url = new URL(request.url);
70+
71+
// 为当前请求创建配置副本,避免修改全局变量
72+
const requestConfig = {
73+
userID: UUID || userID,
74+
socks5Address: SOCKS5 || socks5Address,
75+
socks5Relay: SOCKS5_RELAY === 'true' || socks5Relay,
76+
proxyIP: null,
77+
proxyPort: null,
78+
enableSocks: false,
79+
parsedSocks5Address: {}
80+
};
81+
82+
// 获取正常URL参数
83+
let urlPROXYIP = url.searchParams.get('proxyip');
84+
let urlSOCKS5 = url.searchParams.get('socks5');
85+
let urlSOCKS5_RELAY = url.searchParams.get('socks5_relay');
86+
87+
// 检查编码在路径中的参数
88+
if (!urlPROXYIP && !urlSOCKS5 && !urlSOCKS5_RELAY) {
89+
const encodedParams = parseEncodedQueryParams(url.pathname);
90+
urlPROXYIP = urlPROXYIP || encodedParams.proxyip;
91+
urlSOCKS5 = urlSOCKS5 || encodedParams.socks5;
92+
urlSOCKS5_RELAY = urlSOCKS5_RELAY || encodedParams.socks5_relay;
93+
}
94+
95+
// 验证proxyip格式
96+
if (urlPROXYIP) {
97+
// 验证格式: domain:port 或 ip:port
98+
const proxyPattern = /^([a-zA-Z0-9][-a-zA-Z0-9.]*(\.[a-zA-Z0-9][-a-zA-Z0-9.]*)+|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[[0-9a-fA-F:]+\]):\d{1,5}$/;
99+
if (!proxyPattern.test(urlPROXYIP)) {
100+
console.warn('无效的proxyip格式:', urlPROXYIP);
101+
urlPROXYIP = null;
102+
}
103+
}
104+
105+
// 验证socks5格式
106+
if (urlSOCKS5) {
107+
// 基本验证 - 可以根据实际格式要求调整
108+
const socks5Pattern = /^(([^:@]+:[^:@]+@)?[a-zA-Z0-9][-a-zA-Z0-9.]*(\.[a-zA-Z0-9][-a-zA-Z0-9.]*)+|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):\d{1,5}$/;
109+
if (!socks5Pattern.test(urlSOCKS5)) {
110+
console.warn('无效的socks5格式:', urlSOCKS5);
111+
urlSOCKS5 = null;
112+
}
113+
}
114+
115+
// 应用URL参数到当前请求的配置
116+
requestConfig.socks5Address = urlSOCKS5 || requestConfig.socks5Address;
117+
requestConfig.socks5Relay = urlSOCKS5_RELAY === 'true' || requestConfig.socks5Relay;
118+
119+
// 记录参数值,用于调试
120+
console.log('配置参数:', requestConfig.userID, requestConfig.socks5Address, requestConfig.socks5Relay, urlPROXYIP);
121+
122+
// Handle proxy configuration for the current request
123+
const proxyConfig = handleProxyConfig(urlPROXYIP || PROXYIP);
124+
requestConfig.proxyIP = proxyConfig.ip;
125+
requestConfig.proxyPort = proxyConfig.port;
72126

73-
// Handle proxy configuration
74-
const proxyConfig = handleProxyConfig(PROXYIP);
75-
proxyIP = proxyConfig.ip;
76-
proxyPort = proxyConfig.port;
127+
// 记录最终使用的代理设置
128+
console.log('使用代理:', requestConfig.proxyIP, requestConfig.proxyPort);
77129

78-
if (socks5Address) {
130+
if (requestConfig.socks5Address) {
79131
try {
80-
const selectedSocks5 = selectRandomAddress(socks5Address);
81-
parsedSocks5Address = socks5AddressParser(selectedSocks5);
82-
enableSocks = true;
132+
const selectedSocks5 = selectRandomAddress(requestConfig.socks5Address);
133+
requestConfig.parsedSocks5Address = socks5AddressParser(selectedSocks5);
134+
requestConfig.enableSocks = true;
83135
} catch (err) {
84136
console.log(err.toString());
85-
enableSocks = false;
137+
requestConfig.enableSocks = false;
86138
}
87139
}
88140

89-
const userIDs = userID.includes(',') ? userID.split(',').map(id => id.trim()) : [userID];
90-
const url = new URL(request.url);
141+
const userIDs = requestConfig.userID.includes(',') ? requestConfig.userID.split(',').map(id => id.trim()) : [requestConfig.userID];
91142
const host = request.headers.get('Host');
92143
const requestedPath = url.pathname.substring(1); // Remove leading slash
93144
const matchingUserID = userIDs.length === 1 ?
@@ -110,7 +161,7 @@ export default {
110161
if (matchingUserID) {
111162
if (url.pathname === `/${matchingUserID}` || url.pathname === `/sub/${matchingUserID}`) {
112163
const isSubscription = url.pathname.startsWith('/sub/');
113-
const proxyAddresses = PROXYIP ? PROXYIP.split(',').map(addr => addr.trim()) : proxyIP;
164+
const proxyAddresses = PROXYIP ? PROXYIP.split(',').map(addr => addr.trim()) : requestConfig.proxyIP;
114165
const content = isSubscription ?
115166
GenSub(matchingUserID, host, proxyAddresses) :
116167
getConfig(matchingUserID, host, proxyAddresses);
@@ -129,7 +180,7 @@ export default {
129180
}
130181
return handleDefaultPath(url, request);
131182
} else {
132-
return await ProtocolOverWSHandler(request);
183+
return await ProtocolOverWSHandler(request, requestConfig);
133184
}
134185
} catch (err) {
135186
return new Response(err.toString());
@@ -395,9 +446,22 @@ async function handleDefaultPath(url, request) {
395446
/**
396447
* Handles protocol over WebSocket requests by creating a WebSocket pair, accepting the WebSocket connection, and processing the protocol header.
397448
* @param {import("@cloudflare/workers-types").Request} request - The incoming request object
449+
* @param {Object} config - The configuration for this request
398450
* @returns {Promise<Response>} WebSocket response
399451
*/
400-
async function ProtocolOverWSHandler(request) {
452+
async function ProtocolOverWSHandler(request, config = null) {
453+
// 如果没有传入配置,使用全局配置
454+
if (!config) {
455+
config = {
456+
userID,
457+
socks5Address,
458+
socks5Relay,
459+
proxyIP,
460+
proxyPort,
461+
enableSocks,
462+
parsedSocks5Address
463+
};
464+
}
401465

402466
/** @type {import("@cloudflare/workers-types").WebSocket[]} */
403467
// @ts-ignore
@@ -443,7 +507,7 @@ async function ProtocolOverWSHandler(request) {
443507
rawDataIndex,
444508
ProtocolVersion = new Uint8Array([0, 0]),
445509
isUDP,
446-
} = ProcessProtocolHeader(chunk, userID);
510+
} = ProcessProtocolHeader(chunk, config.userID);
447511
address = addressRemote;
448512
portWithRandomLog = `${portRemote}--${Math.random()} ${isUDP ? 'udp ' : 'tcp '
449513
} `;
@@ -467,7 +531,7 @@ async function ProtocolOverWSHandler(request) {
467531
if (isDns) {
468532
return handleDNSQuery(rawClientData, webSocket, ProtocolResponseHeader, log);
469533
}
470-
HandleTCPOutBound(remoteSocketWapper, addressType, addressRemote, portRemote, rawClientData, webSocket, ProtocolResponseHeader, log);
534+
HandleTCPOutBound(remoteSocketWapper, addressType, addressRemote, portRemote, rawClientData, webSocket, ProtocolResponseHeader, log, config);
471535
},
472536
close() {
473537
log(`readableWebSocketStream is close`);
@@ -497,15 +561,29 @@ async function ProtocolOverWSHandler(request) {
497561
* @param {WebSocket} webSocket - WebSocket connection
498562
* @param {Uint8Array} protocolResponseHeader - Protocol response header
499563
* @param {Function} log - Logging function
564+
* @param {Object} config - The configuration for this request
500565
*/
501-
async function HandleTCPOutBound(remoteSocket, addressType, addressRemote, portRemote, rawClientData, webSocket, protocolResponseHeader, log,) {
566+
async function HandleTCPOutBound(remoteSocket, addressType, addressRemote, portRemote, rawClientData, webSocket, protocolResponseHeader, log, config = null) {
567+
// 如果没有传入配置,使用全局配置
568+
if (!config) {
569+
config = {
570+
userID,
571+
socks5Address,
572+
socks5Relay,
573+
proxyIP,
574+
proxyPort,
575+
enableSocks,
576+
parsedSocks5Address
577+
};
578+
}
579+
502580
async function connectAndWrite(address, port, socks = false) {
503581
/** @type {import("@cloudflare/workers-types").Socket} */
504582
let tcpSocket;
505-
if (socks5Relay) {
506-
tcpSocket = await socks5Connect(addressType, address, port, log)
583+
if (config.socks5Relay) {
584+
tcpSocket = await socks5Connect(addressType, address, port, log, config.parsedSocks5Address)
507585
} else {
508-
tcpSocket = socks ? await socks5Connect(addressType, address, port, log)
586+
tcpSocket = socks ? await socks5Connect(addressType, address, port, log, config.parsedSocks5Address)
509587
: connect({
510588
hostname: address,
511589
port: port,
@@ -521,10 +599,11 @@ async function HandleTCPOutBound(remoteSocket, addressType, addressRemote, portR
521599

522600
// if the cf connect tcp socket have no incoming data, we retry to redirect ip
523601
async function retry() {
524-
if (enableSocks) {
602+
let tcpSocket;
603+
if (config.enableSocks) {
525604
tcpSocket = await connectAndWrite(addressRemote, portRemote, true);
526605
} else {
527-
tcpSocket = await connectAndWrite(proxyIP || addressRemote, proxyPort || portRemote, false);
606+
tcpSocket = await connectAndWrite(config.proxyIP || addressRemote, config.proxyPort || portRemote, false);
528607
}
529608
// no matter retry success or not, close websocket
530609
tcpSocket.closed.catch(error => {
@@ -871,10 +950,13 @@ async function handleDNSQuery(udpChunk, webSocket, protocolResponseHeader, log)
871950
* @param {string} addressRemote - Remote address
872951
* @param {number} portRemote - Remote port
873952
* @param {Function} log - Logging function
953+
* @param {Object} parsedSocks5Addr - Parsed SOCKS5 address information
874954
* @returns {Promise<Socket>} Connected socket
875955
*/
876-
async function socks5Connect(addressType, addressRemote, portRemote, log) {
877-
const { username, password, hostname, port } = parsedSocks5Address;
956+
async function socks5Connect(addressType, addressRemote, portRemote, log, parsedSocks5Addr = null) {
957+
// 如果没有传入解析的SOCKS5地址,使用全局的
958+
const { username, password, hostname, port } = parsedSocks5Addr || parsedSocks5Address;
959+
878960
// Connect to the SOCKS server
879961
const socket = connect({
880962
hostname,
@@ -1237,9 +1319,9 @@ function getConfig(userIDs, hostName, proxyIP) {
12371319
<h3>Best IP Configuration</h3>
12381320
<div class="input-group mb-3">
12391321
<select class="form-select" id="proxySelect" onchange="updateProxyConfig()">
1240-
${typeof proxyIP === 'string' ?
1241-
`<option value="${proxyIP}">${proxyIP}</option>` :
1242-
Array.from(proxyIP).map(proxy => `<option value="${proxy}">${proxy}</option>`).join('')}
1322+
${typeof proxyIP === 'string' ?
1323+
`<option value="${proxyIP}">${proxyIP}</option>` :
1324+
Array.from(proxyIP).map(proxy => `<option value="${proxy}">${proxy}</option>`).join('')}
12431325
</select>
12441326
</div>
12451327
<br>
@@ -1407,3 +1489,25 @@ function selectRandomAddress(addresses) {
14071489
addresses;
14081490
return addressArray[Math.floor(Math.random() * addressArray.length)];
14091491
}
1492+
1493+
/**
1494+
* 合并路径查询参数解析为通用函数
1495+
* @param {string} pathname - URL路径
1496+
* @returns {Object} 解析的参数对象
1497+
*/
1498+
function parseEncodedQueryParams(pathname) {
1499+
const params = {};
1500+
if (pathname.includes('%3F')) {
1501+
const encodedParamsMatch = pathname.match(/%3F(.+)$/);
1502+
if (encodedParamsMatch) {
1503+
const encodedParams = encodedParamsMatch[1];
1504+
const paramPairs = encodedParams.split('&');
1505+
1506+
for (const pair of paramPairs) {
1507+
const [key, value] = pair.split('=');
1508+
if (value) params[key] = decodeURIComponent(value);
1509+
}
1510+
}
1511+
}
1512+
return params;
1513+
}

0 commit comments

Comments
 (0)