w-ipproxy 1.0.9 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/w-ipproxy.umd.js +2 -2
- package/dist/w-ipproxy.umd.js.map +1 -1
- package/docs/WIpProxy.html +1 -1
- package/docs/WIpProxy.mjs.html +38 -4
- package/docs/index.html +1 -1
- package/package.json +2 -2
- package/src/WIpProxy.mjs +37 -3
- package/test/anonymity.test.mjs +35 -4
package/docs/WIpProxy.html
CHANGED
|
@@ -709,7 +709,7 @@
|
|
|
709
709
|
<br class="clear">
|
|
710
710
|
|
|
711
711
|
<footer>
|
|
712
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
712
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 18 2026 13:44:53 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
713
713
|
</footer>
|
|
714
714
|
|
|
715
715
|
<script>prettyPrint();</script>
|
package/docs/WIpProxy.mjs.html
CHANGED
|
@@ -185,6 +185,40 @@ function WIpProxy(opt = {}) {
|
|
|
185
185
|
let prxsRaw = []
|
|
186
186
|
let kpPrx = {}
|
|
187
187
|
|
|
188
|
+
//_normalizeIp: 正規化單一IP字串, 去除[]包裹, ipv4之port, 及ipv4-mapped-ipv6之'::ffff:'前綴
|
|
189
|
+
function _normalizeIp(v) {
|
|
190
|
+
let s = trim(v)
|
|
191
|
+
if (s === '') {
|
|
192
|
+
return ''
|
|
193
|
+
}
|
|
194
|
+
if (s[0] === '[') {
|
|
195
|
+
//形如'[::1]:8080'或'[2001:db8::1]'
|
|
196
|
+
let k = s.indexOf(']')
|
|
197
|
+
if (k > 0) {
|
|
198
|
+
s = s.slice(1, k)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else if (s.split(':').length === 2) {
|
|
202
|
+
//僅單一冒號才視為'1.2.3.4:port', 多冒號為ipv6不可截斷
|
|
203
|
+
s = s.split(':')[0]
|
|
204
|
+
}
|
|
205
|
+
//ipv4-mapped-ipv6, 如'::ffff:1.2.3.4', 統一還原為ipv4
|
|
206
|
+
let m = s.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/i)
|
|
207
|
+
if (m) {
|
|
208
|
+
s = m[1]
|
|
209
|
+
}
|
|
210
|
+
return s
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
//_originHasIp: 檢查origin(可能為'ip1, ip2, ...')逐段正規化後是否含有指定IP
|
|
214
|
+
function _originHasIp(origin, ip) {
|
|
215
|
+
if (!isestr(ip)) {
|
|
216
|
+
return false
|
|
217
|
+
}
|
|
218
|
+
let vs = map(sep(origin, ','), (v) => _normalizeIp(v))
|
|
219
|
+
return vs.indexOf(ip) >= 0
|
|
220
|
+
}
|
|
221
|
+
|
|
188
222
|
//myRealIp: 本機真實公網IP, 用於匿名性檢測時比對代理egress是否洩漏
|
|
189
223
|
let myRealIp = ''
|
|
190
224
|
let myRealIpReady = false
|
|
@@ -199,8 +233,8 @@ function WIpProxy(opt = {}) {
|
|
|
199
233
|
let res = await axios.get('https://httpbin.org/ip', { timeout: 10000 })
|
|
200
234
|
let origin = get(res, 'data.origin', '')
|
|
201
235
|
if (isestr(origin)) {
|
|
202
|
-
//origin可能為"1.2.3.4"
|
|
203
|
-
myRealIp = origin
|
|
236
|
+
//origin可能為"1.2.3.4", "1.2.3.4:port", 或"::ffff:1.2.3.4"(部分環境如雲端runner為ipv4-mapped-ipv6), 取首個逗號前再正規化
|
|
237
|
+
myRealIp = _normalizeIp(sep(origin, ',')[0])
|
|
204
238
|
}
|
|
205
239
|
}
|
|
206
240
|
catch (err) {
|
|
@@ -239,7 +273,7 @@ function WIpProxy(opt = {}) {
|
|
|
239
273
|
if (!isestr(origin)) {
|
|
240
274
|
return { ok: false, reason: 'anonymity non-json' }
|
|
241
275
|
}
|
|
242
|
-
if (origin
|
|
276
|
+
if (_originHasIp(origin, myRealIp)) {
|
|
243
277
|
return { ok: false, reason: `leaks real IP` }
|
|
244
278
|
}
|
|
245
279
|
}
|
|
@@ -541,7 +575,7 @@ export default WIpProxy
|
|
|
541
575
|
<br class="clear">
|
|
542
576
|
|
|
543
577
|
<footer>
|
|
544
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
578
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 18 2026 13:44:53 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
545
579
|
</footer>
|
|
546
580
|
|
|
547
581
|
<script>prettyPrint();</script>
|
package/docs/index.html
CHANGED
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
<br class="clear">
|
|
72
72
|
|
|
73
73
|
<footer>
|
|
74
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
74
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 18 2026 13:44:53 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
75
75
|
</footer>
|
|
76
76
|
|
|
77
77
|
<script>prettyPrint();</script>
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "w-ipproxy",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"main": "dist/w-ipproxy.umd.js",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@hapi/hapi": "^21.4.8",
|
|
7
7
|
"axios": "^1.13.6",
|
|
8
8
|
"cheerio": "^1.2.0",
|
|
9
|
-
"wsemi": "^1.8.
|
|
9
|
+
"wsemi": "^1.8.75"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"w-package-tools": "^1.1.12"
|
package/src/WIpProxy.mjs
CHANGED
|
@@ -138,6 +138,40 @@ function WIpProxy(opt = {}) {
|
|
|
138
138
|
let prxsRaw = []
|
|
139
139
|
let kpPrx = {}
|
|
140
140
|
|
|
141
|
+
//_normalizeIp: 正規化單一IP字串, 去除[]包裹, ipv4之port, 及ipv4-mapped-ipv6之'::ffff:'前綴
|
|
142
|
+
function _normalizeIp(v) {
|
|
143
|
+
let s = trim(v)
|
|
144
|
+
if (s === '') {
|
|
145
|
+
return ''
|
|
146
|
+
}
|
|
147
|
+
if (s[0] === '[') {
|
|
148
|
+
//形如'[::1]:8080'或'[2001:db8::1]'
|
|
149
|
+
let k = s.indexOf(']')
|
|
150
|
+
if (k > 0) {
|
|
151
|
+
s = s.slice(1, k)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else if (s.split(':').length === 2) {
|
|
155
|
+
//僅單一冒號才視為'1.2.3.4:port', 多冒號為ipv6不可截斷
|
|
156
|
+
s = s.split(':')[0]
|
|
157
|
+
}
|
|
158
|
+
//ipv4-mapped-ipv6, 如'::ffff:1.2.3.4', 統一還原為ipv4
|
|
159
|
+
let m = s.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/i)
|
|
160
|
+
if (m) {
|
|
161
|
+
s = m[1]
|
|
162
|
+
}
|
|
163
|
+
return s
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
//_originHasIp: 檢查origin(可能為'ip1, ip2, ...')逐段正規化後是否含有指定IP
|
|
167
|
+
function _originHasIp(origin, ip) {
|
|
168
|
+
if (!isestr(ip)) {
|
|
169
|
+
return false
|
|
170
|
+
}
|
|
171
|
+
let vs = map(sep(origin, ','), (v) => _normalizeIp(v))
|
|
172
|
+
return vs.indexOf(ip) >= 0
|
|
173
|
+
}
|
|
174
|
+
|
|
141
175
|
//myRealIp: 本機真實公網IP, 用於匿名性檢測時比對代理egress是否洩漏
|
|
142
176
|
let myRealIp = ''
|
|
143
177
|
let myRealIpReady = false
|
|
@@ -152,8 +186,8 @@ function WIpProxy(opt = {}) {
|
|
|
152
186
|
let res = await axios.get('https://httpbin.org/ip', { timeout: 10000 })
|
|
153
187
|
let origin = get(res, 'data.origin', '')
|
|
154
188
|
if (isestr(origin)) {
|
|
155
|
-
//origin可能為"1.2.3.4"
|
|
156
|
-
myRealIp = origin
|
|
189
|
+
//origin可能為"1.2.3.4", "1.2.3.4:port", 或"::ffff:1.2.3.4"(部分環境如雲端runner為ipv4-mapped-ipv6), 取首個逗號前再正規化
|
|
190
|
+
myRealIp = _normalizeIp(sep(origin, ',')[0])
|
|
157
191
|
}
|
|
158
192
|
}
|
|
159
193
|
catch (err) {
|
|
@@ -192,7 +226,7 @@ function WIpProxy(opt = {}) {
|
|
|
192
226
|
if (!isestr(origin)) {
|
|
193
227
|
return { ok: false, reason: 'anonymity non-json' }
|
|
194
228
|
}
|
|
195
|
-
if (origin
|
|
229
|
+
if (_originHasIp(origin, myRealIp)) {
|
|
196
230
|
return { ok: false, reason: `leaks real IP` }
|
|
197
231
|
}
|
|
198
232
|
}
|
package/test/anonymity.test.mjs
CHANGED
|
@@ -4,6 +4,37 @@ import https from 'https'
|
|
|
4
4
|
import wip from '../src/WIpProxy.mjs'
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
//正規化單一IP字串, 去除[]包裹, ipv4之port, 及ipv4-mapped-ipv6之'::ffff:'前綴
|
|
8
|
+
function normalizeIp(v) {
|
|
9
|
+
let s = String(v || '').trim()
|
|
10
|
+
if (s === '') {
|
|
11
|
+
return ''
|
|
12
|
+
}
|
|
13
|
+
if (s[0] === '[') {
|
|
14
|
+
let k = s.indexOf(']')
|
|
15
|
+
if (k > 0) {
|
|
16
|
+
s = s.slice(1, k)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
else if (s.split(':').length === 2) {
|
|
20
|
+
s = s.split(':')[0]
|
|
21
|
+
}
|
|
22
|
+
let m = s.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/i)
|
|
23
|
+
if (m) {
|
|
24
|
+
s = m[1]
|
|
25
|
+
}
|
|
26
|
+
return s
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//檢查origin(可能為'ip1, ip2, ...')逐段正規化後是否含有指定IP
|
|
30
|
+
function originHasIp(origin, ip) {
|
|
31
|
+
if (ip === '') {
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
return String(origin).split(',').map((v) => normalizeIp(v)).indexOf(ip) >= 0
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
7
38
|
describe('anonymity (elite + anonymityCheck)', function() {
|
|
8
39
|
this.timeout(240 * 1000) //4min, 因為getProxiesOnce加上獨立leak驗證本身就耗時
|
|
9
40
|
|
|
@@ -12,10 +43,10 @@ describe('anonymity (elite + anonymityCheck)', function() {
|
|
|
12
43
|
|
|
13
44
|
before(async function() {
|
|
14
45
|
|
|
15
|
-
//取本機真實公網IP
|
|
46
|
+
//取本機真實公網IP, 各環境格式不一(如雲端runner為'::ffff:1.2.3.4'), 須正規化後才可比對
|
|
16
47
|
try {
|
|
17
48
|
let r = await axios.get('https://httpbin.org/ip', { timeout: 10000 })
|
|
18
|
-
myIp = r.data?.origin || ''
|
|
49
|
+
myIp = normalizeIp(String(r.data?.origin || '').split(',')[0])
|
|
19
50
|
}
|
|
20
51
|
catch (err) {
|
|
21
52
|
this.skip()
|
|
@@ -57,7 +88,7 @@ describe('anonymity (elite + anonymityCheck)', function() {
|
|
|
57
88
|
return { proxy: p.proxy, ok: true, reason: 'non-json, ignored' }
|
|
58
89
|
}
|
|
59
90
|
//回傳origin含本機真實IP即代表該代理洩漏
|
|
60
|
-
let leaks = origin
|
|
91
|
+
let leaks = originHasIp(origin, myIp)
|
|
61
92
|
return { proxy: p.proxy, ok: !leaks, origin }
|
|
62
93
|
}
|
|
63
94
|
catch (err) {
|
|
@@ -112,7 +143,7 @@ describe('anonymity (elite + anonymityCheck)', function() {
|
|
|
112
143
|
}
|
|
113
144
|
let bad = []
|
|
114
145
|
for (let k of LEAK_IP_HEADERS) {
|
|
115
|
-
if (k in hs && hs[k]
|
|
146
|
+
if (k in hs && originHasIp(hs[k], myIp)) {
|
|
116
147
|
bad.push(`${k}=${hs[k]}`)
|
|
117
148
|
}
|
|
118
149
|
}
|