-
Notifications
You must be signed in to change notification settings - Fork 305
/
_worker.js
199 lines (192 loc) · 5.37 KB
/
_worker.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
function logError(request, message) {
console.error(
`${message}, clientIp: ${request.headers.get(
"cf-connecting-ip"
)}, user-agent: ${request.headers.get("user-agent")}, url: ${request.url}`
);
}
function createNewRequest(request, url, proxyHostname, originHostname) {
const newRequestHeaders = new Headers(request.headers);
for (const [key, value] of newRequestHeaders) {
if (value.includes(originHostname)) {
newRequestHeaders.set(
key,
value.replace(
new RegExp(`(?<!\\.)\\b${originHostname}\\b`, "g"),
proxyHostname
)
);
}
}
return new Request(url.toString(), {
method: request.method,
headers: newRequestHeaders,
body: request.body,
});
}
function setResponseHeaders(
originalResponse,
proxyHostname,
originHostname,
DEBUG
) {
const newResponseHeaders = new Headers(originalResponse.headers);
for (const [key, value] of newResponseHeaders) {
if (value.includes(proxyHostname)) {
newResponseHeaders.set(
key,
value.replace(
new RegExp(`(?<!\\.)\\b${proxyHostname}\\b`, "g"),
originHostname
)
);
}
}
if (DEBUG) {
newResponseHeaders.delete("content-security-policy");
}
return newResponseHeaders;
}
/**
* 替换内容
* @param originalResponse 响应
* @param proxyHostname 代理地址 hostname
* @param pathnameRegex 代理地址路径匹配的正则表达式
* @param originHostname 替换的字符串
* @returns {Promise<*>}
*/
async function replaceResponseText(
originalResponse,
proxyHostname,
pathnameRegex,
originHostname
) {
let text = await originalResponse.text();
if (pathnameRegex) {
pathnameRegex = pathnameRegex.replace(/^\^/, "");
return text.replace(
new RegExp(`((?<!\\.)\\b${proxyHostname}\\b)(${pathnameRegex})`, "g"),
`${originHostname}$2`
);
} else {
return text.replace(
new RegExp(`(?<!\\.)\\b${proxyHostname}\\b`, "g"),
originHostname
);
}
}
async function nginx() {
return `<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>`;
}
export default {
async fetch(request, env, ctx) {
try {
const {
PROXY_HOSTNAME,
PROXY_PROTOCOL = "https",
PATHNAME_REGEX,
UA_WHITELIST_REGEX,
UA_BLACKLIST_REGEX,
URL302,
IP_WHITELIST_REGEX,
IP_BLACKLIST_REGEX,
REGION_WHITELIST_REGEX,
REGION_BLACKLIST_REGEX,
DEBUG = false,
} = env;
const url = new URL(request.url);
const originHostname = url.hostname;
if (
!PROXY_HOSTNAME ||
(PATHNAME_REGEX && !new RegExp(PATHNAME_REGEX).test(url.pathname)) ||
(UA_WHITELIST_REGEX &&
!new RegExp(UA_WHITELIST_REGEX).test(
request.headers.get("user-agent").toLowerCase()
)) ||
(UA_BLACKLIST_REGEX &&
new RegExp(UA_BLACKLIST_REGEX).test(
request.headers.get("user-agent").toLowerCase()
)) ||
(IP_WHITELIST_REGEX &&
!new RegExp(IP_WHITELIST_REGEX).test(
request.headers.get("cf-connecting-ip")
)) ||
(IP_BLACKLIST_REGEX &&
new RegExp(IP_BLACKLIST_REGEX).test(
request.headers.get("cf-connecting-ip")
)) ||
(REGION_WHITELIST_REGEX &&
!new RegExp(REGION_WHITELIST_REGEX).test(
request.headers.get("cf-ipcountry")
)) ||
(REGION_BLACKLIST_REGEX &&
new RegExp(REGION_BLACKLIST_REGEX).test(
request.headers.get("cf-ipcountry")
))
) {
logError(request, "Invalid");
return URL302
? Response.redirect(URL302, 302)
: new Response(await nginx(), {
headers: {
"Content-Type": "text/html; charset=utf-8",
},
});
}
url.host = PROXY_HOSTNAME;
url.protocol = PROXY_PROTOCOL;
const newRequest = createNewRequest(
request,
url,
PROXY_HOSTNAME,
originHostname
);
const originalResponse = await fetch(newRequest);
const newResponseHeaders = setResponseHeaders(
originalResponse,
PROXY_HOSTNAME,
originHostname,
DEBUG
);
const contentType = newResponseHeaders.get("content-type") || "";
let body;
if (contentType.includes("text/")) {
body = await replaceResponseText(
originalResponse,
PROXY_HOSTNAME,
PATHNAME_REGEX,
originHostname
);
} else {
body = originalResponse.body;
}
return new Response(body, {
status: originalResponse.status,
headers: newResponseHeaders,
});
} catch (error) {
logError(request, `Fetch error: ${error.message}`);
return new Response("Internal Server Error", { status: 500 });
}
},
};