@anonympins/fingerprint 0.7.0 → 0.7.1
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/CHANGELOG.md +17 -0
- package/README.md +1 -1
- package/composer.json +8 -1
- package/package.json +1 -1
- package/public/fingerprint-wordpress.zip +0 -0
- package/src/js/build-client.js +8 -10
- package/src/js/fingerprint.builder.js +37 -38
- package/src/js/fingerprint.client.js +1531 -1463
- package/src/js/fingerprint.client.obfuscated.js +1 -0
- package/src/js/fingerprint.js +152 -10
- package/src/js/fingerprint.utils.js +213 -213
- package/src/js/library.js +340 -376
- package/src/js/mongodb-store.js +1 -1
- package/src/js/optimization.worker.js +27 -27
- package/src/js/pow.solver.inline.js +154 -56
- package/src/js/pow.solver.js +180 -92
- package/src/js/pow.worker.js +48 -48
- package/src/js/redis-store.js +1 -1
- package/src/js/tests/fingerprint.client.init.test.js +146 -143
- package/src/js/tests/fingerprint.test.js +34 -7
- package/src/js/tests/ip-reputation.test.js +1 -0
- package/src/js/upow-model-task.js +14 -15
- package/src/php/AutoTuner.php +34 -34
- package/src/php/Challenge/ChallengeUtils.php +56 -10
- package/src/php/Config/SecurityProfiles.php +20 -0
- package/src/php/DirectFingerprint.php +190 -145
- package/src/php/FingerprintBuilder.php +29 -30
- package/src/php/FingerprintClient.php +147 -148
- package/src/php/FingerprintEngine.php +7 -5
- package/src/php/Ja3AnomalyDetector.php +33 -32
- package/src/php/Optimization/FunctionRegistry.php +7 -7
- package/src/php/Optimization/Optimization.php +24 -24
- package/src/php/Optimization/OptimizationOperators.php +26 -25
- package/src/php/Optimization/ProblemInitializers.php +52 -52
- package/src/php/ProblemManager.php +31 -34
- package/src/php/RequestContext.php +8 -1
- package/src/php/Store/IStore.php +7 -8
- package/src/php/Store/InMemoryStore.php +66 -66
- package/src/php/Store/MongoDbStore.php +5 -5
- package/src/php/Store/RedisStore.php +5 -5
- package/src/php/Store/StoreManager.php +35 -35
- package/src/php/Tests/ChallengeUtilsTest.php +453 -453
- package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
- package/src/php/Tests/FingerprintEngineTest.php +662 -662
- package/src/php/Tests/IpReputationTest.php +10 -0
- package/src/php/Tests/MaliciousPatternsTest.php +103 -103
- package/src/php/Tests/MetricsTest.php +97 -97
- package/src/php/Tests/ProblemManagerTest.php +376 -376
- package/src/php/Tests/QuicFingerprintTest.php +53 -53
- package/src/php/Tests/RequestUtilsTest.php +471 -471
- package/src/php/Tests/TLSClientHelloParserTest.php +177 -177
- package/src/php/Tests/config/ed25519_key.json +3 -3
- package/src/php/Utils/Env.php +49 -49
- package/src/php/Utils/MaliciousPatterns.php +74 -74
- package/src/php/Utils/MetricsManager.php +209 -209
- package/src/php/Utils/RequestUtils.php +129 -7
- package/src/php/Utils/TLSClientHelloParser.php +369 -369
- package/src/php/WordPress/WpDbStore.php +155 -0
- package/src/php/WordPress/fingerprint-wordpress.php +217 -0
- package/src/php/WordPress/package.php +123 -0
- package/src/php/bin/auto-tune.php +118 -118
|
@@ -1,214 +1,214 @@
|
|
|
1
|
-
import {cyrb53} from "./fingerprint.builder.js";
|
|
2
|
-
|
|
3
|
-
export function verifyZkpProof(yStr, tStr, sStr) {
|
|
4
|
-
try {
|
|
5
|
-
const y = BigInt('0x' + yStr);
|
|
6
|
-
const t = BigInt('0x' + tStr);
|
|
7
|
-
const s = BigInt('0x' + sStr);
|
|
8
|
-
|
|
9
|
-
const ZKP_P = 115792089237316195423570985008687907853269984665640564039457584007908834671663n;
|
|
10
|
-
const ZKP_G = 2n;
|
|
11
|
-
|
|
12
|
-
const cStr = ZKP_G.toString() + y.toString() + t.toString();
|
|
13
|
-
const hashHex = crypto.createHash('sha256').update(cStr).digest('hex');
|
|
14
|
-
const c = BigInt('0x' + hashHex) % ZKP_P;
|
|
15
|
-
|
|
16
|
-
return modPow(ZKP_G, s, ZKP_P) === (t * modPow(y, c, ZKP_P)) % ZKP_P;
|
|
17
|
-
} catch (e) {
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function safeJsonStringify(val) {
|
|
23
|
-
return JSON.stringify(val)
|
|
24
|
-
.replace(/</g, '\\u003c')
|
|
25
|
-
.replace(/>/g, '\\u003e')
|
|
26
|
-
.replace(/\u2028/g, '\\u2028')
|
|
27
|
-
.replace(/\u2029/g, '\\u2029');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function sanitizeRedirectPath(p) {
|
|
31
|
-
if (typeof p !== 'string') return '/';
|
|
32
|
-
let sanitized = p.replace(/[^a-zA-Z0-9\/.\-_~%?&=:@+,;]/g, '');
|
|
33
|
-
|
|
34
|
-
// Prevent protocol-relative redirects (e.g. //evil.com)
|
|
35
|
-
if (sanitized.startsWith('//')) {
|
|
36
|
-
sanitized = '/' + sanitized.replace(/^\/+/g, '');
|
|
37
|
-
}
|
|
38
|
-
// Prevent absolute redirects (e.g. http://evil.com)
|
|
39
|
-
if (/^https?:\/\//i.test(sanitized)) {
|
|
40
|
-
try {
|
|
41
|
-
const parsed = new URL(sanitized);
|
|
42
|
-
sanitized = parsed.pathname + parsed.search + parsed.hash;
|
|
43
|
-
} catch (e) {
|
|
44
|
-
sanitized = '/';
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
if (!sanitized.startsWith('/')) {
|
|
48
|
-
sanitized = '/' + sanitized;
|
|
49
|
-
}
|
|
50
|
-
return sanitized.replace(/^\/+/g, '/');
|
|
51
|
-
}
|
|
52
|
-
export function decodePolymorphicFingerprint(fpString, mapping) {
|
|
53
|
-
if (!fpString || !mapping || !mapping.keys) return fpString;
|
|
54
|
-
const reverseKeys = {};
|
|
55
|
-
for (const [orig, rand] of Object.entries(mapping.keys)) {
|
|
56
|
-
reverseKeys[rand] = orig;
|
|
57
|
-
}
|
|
58
|
-
const parts = fpString.split('|');
|
|
59
|
-
const mappedParts = parts.map(part => {
|
|
60
|
-
const pair = part.split(':');
|
|
61
|
-
if (pair.length === 2) {
|
|
62
|
-
const origKey = reverseKeys[pair[0]] || pair[0];
|
|
63
|
-
return `${origKey}:${pair[1]}`;
|
|
64
|
-
}
|
|
65
|
-
return part;
|
|
66
|
-
});
|
|
67
|
-
return mappedParts.join('|');
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* @private
|
|
73
|
-
* Deep merges two objects. The `source` object's properties overwrite the `target`'s.
|
|
74
|
-
* @param {object} target - The target object.
|
|
75
|
-
* @param {object} source - The source object.
|
|
76
|
-
* @returns {object} The merged object.
|
|
77
|
-
*/
|
|
78
|
-
export function deepMerge(target, source) {
|
|
79
|
-
const output = { ...target };
|
|
80
|
-
if (target && typeof target === 'object' && source && typeof source === 'object') {
|
|
81
|
-
Object.keys(source).forEach(key => {
|
|
82
|
-
if (source[key] && typeof source[key] === 'object' && key in target) {
|
|
83
|
-
output[key] = deepMerge(target[key], source[key]);
|
|
84
|
-
} else {
|
|
85
|
-
output[key] = source[key];
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
return output;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Creates a stable hash based on device characteristics, independent of the IP.
|
|
94
|
-
* This is our "level 2 fingerprint".
|
|
95
|
-
* @param {object} context - The request context.
|
|
96
|
-
* @returns {string} A hash representing the device.
|
|
97
|
-
*/
|
|
98
|
-
export function getHeaderSignature(context) {
|
|
99
|
-
if (!context.rawHeaders) return '';
|
|
100
|
-
const headerKeys = [];
|
|
101
|
-
for (let i = 0; i < context.rawHeaders.length; i += 2) {
|
|
102
|
-
headerKeys.push(context.rawHeaders[i]);
|
|
103
|
-
}
|
|
104
|
-
return cyrb53(headerKeys.sort().join(','));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Analyses a raw JA3 string.
|
|
109
|
-
* Format: "TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurveFormats"
|
|
110
|
-
* @param {string} ja3String
|
|
111
|
-
* @returns {object|null}
|
|
112
|
-
*/
|
|
113
|
-
export function parseJa3(ja3String) {
|
|
114
|
-
if (!ja3String || typeof ja3String !== 'string') {
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
const parts = ja3String.split(',');
|
|
118
|
-
if (parts.length !== 5) {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
return {
|
|
122
|
-
tlsVersion: parseInt(parts[0], 10),
|
|
123
|
-
ciphers: parts[1] !== '' ? parts[1].split('-').map(Number) : [],
|
|
124
|
-
extensions: parts[2] !== '' ? parts[2].split('-').map(Number) : [],
|
|
125
|
-
curves: parts[3] !== '' ? parts[3].split('-').map(Number) : [],
|
|
126
|
-
points: parts[4] !== '' ? parts[4].split('-').map(Number) : []
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export function modPow(base, exponent, modulus) {
|
|
131
|
-
if (modulus === 1n) return 0n;
|
|
132
|
-
let result = 1n;
|
|
133
|
-
base = base % modulus;
|
|
134
|
-
while (exponent > 0n) {
|
|
135
|
-
if (exponent % 2n === 1n) {
|
|
136
|
-
result = (result * base) % modulus;
|
|
137
|
-
}
|
|
138
|
-
exponent = exponent >> 1n;
|
|
139
|
-
base = (base * base) % modulus;
|
|
140
|
-
}
|
|
141
|
-
return result;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
export function hashNetwork(ip, prefix = 24) {
|
|
146
|
-
//
|
|
147
|
-
const parts = ip.split('.');
|
|
148
|
-
if (parts.length !== 4) return null;
|
|
149
|
-
const maskBytes = prefix / 8;
|
|
150
|
-
const network = parts.slice(0, maskBytes).join('.');
|
|
151
|
-
//
|
|
152
|
-
let hash = 0;
|
|
153
|
-
for (let i = 0; i < network.length; i++) {
|
|
154
|
-
const char = network.charCodeAt(i);
|
|
155
|
-
hash = ((hash << 5) - hash) + char;
|
|
156
|
-
hash = hash & hash;
|
|
157
|
-
}
|
|
158
|
-
return hash.toString(16);
|
|
159
|
-
}
|
|
160
|
-
export function normalizeReferer(referer) {
|
|
161
|
-
try {
|
|
162
|
-
const url = new URL(referer);
|
|
163
|
-
return `${url.protocol}//${url.hostname}`;
|
|
164
|
-
} catch {
|
|
165
|
-
return referer;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export function isPrivateIp(ip) {
|
|
170
|
-
//
|
|
171
|
-
const parts = ip.split('.');
|
|
172
|
-
if (parts.length !== 4) return false;
|
|
173
|
-
const first = parseInt(parts[0]);
|
|
174
|
-
return (first === 10) || (first === 172 && parseInt(parts[1]) >= 16 && parseInt(parts[1]) <= 31) || (first === 192 && parseInt(parts[1]) === 168);
|
|
175
|
-
}
|
|
176
|
-
//
|
|
177
|
-
export function parseUserAgent(ua) {
|
|
178
|
-
//
|
|
179
|
-
const result = {};
|
|
180
|
-
|
|
181
|
-
//
|
|
182
|
-
if (ua.includes('Chrome') && !ua.includes('Edg')) {
|
|
183
|
-
result.browser = 'Chrome';
|
|
184
|
-
const match = ua.match(/Chrome\/(\d+)/);
|
|
185
|
-
if (match) result.browser += `/${match[1]}`;
|
|
186
|
-
} else if (ua.includes('Firefox')) {
|
|
187
|
-
result.browser = 'Firefox';
|
|
188
|
-
const match = ua.match(/Firefox\/(\d+)/);
|
|
189
|
-
if (match) result.browser += `/${match[1]}`;
|
|
190
|
-
} else if (ua.includes('Safari') && !ua.includes('Chrome')) {
|
|
191
|
-
result.browser = 'Safari';
|
|
192
|
-
const match = ua.match(/Version\/(\d+)/);
|
|
193
|
-
if (match) result.browser += `/${match[1]}`;
|
|
194
|
-
} else if (ua.includes('Edg')) {
|
|
195
|
-
result.browser = 'Edge';
|
|
196
|
-
const match = ua.match(/Edg\/(\d+)/);
|
|
197
|
-
if (match) result.browser += `/${match[1]}`;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
//
|
|
201
|
-
if (ua.includes('Windows NT 10.0')) result.os = 'Windows 10';
|
|
202
|
-
else if (ua.includes('Windows NT 6.1')) result.os = 'Windows 7';
|
|
203
|
-
else if (ua.includes('Mac OS X')) result.os = 'macOS';
|
|
204
|
-
else if (ua.includes('Linux') && !ua.includes('Android')) result.os = 'Linux';
|
|
205
|
-
else if (ua.includes('Android')) result.os = 'Android';
|
|
206
|
-
else if (ua.includes('iPhone') || ua.includes('iPad')) result.os = 'iOS';
|
|
207
|
-
|
|
208
|
-
//
|
|
209
|
-
if (ua.includes('Mobile')) result.device = 'mobile';
|
|
210
|
-
else if (ua.includes('Tablet')) result.device = 'tablet';
|
|
211
|
-
else result.device = 'desktop';
|
|
212
|
-
|
|
213
|
-
return result;
|
|
1
|
+
import {cyrb53} from "./fingerprint.builder.js";
|
|
2
|
+
|
|
3
|
+
export function verifyZkpProof(yStr, tStr, sStr) {
|
|
4
|
+
try {
|
|
5
|
+
const y = BigInt('0x' + yStr);
|
|
6
|
+
const t = BigInt('0x' + tStr);
|
|
7
|
+
const s = BigInt('0x' + sStr);
|
|
8
|
+
|
|
9
|
+
const ZKP_P = 115792089237316195423570985008687907853269984665640564039457584007908834671663n;
|
|
10
|
+
const ZKP_G = 2n;
|
|
11
|
+
|
|
12
|
+
const cStr = ZKP_G.toString() + y.toString() + t.toString();
|
|
13
|
+
const hashHex = crypto.createHash('sha256').update(cStr).digest('hex');
|
|
14
|
+
const c = BigInt('0x' + hashHex) % ZKP_P;
|
|
15
|
+
|
|
16
|
+
return modPow(ZKP_G, s, ZKP_P) === (t * modPow(y, c, ZKP_P)) % ZKP_P;
|
|
17
|
+
} catch (e) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function safeJsonStringify(val) {
|
|
23
|
+
return JSON.stringify(val)
|
|
24
|
+
.replace(/</g, '\\u003c')
|
|
25
|
+
.replace(/>/g, '\\u003e')
|
|
26
|
+
.replace(/\u2028/g, '\\u2028')
|
|
27
|
+
.replace(/\u2029/g, '\\u2029');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function sanitizeRedirectPath(p) {
|
|
31
|
+
if (typeof p !== 'string') return '/';
|
|
32
|
+
let sanitized = p.replace(/[^a-zA-Z0-9\/.\-_~%?&=:@+,;]/g, '');
|
|
33
|
+
|
|
34
|
+
// Prevent protocol-relative redirects (e.g. //evil.com)
|
|
35
|
+
if (sanitized.startsWith('//')) {
|
|
36
|
+
sanitized = '/' + sanitized.replace(/^\/+/g, '');
|
|
37
|
+
}
|
|
38
|
+
// Prevent absolute redirects (e.g. http://evil.com)
|
|
39
|
+
if (/^https?:\/\//i.test(sanitized)) {
|
|
40
|
+
try {
|
|
41
|
+
const parsed = new URL(sanitized);
|
|
42
|
+
sanitized = parsed.pathname + parsed.search + parsed.hash;
|
|
43
|
+
} catch (e) {
|
|
44
|
+
sanitized = '/';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!sanitized.startsWith('/')) {
|
|
48
|
+
sanitized = '/' + sanitized;
|
|
49
|
+
}
|
|
50
|
+
return sanitized.replace(/^\/+/g, '/');
|
|
51
|
+
}
|
|
52
|
+
export function decodePolymorphicFingerprint(fpString, mapping) {
|
|
53
|
+
if (!fpString || !mapping || !mapping.keys) return fpString;
|
|
54
|
+
const reverseKeys = {};
|
|
55
|
+
for (const [orig, rand] of Object.entries(mapping.keys)) {
|
|
56
|
+
reverseKeys[rand] = orig;
|
|
57
|
+
}
|
|
58
|
+
const parts = fpString.split('|');
|
|
59
|
+
const mappedParts = parts.map(part => {
|
|
60
|
+
const pair = part.split(':');
|
|
61
|
+
if (pair.length === 2) {
|
|
62
|
+
const origKey = reverseKeys[pair[0]] || pair[0];
|
|
63
|
+
return `${origKey}:${pair[1]}`;
|
|
64
|
+
}
|
|
65
|
+
return part;
|
|
66
|
+
});
|
|
67
|
+
return mappedParts.join('|');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @private
|
|
73
|
+
* Deep merges two objects. The `source` object's properties overwrite the `target`'s.
|
|
74
|
+
* @param {object} target - The target object.
|
|
75
|
+
* @param {object} source - The source object.
|
|
76
|
+
* @returns {object} The merged object.
|
|
77
|
+
*/
|
|
78
|
+
export function deepMerge(target, source) {
|
|
79
|
+
const output = { ...target };
|
|
80
|
+
if (target && typeof target === 'object' && source && typeof source === 'object') {
|
|
81
|
+
Object.keys(source).forEach(key => {
|
|
82
|
+
if (source[key] && typeof source[key] === 'object' && key in target) {
|
|
83
|
+
output[key] = deepMerge(target[key], source[key]);
|
|
84
|
+
} else {
|
|
85
|
+
output[key] = source[key];
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return output;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Creates a stable hash based on device characteristics, independent of the IP.
|
|
94
|
+
* This is our "level 2 fingerprint".
|
|
95
|
+
* @param {object} context - The request context.
|
|
96
|
+
* @returns {string} A hash representing the device.
|
|
97
|
+
*/
|
|
98
|
+
export function getHeaderSignature(context) {
|
|
99
|
+
if (!context.rawHeaders) return '';
|
|
100
|
+
const headerKeys = [];
|
|
101
|
+
for (let i = 0; i < context.rawHeaders.length; i += 2) {
|
|
102
|
+
headerKeys.push(context.rawHeaders[i]);
|
|
103
|
+
}
|
|
104
|
+
return cyrb53(headerKeys.sort().join(','));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Analyses a raw JA3 string.
|
|
109
|
+
* Format: "TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurveFormats"
|
|
110
|
+
* @param {string} ja3String
|
|
111
|
+
* @returns {object|null}
|
|
112
|
+
*/
|
|
113
|
+
export function parseJa3(ja3String) {
|
|
114
|
+
if (!ja3String || typeof ja3String !== 'string') {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const parts = ja3String.split(',');
|
|
118
|
+
if (parts.length !== 5) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
tlsVersion: parseInt(parts[0], 10),
|
|
123
|
+
ciphers: parts[1] !== '' ? parts[1].split('-').map(Number) : [],
|
|
124
|
+
extensions: parts[2] !== '' ? parts[2].split('-').map(Number) : [],
|
|
125
|
+
curves: parts[3] !== '' ? parts[3].split('-').map(Number) : [],
|
|
126
|
+
points: parts[4] !== '' ? parts[4].split('-').map(Number) : []
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function modPow(base, exponent, modulus) {
|
|
131
|
+
if (modulus === 1n) return 0n;
|
|
132
|
+
let result = 1n;
|
|
133
|
+
base = base % modulus;
|
|
134
|
+
while (exponent > 0n) {
|
|
135
|
+
if (exponent % 2n === 1n) {
|
|
136
|
+
result = (result * base) % modulus;
|
|
137
|
+
}
|
|
138
|
+
exponent = exponent >> 1n;
|
|
139
|
+
base = (base * base) % modulus;
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
export function hashNetwork(ip, prefix = 24) {
|
|
146
|
+
// Network hash (/24 or /16 mask)
|
|
147
|
+
const parts = ip.split('.');
|
|
148
|
+
if (parts.length !== 4) return null;
|
|
149
|
+
const maskBytes = prefix / 8;
|
|
150
|
+
const network = parts.slice(0, maskBytes).join('.');
|
|
151
|
+
// Basic hash
|
|
152
|
+
let hash = 0;
|
|
153
|
+
for (let i = 0; i < network.length; i++) {
|
|
154
|
+
const char = network.charCodeAt(i);
|
|
155
|
+
hash = ((hash << 5) - hash) + char;
|
|
156
|
+
hash = hash & hash;
|
|
157
|
+
}
|
|
158
|
+
return hash.toString(16);
|
|
159
|
+
}
|
|
160
|
+
export function normalizeReferer(referer) {
|
|
161
|
+
try {
|
|
162
|
+
const url = new URL(referer);
|
|
163
|
+
return `${url.protocol}//${url.hostname}`;
|
|
164
|
+
} catch {
|
|
165
|
+
return referer;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function isPrivateIp(ip) {
|
|
170
|
+
// Check whether the IP address is private
|
|
171
|
+
const parts = ip.split('.');
|
|
172
|
+
if (parts.length !== 4) return false;
|
|
173
|
+
const first = parseInt(parts[0]);
|
|
174
|
+
return (first === 10) || (first === 172 && parseInt(parts[1]) >= 16 && parseInt(parts[1]) <= 31) || (first === 192 && parseInt(parts[1]) === 168);
|
|
175
|
+
}
|
|
176
|
+
// Utility parsing functions
|
|
177
|
+
export function parseUserAgent(ua) {
|
|
178
|
+
// Basic User-Agent parser
|
|
179
|
+
const result = {};
|
|
180
|
+
|
|
181
|
+
// Browser detection
|
|
182
|
+
if (ua.includes('Chrome') && !ua.includes('Edg')) {
|
|
183
|
+
result.browser = 'Chrome';
|
|
184
|
+
const match = ua.match(/Chrome\/(\d+)/);
|
|
185
|
+
if (match) result.browser += `/${match[1]}`;
|
|
186
|
+
} else if (ua.includes('Firefox')) {
|
|
187
|
+
result.browser = 'Firefox';
|
|
188
|
+
const match = ua.match(/Firefox\/(\d+)/);
|
|
189
|
+
if (match) result.browser += `/${match[1]}`;
|
|
190
|
+
} else if (ua.includes('Safari') && !ua.includes('Chrome')) {
|
|
191
|
+
result.browser = 'Safari';
|
|
192
|
+
const match = ua.match(/Version\/(\d+)/);
|
|
193
|
+
if (match) result.browser += `/${match[1]}`;
|
|
194
|
+
} else if (ua.includes('Edg')) {
|
|
195
|
+
result.browser = 'Edge';
|
|
196
|
+
const match = ua.match(/Edg\/(\d+)/);
|
|
197
|
+
if (match) result.browser += `/${match[1]}`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// OS detection
|
|
201
|
+
if (ua.includes('Windows NT 10.0')) result.os = 'Windows 10';
|
|
202
|
+
else if (ua.includes('Windows NT 6.1')) result.os = 'Windows 7';
|
|
203
|
+
else if (ua.includes('Mac OS X')) result.os = 'macOS';
|
|
204
|
+
else if (ua.includes('Linux') && !ua.includes('Android')) result.os = 'Linux';
|
|
205
|
+
else if (ua.includes('Android')) result.os = 'Android';
|
|
206
|
+
else if (ua.includes('iPhone') || ua.includes('iPad')) result.os = 'iOS';
|
|
207
|
+
|
|
208
|
+
// Device form-factor detection
|
|
209
|
+
if (ua.includes('Mobile')) result.device = 'mobile';
|
|
210
|
+
else if (ua.includes('Tablet')) result.device = 'tablet';
|
|
211
|
+
else result.device = 'desktop';
|
|
212
|
+
|
|
213
|
+
return result;
|
|
214
214
|
}
|