@smpx/koa-request 0.2.2 → 0.2.6
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/Request.js +111 -9
- package/package.json +7 -7
- package/.lh/.gitignore.json +0 -18
- package/.lh/.lhignore +0 -6
- package/.lh/IPRange.js.json +0 -18
- package/.lh/Request.js.json +0 -94
- package/.lh/postinstall.js.json +0 -22
package/Request.js
CHANGED
|
@@ -240,6 +240,12 @@ class Request {
|
|
|
240
240
|
return paramValue;
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Get parameter as an integer (from query or body)
|
|
245
|
+
* @param {string} key
|
|
246
|
+
* @param {number} defaultValue
|
|
247
|
+
* @returns {number}
|
|
248
|
+
*/
|
|
243
249
|
paramInt(key, defaultValue = 0) {
|
|
244
250
|
const value = this.param(key, null);
|
|
245
251
|
if (value === null) return defaultValue;
|
|
@@ -252,6 +258,15 @@ class Request {
|
|
|
252
258
|
return intVal;
|
|
253
259
|
}
|
|
254
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Get parameter as a boolean
|
|
263
|
+
* Only '0', 'false', 'no', 'off' are considered falsy
|
|
264
|
+
* empty string is truthy, so url?param would be truthy
|
|
265
|
+
* but url?param=0 or url?param=false would be falsy
|
|
266
|
+
* @param {string} key
|
|
267
|
+
* @param {boolean} defaultValue
|
|
268
|
+
* @returns {boolean}
|
|
269
|
+
*/
|
|
255
270
|
paramBool(key, defaultValue = false) {
|
|
256
271
|
const value = this.param(key, null);
|
|
257
272
|
if (value === null) return defaultValue;
|
|
@@ -267,6 +282,13 @@ class Request {
|
|
|
267
282
|
return true;
|
|
268
283
|
}
|
|
269
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Get the parameter as an id.
|
|
287
|
+
* id any is a max 20 character long alphanumeric string
|
|
288
|
+
* @param {string} key
|
|
289
|
+
* @param {string} defaultValue
|
|
290
|
+
* @returns {string}
|
|
291
|
+
*/
|
|
270
292
|
paramId(key, defaultValue = '') {
|
|
271
293
|
const value = this.param(key, null);
|
|
272
294
|
if (value === null) return defaultValue;
|
|
@@ -295,10 +317,20 @@ class Request {
|
|
|
295
317
|
});
|
|
296
318
|
}
|
|
297
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Get the file with this param name
|
|
322
|
+
* @param {string} key
|
|
323
|
+
* @returns
|
|
324
|
+
*/
|
|
298
325
|
file(key) {
|
|
299
326
|
return this.files(key)[0] || null;
|
|
300
327
|
}
|
|
301
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Get all the files with this param name
|
|
331
|
+
* @param {string} key
|
|
332
|
+
* @returns {array}
|
|
333
|
+
*/
|
|
302
334
|
files(key) {
|
|
303
335
|
if (this.ctx.request.files && this.ctx.request.files[key]) {
|
|
304
336
|
const result = this.ctx.request.files[key];
|
|
@@ -309,6 +341,11 @@ class Request {
|
|
|
309
341
|
return [];
|
|
310
342
|
}
|
|
311
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Get the bearer token of the request.
|
|
346
|
+
* Authorization: Bearer abc would give abc as bearer token
|
|
347
|
+
* @returns {string}
|
|
348
|
+
*/
|
|
312
349
|
bearerToken() {
|
|
313
350
|
const authorization = this.ctx.headers.authorization;
|
|
314
351
|
if (!authorization) return '';
|
|
@@ -320,12 +357,19 @@ class Request {
|
|
|
320
357
|
return parts[1];
|
|
321
358
|
}
|
|
322
359
|
|
|
360
|
+
/**
|
|
361
|
+
* Get the api token of the request.
|
|
362
|
+
* API token can be sent as a header x-api-token
|
|
363
|
+
* @returns {string}
|
|
364
|
+
*/
|
|
323
365
|
apiToken() {
|
|
324
366
|
return this.header('x-api-token');
|
|
325
367
|
}
|
|
326
368
|
|
|
327
|
-
|
|
328
|
-
|
|
369
|
+
/**
|
|
370
|
+
* initialize a request
|
|
371
|
+
* set important cookies and all
|
|
372
|
+
*/
|
|
329
373
|
async init() {
|
|
330
374
|
// don't allow other domains to embed our iframe
|
|
331
375
|
if (isProduction) {
|
|
@@ -346,10 +390,18 @@ class Request {
|
|
|
346
390
|
}
|
|
347
391
|
}
|
|
348
392
|
|
|
393
|
+
/**
|
|
394
|
+
* check whether the request is http or https
|
|
395
|
+
* isHttp returns false if the request is https, true otherwise
|
|
396
|
+
* @returns {boolean}
|
|
397
|
+
*/
|
|
349
398
|
isHttp() {
|
|
350
399
|
if (this._isHttp === undefined) {
|
|
351
400
|
const ctx = this.ctx;
|
|
352
401
|
this._isHttp = (ctx.headers.origin || '').startsWith('http:') || (ctx.headers.host || '').includes(':');
|
|
402
|
+
if (!this._isHttp && this.ctx.protocol !== 'https') {
|
|
403
|
+
this.ctx.cookies.secure = true;
|
|
404
|
+
}
|
|
353
405
|
}
|
|
354
406
|
return this._isHttp;
|
|
355
407
|
}
|
|
@@ -363,6 +415,7 @@ class Request {
|
|
|
363
415
|
*/
|
|
364
416
|
|
|
365
417
|
/**
|
|
418
|
+
* Get or set a cookie
|
|
366
419
|
* @template {string | number | boolean | undefined} V
|
|
367
420
|
* @param {string} name
|
|
368
421
|
* @param {V} value
|
|
@@ -418,7 +471,7 @@ class Request {
|
|
|
418
471
|
}
|
|
419
472
|
|
|
420
473
|
const isHttp = this.isHttp();
|
|
421
|
-
if (options
|
|
474
|
+
if (!('secure' in options) && !isHttp) {
|
|
422
475
|
options.secure = true;
|
|
423
476
|
}
|
|
424
477
|
|
|
@@ -460,18 +513,35 @@ class Request {
|
|
|
460
513
|
return key ? this._trackingHeader[key] : this._trackingHeader;
|
|
461
514
|
}
|
|
462
515
|
|
|
516
|
+
/**
|
|
517
|
+
* Get the user agent of the request.
|
|
518
|
+
* @returns {string}
|
|
519
|
+
*/
|
|
463
520
|
userAgent() {
|
|
464
521
|
return this.trackingHeader('user-agent') || this.header('user-agent');
|
|
465
522
|
}
|
|
466
523
|
|
|
524
|
+
/**
|
|
525
|
+
* Get the referer of the request.
|
|
526
|
+
* @returns {string}
|
|
527
|
+
*/
|
|
467
528
|
referer() {
|
|
468
529
|
return this.trackingHeader('referer') || this.header('referer');
|
|
469
530
|
}
|
|
470
531
|
|
|
532
|
+
/**
|
|
533
|
+
* Get the referer name of the request.
|
|
534
|
+
* @returns {string}
|
|
535
|
+
*/
|
|
471
536
|
refererName() {
|
|
472
537
|
return this._refererName;
|
|
473
538
|
}
|
|
474
539
|
|
|
540
|
+
/**
|
|
541
|
+
* Parse the user agent with ua-parser-js and return the result
|
|
542
|
+
* Returns {ua: '', browser: {}, cpu: {}, device: {}, engine: {}, os: {} }
|
|
543
|
+
* @returns {object}
|
|
544
|
+
*/
|
|
475
545
|
parseUserAgent() {
|
|
476
546
|
if (!this._ua) {
|
|
477
547
|
this._ua = uaParser.setUA(this.userAgent()).getResult() || {};
|
|
@@ -479,19 +549,31 @@ class Request {
|
|
|
479
549
|
return this._ua;
|
|
480
550
|
}
|
|
481
551
|
|
|
552
|
+
/**
|
|
553
|
+
* Get the browser name
|
|
554
|
+
* @returns {string}
|
|
555
|
+
*/
|
|
482
556
|
browser() {
|
|
483
557
|
const ua = this.parseUserAgent();
|
|
484
558
|
const deviceType = (ua && ua.device && ua.device.type) || '';
|
|
485
559
|
const browserName = (ua && ua.browser && ua.browser.name) || '';
|
|
486
560
|
|
|
487
561
|
if (deviceType === 'mobile') {
|
|
488
|
-
|
|
489
|
-
|
|
562
|
+
switch (browserName) {
|
|
563
|
+
case 'Chrome': return 'Chrome Mobile';
|
|
564
|
+
case 'Firefox': return 'Firefox Mobile';
|
|
565
|
+
case 'Safari': return 'Safari Mobile';
|
|
566
|
+
case 'Mobile Safari': return 'Safari Mobile';
|
|
567
|
+
}
|
|
490
568
|
}
|
|
491
569
|
|
|
492
570
|
return browserName;
|
|
493
571
|
}
|
|
494
572
|
|
|
573
|
+
/**
|
|
574
|
+
* Get the browser name + version (eg. Chrome 96.1.0.110)
|
|
575
|
+
* @returns {string}
|
|
576
|
+
*/
|
|
495
577
|
browserVersion() {
|
|
496
578
|
const ua = this.parseUserAgent();
|
|
497
579
|
let browerVersion = (ua.browser && ua.browser.version) || '';
|
|
@@ -499,17 +581,29 @@ class Request {
|
|
|
499
581
|
return this.browser() + browerVersion;
|
|
500
582
|
}
|
|
501
583
|
|
|
584
|
+
/**
|
|
585
|
+
* Get the browser version only (eg. 96.1.0.110)
|
|
586
|
+
* @returns {string}
|
|
587
|
+
*/
|
|
502
588
|
browserVersionRaw() {
|
|
503
589
|
const ua = this.parseUserAgent();
|
|
504
590
|
const version = (ua.browser && ua.browser.version) || '';
|
|
505
591
|
return String(version);
|
|
506
592
|
}
|
|
507
593
|
|
|
594
|
+
/**
|
|
595
|
+
* Get the os name (eg. Windows)
|
|
596
|
+
* @returns {string}
|
|
597
|
+
*/
|
|
508
598
|
os() {
|
|
509
599
|
const ua = this.parseUserAgent();
|
|
510
600
|
return (ua.os && ua.os.name) || '';
|
|
511
601
|
}
|
|
512
602
|
|
|
603
|
+
/**
|
|
604
|
+
* Get the os name + version (eg. Windows 11)
|
|
605
|
+
* @returns {string}
|
|
606
|
+
*/
|
|
513
607
|
osVersion() {
|
|
514
608
|
const ua = this.parseUserAgent();
|
|
515
609
|
let osVersion = (ua.os && ua.os.version) || '';
|
|
@@ -517,6 +611,10 @@ class Request {
|
|
|
517
611
|
return this.os() + osVersion;
|
|
518
612
|
}
|
|
519
613
|
|
|
614
|
+
/**
|
|
615
|
+
* Get the header sm-user-agent
|
|
616
|
+
* @returns {string}
|
|
617
|
+
*/
|
|
520
618
|
smUserAgent() {
|
|
521
619
|
return this.header('sm-user-agent');
|
|
522
620
|
}
|
|
@@ -847,6 +945,7 @@ class Request {
|
|
|
847
945
|
}
|
|
848
946
|
|
|
849
947
|
/**
|
|
948
|
+
* Get the ip of the request
|
|
850
949
|
* @returns {string}
|
|
851
950
|
*/
|
|
852
951
|
ip() {
|
|
@@ -868,6 +967,7 @@ class Request {
|
|
|
868
967
|
}
|
|
869
968
|
|
|
870
969
|
/**
|
|
970
|
+
* Get the real ip of the request (after considering x-forwarded-for)
|
|
871
971
|
* @returns {string}
|
|
872
972
|
*/
|
|
873
973
|
realIP() {
|
|
@@ -900,14 +1000,16 @@ class Request {
|
|
|
900
1000
|
}
|
|
901
1001
|
|
|
902
1002
|
/**
|
|
903
|
-
* Is being proxied through local proxy
|
|
1003
|
+
* Is the request being proxied through local proxy
|
|
1004
|
+
* @returns {boolean}
|
|
904
1005
|
*/
|
|
905
1006
|
isProxied() {
|
|
906
1007
|
return this.header('x-real-ip') && this.ctx.ip.endsWith('127.0.0.1');
|
|
907
1008
|
}
|
|
908
1009
|
|
|
909
1010
|
/**
|
|
910
|
-
* Is request being proxied through an external proxy (like chrome data-saver)
|
|
1011
|
+
* Is the request being proxied through an external proxy (like chrome data-saver)
|
|
1012
|
+
* @returns {boolean}
|
|
911
1013
|
*/
|
|
912
1014
|
isVia() {
|
|
913
1015
|
return this.header('via') && this.realIP() === this.ip();
|
|
@@ -1128,8 +1230,8 @@ class Request {
|
|
|
1128
1230
|
refererSource = host;
|
|
1129
1231
|
}
|
|
1130
1232
|
|
|
1131
|
-
const query = refererUri.
|
|
1132
|
-
const term = query.q || query.searchfor || query.pq || 'not_available';
|
|
1233
|
+
const query = refererUri.searchParams;
|
|
1234
|
+
const term = query.get('q') || query.get('searchfor') || query.get('pq') || 'not_available';
|
|
1133
1235
|
|
|
1134
1236
|
return {
|
|
1135
1237
|
name: host,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smpx/koa-request",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Handle basic tasks for koajs",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"ip": "^1.1.5",
|
|
34
34
|
"koa-basic-auth": "^4.0.0",
|
|
35
|
-
"koa-body": "^4.
|
|
36
|
-
"koa-send": "^5.0.
|
|
37
|
-
"koa2-ratelimit": "^0.9.
|
|
38
|
-
"maxmind": "^4.
|
|
39
|
-
"tar": "^6.
|
|
40
|
-
"ua-parser-js": "^0.
|
|
35
|
+
"koa-body": "^4.2.0",
|
|
36
|
+
"koa-send": "^5.0.1",
|
|
37
|
+
"koa2-ratelimit": "^0.9.1",
|
|
38
|
+
"maxmind": "^4.3.3",
|
|
39
|
+
"tar": "^6.1.11",
|
|
40
|
+
"ua-parser-js": "^1.0.2"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"eslint": "^5.7.0",
|
package/.lh/.gitignore.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"sourceFile": ".gitignore",
|
|
3
|
-
"activeCommit": 0,
|
|
4
|
-
"commits": [
|
|
5
|
-
{
|
|
6
|
-
"activePatchIndex": 0,
|
|
7
|
-
"patches": [
|
|
8
|
-
{
|
|
9
|
-
"date": 1622045206905,
|
|
10
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n"
|
|
11
|
-
}
|
|
12
|
-
],
|
|
13
|
-
"date": 1622045206905,
|
|
14
|
-
"name": "Commit-0",
|
|
15
|
-
"content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n# next.js build output\n.next\n\n# lock files\npackage-lock.json\nyarn.lock\n\nprivate\ndist\n\n# geoip files\nGeoLite2-City.mmdb\n\n# local history (vs code extension - xyz.local-history)\n.history/*\n.lh\n"
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
}
|
package/.lh/.lhignore
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
# list file to not track by the local-history extension. comment line starts with a '#' character
|
|
2
|
-
# each line describe a regular expression pattern (search for "Javascript regex")
|
|
3
|
-
# it will relate to the workspace directory root. for example:
|
|
4
|
-
# ".*\.txt" ignores any file with "txt" extension
|
|
5
|
-
# "/test/.*" ignores all the files under the "test" directory
|
|
6
|
-
# ".*/test/.*" ignores all the files under any "test" directory (even under sub-folders)
|
package/.lh/IPRange.js.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"sourceFile": "IPRange.js",
|
|
3
|
-
"activeCommit": 0,
|
|
4
|
-
"commits": [
|
|
5
|
-
{
|
|
6
|
-
"activePatchIndex": 0,
|
|
7
|
-
"patches": [
|
|
8
|
-
{
|
|
9
|
-
"date": 1622576413980,
|
|
10
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n"
|
|
11
|
-
}
|
|
12
|
-
],
|
|
13
|
-
"date": 1622576413980,
|
|
14
|
-
"name": "Commit-0",
|
|
15
|
-
"content": "const ipLib = require('ip');\n\nconst PRIVATE_IPS = [\n\t'0.0.0.0/8',\n\t'240.0.0.0/4',\n\t'10.0.0.0/8',\n\t'127.0.0.0/8',\n\t'169.254.0.0/16',\n\t'172.16.0.0/12',\n\t'192.168.0.0/16',\n];\n\n// Source: https://www.lifewire.com/what-is-the-ip-address-of-google-818153\nconst GOOGLE_IPS = [\n\t'64.233.160.0 - 64.233.191.255',\n\t'66.102.0.0 - 66.102.15.255',\n\t'66.249.64.0 - 66.249.95.255',\n\t'72.14.192.0 - 72.14.255.255',\n\t'74.125.0.0 - 74.125.255.255',\n\t'209.85.128.0 - 209.85.255.255',\n\t'216.239.32.0 - 216.239.63.255',\n\t//\n\t'64.68.90.1 - 64.68.90.255',\n\t'64.233.173.193 - 64.233.173.255',\n\t'66.249.64.1 - 66.249.79.255',\n\t'216.239.33.96 - 216.239.59.128',\n];\n\nconst SEARCH_IPS = [\n\t'178.154.128.0/17', // YANDAX\n\t'87.250.224.0/19',\t// YANDAX\n\t'157.55.0.0/16',\t// Microsoft\n\t'207.46.0.0/19',\t// Microsoft\n\t'17.0.0.0/8',\t\t// Apple\n];\n\nlet compiledPrivateIps;\nlet compiledGoogleIps;\nlet compiledSearchIps;\nlet publicIp;\nlet privateIp;\n\nfunction ipRangeCheckRegex(cidrs) {\n\tconst cidrJoin = cidrs.map((cidr) => {\n\t\tif (!cidr.endsWith('.')) cidr += '.';\n\t\treturn cidr.replace(/\\./g, '\\\\.');\n\t}).join('|');\n\n\treturn new RegExp(`^(?:${cidrJoin})`);\n}\n\nfunction compile(cidrs) {\n\tif (!Array.isArray(cidrs)) {\n\t\tcidrs = [cidrs];\n\t}\n\n\tconst ranges = [];\n\tconst startsWith = [];\n\tlet rangeRegex = null;\n\n\tfor (const cidr of cidrs) {\n\t\t// match cidr\n\t\tif (cidr.includes('/')) {\n\t\t\t// Split the range by the slash\n\t\t\tconst parts = cidr.split('/');\n\n\t\t\t// Work out how many IPs the /slash-part matches.\n\t\t\t// We run 2^(32-slash)\n\t\t\tconst numIps = 2 ** (32 - Number(parts[1]));\n\n\t\t\tconst ipStart = ipLib.toLong(parts[0]);\n\t\t\tconst ipEnd = (ipStart + numIps) - 1;\n\t\t\tranges.push([ipStart, ipEnd]);\n\t\t}\n\n\t\t// match range\n\t\tif (cidr.includes('-')) {\n\t\t\tconst parts = cidr.split('-');\n\t\t\tconst ipStart = ipLib.toLong(parts[0].trim());\n\t\t\tconst ipEnd = ipLib.toLong(parts[1].trim());\n\t\t\tranges.push([ipStart, ipEnd]);\n\t\t}\n\n\t\t// match a single ip or ips like (123.156)\n\t\tstartsWith.push(cidr);\n\t}\n\n\t// if we only need to check for simple ranges, do it with regex\n\tif (startsWith.length) {\n\t\trangeRegex = ipRangeCheckRegex(startsWith);\n\t}\n\n\treturn [ranges, rangeRegex];\n}\n\nfunction contains(ip, ranges, rangeRegex) {\n\tif (ranges.length) {\n\t\tconst ipLong = ipLib.toLong(ip);\n\t\tfor (const range of ranges) {\n\t\t\tif (ipLong >= range[0] && ipLong <= range[1]) return true;\n\t\t}\n\t}\n\n\tif (rangeRegex) {\n\t\treturn rangeRegex.test(ip + '.');\n\t}\n\n\treturn false;\n}\n\nclass IPRange {\n\tconstructor(cidrs) {\n\t\tconst [range, rangeRegex] = compile(cidrs || []);\n\t\tthis.range = range;\n\t\tthis.rangeRegex = rangeRegex;\n\t}\n\n\thas(ip) {\n\t\treturn contains(ip, this.range, this.rangeRegex);\n\t}\n\n\tstatic has(ip, cidrs) {\n\t\tif (!cidrs || !cidrs.length) return false;\n\t\tconst [ranges, rangeRegex] = compile(cidrs);\n\t\treturn contains(ip, ranges, rangeRegex);\n\t}\n\n\tstatic isPrivateIp(ip) {\n\t\tif (!compiledPrivateIps) {\n\t\t\tcompiledPrivateIps = new IPRange(PRIVATE_IPS);\n\t\t}\n\t\treturn compiledPrivateIps.has(ip);\n\t}\n\n\tstatic isGoogleIp(ip) {\n\t\tif (!compiledGoogleIps) {\n\t\t\tcompiledGoogleIps = new IPRange(GOOGLE_IPS);\n\t\t}\n\t\treturn compiledGoogleIps.has(ip);\n\t}\n\n\tstatic isSearchIp(ip) {\n\t\tif (!compiledSearchIps) {\n\t\t\tcompiledSearchIps = new IPRange(SEARCH_IPS);\n\t\t}\n\t\treturn this.isGoogleIp(ip) || compiledSearchIps.has(ip);\n\t}\n\n\tstatic isLocalhost(ip) {\n\t\tif (!publicIp) {\n\t\t\tpublicIp = ipLib.address('public') || '0.0.0.0';\n\t\t}\n\t\tif (!privateIp) {\n\t\t\tprivateIp = ipLib.address('private') || '127.0.0.1';\n\t\t}\n\n\t\treturn ['127.0.0.1', publicIp, privateIp].includes(ip);\n\t}\n}\n\nmodule.exports = IPRange;\n"
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
}
|
package/.lh/Request.js.json
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"sourceFile": "Request.js",
|
|
3
|
-
"activeCommit": 0,
|
|
4
|
-
"commits": [
|
|
5
|
-
{
|
|
6
|
-
"activePatchIndex": 19,
|
|
7
|
-
"patches": [
|
|
8
|
-
{
|
|
9
|
-
"date": 1622037853546,
|
|
10
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n"
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"date": 1622037859422,
|
|
14
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -820,9 +820,8 @@\n \t\treturn sessionId;\n \t}\n \n \t// session id that's existing (not set in this request)\n-\t// TODO: testing & take params into account\n \texistingSessionId() {\n \t\treturn this.ctx.cookies.get(SESSIONID_COOKIE);\n \t}\n \n"
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"date": 1622037867232,
|
|
18
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1165,16 +1165,9 @@\n \t\t}\n \n \t\t// if the medium is direct then only set cookie if it doesn't already exist\n \t\tconst shouldSetCookie = Boolean(utmExists || medium !== 'direct' || !this.cookie(UTM_COOKIE));\n-\t\tif (!shouldSetCookie) {\n-\t\t\t// fix existing utm cookies (not urlencoded)\n-\t\t\t// TODO: remove later\n-\t\t\tconst existing = this.ctx.cookies.get(UTM_COOKIE) || '';\n-\t\t\tif (!existing.includes('|')) {\n-\t\t\t\treturn;\n-\t\t\t}\n-\t\t}\n+\t\tif (!shouldSetCookie) return;\n \n \t\tthis.cookie(\n \t\t\tUTM_COOKIE,\n \t\t\tjoinCookieParts([source, medium, campaign, term, content], 'none'), {\n"
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"date": 1622044243935,
|
|
22
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1176,8 +1176,50 @@\n \t\t\t},\n \t\t);\n \t}\n \n+\t/**\n+\t * sets UTM cookies from a predefined url\n+\t */\n+\tsetUTMCookieFromUrl(url) {\n+\t\tconst uri = new URL(url, 'http://localhost');\n+\n+\t\tconst params = uri.searchParams;\n+\t\tlet source = params.get('utm_source') || '';\n+\t\tlet medium = params.get('utm_medium') || '';\n+\t\tlet campaign = params.get('utm_campaign') || '';\n+\t\tlet term = params.get('utm_term') || '';\n+\t\tconst content = params.get('utm_content') || '';\n+\n+\t\tif (params.get('gclid')) {\n+\t\t\tsource = source || 'google';\n+\t\t\tmedium = medium || 'cpc';\n+\t\t\tcampaign = campaign || 'google_cpc';\n+\t\t}\n+\n+\t\tconst utmExists = Boolean(source || medium || campaign);\n+\t\tconst referer = this.parseReferer();\n+\t\tthis._refererName = referer.name;\n+\n+\t\tif (!utmExists) {\n+\t\t\tsource = referer.source;\n+\t\t\tmedium = referer.medium;\n+\t\t\tterm = referer.term;\n+\t\t}\n+\n+\t\t// if the medium is direct then only set cookie if it doesn't already exist\n+\t\tconst shouldSetCookie = Boolean(utmExists || medium !== 'direct' || !this.cookie(UTM_COOKIE));\n+\t\tif (!shouldSetCookie) return;\n+\n+\t\tthis.cookie(\n+\t\t\tUTM_COOKIE,\n+\t\t\tjoinCookieParts([source, medium, campaign, term, content], 'none'), {\n+\t\t\t\tmaxAge: ONE_MONTH,\n+\t\t\t\tdomain: '*',\n+\t\t\t},\n+\t\t);\n+\t}\n+\n \tsetAffidCookie() {\n \t\tconst affid = this.ctx.query[AFFID_PARAM];\n \t\tconst subaffid = this.ctx.query[SUBAFFID_PARAM];\n \t\tif (!affid) return;\n"
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"date": 1622044357897,
|
|
26
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1218,8 +1218,44 @@\n \t\t\t},\n \t\t);\n \t}\n \n+\tsetUTMCookieFromQuery(query) {\n+\t\tlet source = query.utm_source || '';\n+\t\tlet medium = query.utm_medium || '';\n+\t\tlet campaign = query.utm_campaign || '';\n+\t\tlet term = query.utm_term || '';\n+\t\tconst content = query.utm_content || '';\n+\n+\t\tif (query.gclid) {\n+\t\t\tsource = source || 'google';\n+\t\t\tmedium = medium || 'cpc';\n+\t\t\tcampaign = campaign || 'google_cpc';\n+\t\t}\n+\n+\t\tconst utmExists = Boolean(source || medium || campaign);\n+\t\tconst referer = this.parseReferer();\n+\t\tthis._refererName = referer.name;\n+\n+\t\tif (!utmExists) {\n+\t\t\tsource = referer.source;\n+\t\t\tmedium = referer.medium;\n+\t\t\tterm = referer.term;\n+\t\t}\n+\n+\t\t// if the medium is direct then only set cookie if it doesn't already exist\n+\t\tconst shouldSetCookie = Boolean(utmExists || medium !== 'direct' || !this.cookie(UTM_COOKIE));\n+\t\tif (!shouldSetCookie) return;\n+\n+\t\tthis.cookie(\n+\t\t\tUTM_COOKIE,\n+\t\t\tjoinCookieParts([source, medium, campaign, term, content], 'none'), {\n+\t\t\t\tmaxAge: ONE_MONTH,\n+\t\t\t\tdomain: '*',\n+\t\t\t},\n+\t\t);\n+\t}\n+\n \tsetAffidCookie() {\n \t\tconst affid = this.ctx.query[AFFID_PARAM];\n \t\tconst subaffid = this.ctx.query[SUBAFFID_PARAM];\n \t\tif (!affid) return;\n"
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"date": 1622044385879,
|
|
30
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1139,43 +1139,9 @@\n \t\t};\n \t}\n \n \tsetUTMCookie() {\n-\t\tconst ctx = this.ctx;\n-\n-\t\tlet source = ctx.query.utm_source || '';\n-\t\tlet medium = ctx.query.utm_medium || '';\n-\t\tlet campaign = ctx.query.utm_campaign || '';\n-\t\tlet term = ctx.query.utm_term || '';\n-\t\tconst content = ctx.query.utm_content || '';\n-\n-\t\tif (ctx.query.gclid) {\n-\t\t\tsource = source || 'google';\n-\t\t\tmedium = medium || 'cpc';\n-\t\t\tcampaign = campaign || 'google_cpc';\n-\t\t}\n-\n-\t\tconst utmExists = Boolean(source || medium || campaign);\n-\t\tconst referer = this.parseReferer();\n-\t\tthis._refererName = referer.name;\n-\n-\t\tif (!utmExists) {\n-\t\t\tsource = referer.source;\n-\t\t\tmedium = referer.medium;\n-\t\t\tterm = referer.term;\n-\t\t}\n-\n-\t\t// if the medium is direct then only set cookie if it doesn't already exist\n-\t\tconst shouldSetCookie = Boolean(utmExists || medium !== 'direct' || !this.cookie(UTM_COOKIE));\n-\t\tif (!shouldSetCookie) return;\n-\n-\t\tthis.cookie(\n-\t\t\tUTM_COOKIE,\n-\t\t\tjoinCookieParts([source, medium, campaign, term, content], 'none'), {\n-\t\t\t\tmaxAge: ONE_MONTH,\n-\t\t\t\tdomain: '*',\n-\t\t\t},\n-\t\t);\n+\t\tthis.setUTMCookieFromQuery(this.ctx.query);\n \t}\n \n \t/**\n \t * sets UTM cookies from a predefined url\n"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"date": 1622044489994,
|
|
34
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1147,43 +1147,17 @@\n \t * sets UTM cookies from a predefined url\n \t */\n \tsetUTMCookieFromUrl(url) {\n \t\tconst uri = new URL(url, 'http://localhost');\n-\n \t\tconst params = uri.searchParams;\n-\t\tlet source = params.get('utm_source') || '';\n-\t\tlet medium = params.get('utm_medium') || '';\n-\t\tlet campaign = params.get('utm_campaign') || '';\n-\t\tlet term = params.get('utm_term') || '';\n-\t\tconst content = params.get('utm_content') || '';\n-\n-\t\tif (params.get('gclid')) {\n-\t\t\tsource = source || 'google';\n-\t\t\tmedium = medium || 'cpc';\n-\t\t\tcampaign = campaign || 'google_cpc';\n-\t\t}\n-\n-\t\tconst utmExists = Boolean(source || medium || campaign);\n-\t\tconst referer = this.parseReferer();\n-\t\tthis._refererName = referer.name;\n-\n-\t\tif (!utmExists) {\n-\t\t\tsource = referer.source;\n-\t\t\tmedium = referer.medium;\n-\t\t\tterm = referer.term;\n-\t\t}\n-\n-\t\t// if the medium is direct then only set cookie if it doesn't already exist\n-\t\tconst shouldSetCookie = Boolean(utmExists || medium !== 'direct' || !this.cookie(UTM_COOKIE));\n-\t\tif (!shouldSetCookie) return;\n-\n-\t\tthis.cookie(\n-\t\t\tUTM_COOKIE,\n-\t\t\tjoinCookieParts([source, medium, campaign, term, content], 'none'), {\n-\t\t\t\tmaxAge: ONE_MONTH,\n-\t\t\t\tdomain: '*',\n-\t\t\t},\n-\t\t);\n+\t\tthis.setUTMCookieFromQuery({\n+\t\t\tutm_source: params.get('utm_source'),\n+\t\t\tutm_medium: params.get('utm_medium'),\n+\t\t\tutm_campaign: params.get('utm_campaign'),\n+\t\t\tutm_term: params.get('utm_term'),\n+\t\t\tutm_content: params.get('utm_content'),\n+\t\t\tgclid: params.get('gclid'),\n+\t\t});\n \t}\n \n \tsetUTMCookieFromQuery(query) {\n \t\tlet source = query.utm_source || '';\n"
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
"date": 1622044507475,
|
|
38
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1158,8 +1158,11 @@\n \t\t\tgclid: params.get('gclid'),\n \t\t});\n \t}\n \n+\t/**\n+\t * sets UTM cookies from a predefined query object\n+\t */\n \tsetUTMCookieFromQuery(query) {\n \t\tlet source = query.utm_source || '';\n \t\tlet medium = query.utm_medium || '';\n \t\tlet campaign = query.utm_campaign || '';\n"
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
"date": 1622044687850,
|
|
42
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -965,12 +965,19 @@\n \t\treturn ['get', 'head', 'options'].includes(this.ctx.method.toLowerCase());\n \t}\n \n \tisAjax() {\n-\t\tif (this.ctx.headers['x-requested-with']) {\n-\t\t\treturn this.ctx.headers['x-requested-with'].toLowerCase() === 'xmlhttprequest';\n+\t\tif (this._isAjax === undefined) {\n+\t\t\tconst h = this.ctx.headers['x-requested-with'];\n+\t\t\tif (h) {\n+\t\t\t\tthis._isAjax = h.toLowerCase() === 'xmlhttprequest';\n+\t\t\t}\n+\t\t\telse {\n+\t\t\t\tthis._isAjax = false;\n+\t\t\t}\n \t\t}\n-\t\treturn false;\n+\n+\t\treturn this._isAjax;\n \t}\n \n \tisJSEnabled() {\n \t\treturn !!this.cookie('js');\n"
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"date": 1622044695588,
|
|
46
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -974,9 +974,8 @@\n \t\t\telse {\n \t\t\t\tthis._isAjax = false;\n \t\t\t}\n \t\t}\n-\n \t\treturn this._isAjax;\n \t}\n \n \tisJSEnabled() {\n"
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
"date": 1622044750464,
|
|
50
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1218,10 +1218,10 @@\n \t\t);\n \t}\n \n \thandlePlatformModification() {\n-\t\t// don't change platform in mobile apps\n-\t\tif (this.isMobileApp()) return;\n+\t\t// don't change platform in mobile apps and ajax\n+\t\tif (this.isMobileApp() || this.isAjax()) return;\n \n \t\tconst platform = this.ctx.query[PLATFORM_PARAM] || this.cookie(PLATFORM_COOKIE);\n \t\tconst setPlatformCookie = this.setPlatform(platform);\n \t\tif (setPlatformCookie) {\n"
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"date": 1622044809259,
|
|
54
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -968,9 +968,9 @@\n \tisAjax() {\n \t\tif (this._isAjax === undefined) {\n \t\t\tconst h = this.ctx.headers['x-requested-with'];\n \t\t\tif (h) {\n-\t\t\t\tthis._isAjax = h.toLowerCase() === 'xmlhttprequest';\n+\t\t\t\tthis._isAjax = ['xmlhttprequest', 'fetch'].includes(h.toLowerCase());\n \t\t\t}\n \t\t\telse {\n \t\t\t\tthis._isAjax = false;\n \t\t\t}\n"
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
"date": 1622044831254,
|
|
58
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1219,9 +1219,9 @@\n \t}\n \n \thandlePlatformModification() {\n \t\t// don't change platform in mobile apps and ajax\n-\t\tif (this.isMobileApp() || this.isAjax()) return;\n+\t\tif (this.isMobileApp()) return;\n \n \t\tconst platform = this.ctx.query[PLATFORM_PARAM] || this.cookie(PLATFORM_COOKIE);\n \t\tconst setPlatformCookie = this.setPlatform(platform);\n \t\tif (setPlatformCookie) {\n"
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
"date": 1622044874192,
|
|
62
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -332,12 +332,14 @@\n \t\t\tconst domain = this.baseDomain();\n \t\t\tthis.ctx.set('Content-Security-Policy', `frame-ancestors https://*.${domain}`);\n \t\t}\n \n-\t\tthis.handlePlatformModification();\n-\t\tthis.setUTMCookie();\n-\t\tthis.setAffidCookie();\n-\t\tthis.handleFlashMessage();\n+\t\tif (!this.isAjax()) {\n+\t\t\tthis.handlePlatformModification();\n+\t\t\tthis.setUTMCookie();\n+\t\t\tthis.setAffidCookie();\n+\t\t\tthis.handleFlashMessage();\n+\t\t}\n \n \t\t// in case of visit out from app we need to set cookies from params\n \t\tif (this.ctx.query.installId) {\n \t\t\tawait this.setCookiesFromParams();\n"
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
"date": 1622044935260,
|
|
66
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -337,13 +337,13 @@\n \t\t\tthis.handlePlatformModification();\n \t\t\tthis.setUTMCookie();\n \t\t\tthis.setAffidCookie();\n \t\t\tthis.handleFlashMessage();\n-\t\t}\n \n-\t\t// in case of visit out from app we need to set cookies from params\n-\t\tif (this.ctx.query.installId) {\n-\t\t\tawait this.setCookiesFromParams();\n+\t\t\t// in case of visit out from app we need to set cookies from params\n+\t\t\tif (this.ctx.query.installId) {\n+\t\t\t\tawait this.setCookiesFromParams();\n+\t\t\t}\n \t\t}\n \t}\n \n \tisHttp() {\n"
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
"date": 1622045056034,
|
|
70
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1207,10 +1207,10 @@\n \t}\n \n \tsetAffidCookie() {\n \t\tconst affid = this.ctx.query[AFFID_PARAM];\n+\t\tif (!affid) return;\n \t\tconst subaffid = this.ctx.query[SUBAFFID_PARAM];\n-\t\tif (!affid) return;\n \n \t\tthis.cookie(\n \t\t\tAFFID_COOKIE,\n \t\t\tjoinCookieParts([affid, subaffid]), {\n@@ -1219,8 +1219,16 @@\n \t\t\t},\n \t\t);\n \t}\n \n+\tsetAffidCookieFromUrl(url) {\n+\t\tconst uri = new URL(url, 'http://localhost');\n+\t\tconst params = uri.searchParams;\n+\t\tconst affid = params.get(AFFID_PARAM);\n+\t\tif (!affid) return;\n+\t\tconst subaffid = params.get(SUBAFFID_PARAM);\n+\t}\n+\n \thandlePlatformModification() {\n \t\t// don't change platform in mobile apps and ajax\n \t\tif (this.isMobileApp()) return;\n \n"
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
"date": 1622045061592,
|
|
74
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1225,8 +1225,16 @@\n \t\tconst params = uri.searchParams;\n \t\tconst affid = params.get(AFFID_PARAM);\n \t\tif (!affid) return;\n \t\tconst subaffid = params.get(SUBAFFID_PARAM);\n+\n+\t\tthis.cookie(\n+\t\t\tAFFID_COOKIE,\n+\t\t\tjoinCookieParts([affid, subaffid]), {\n+\t\t\t\tmaxAge: AFFID_COOKIE_DURATION,\n+\t\t\t\tdomain: '*',\n+\t\t\t},\n+\t\t);\n \t}\n \n \thandlePlatformModification() {\n \t\t// don't change platform in mobile apps and ajax\n"
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
"date": 1622045128428,
|
|
78
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1220,9 +1220,9 @@\n \t\t);\n \t}\n \n \tsetAffidCookieFromUrl(url) {\n-\t\tconst uri = new URL(url, 'http://localhost');\n+\t\tconst uri = (url instanceof URL) ? url : new URL(url, 'http://localhost');\n \t\tconst params = uri.searchParams;\n \t\tconst affid = params.get(AFFID_PARAM);\n \t\tif (!affid) return;\n \t\tconst subaffid = params.get(SUBAFFID_PARAM);\n"
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
"date": 1622045144910,
|
|
82
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1154,9 +1154,9 @@\n \t/**\n \t * sets UTM cookies from a predefined url\n \t */\n \tsetUTMCookieFromUrl(url) {\n-\t\tconst uri = new URL(url, 'http://localhost');\n+\t\tconst uri = (url instanceof URL) ? url : new URL(url, 'http://localhost');\n \t\tconst params = uri.searchParams;\n \t\tthis.setUTMCookieFromQuery({\n \t\t\tutm_source: params.get('utm_source'),\n \t\t\tutm_medium: params.get('utm_medium'),\n"
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
"date": 1622045238469,
|
|
86
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1236,9 +1236,9 @@\n \t\t);\n \t}\n \n \thandlePlatformModification() {\n-\t\t// don't change platform in mobile apps and ajax\n+\t\t// don't change platform in mobile apps\n \t\tif (this.isMobileApp()) return;\n \n \t\tconst platform = this.ctx.query[PLATFORM_PARAM] || this.cookie(PLATFORM_COOKIE);\n \t\tconst setPlatformCookie = this.setPlatform(platform);\n"
|
|
87
|
-
}
|
|
88
|
-
],
|
|
89
|
-
"date": 1622037853546,
|
|
90
|
-
"name": "Commit-0",
|
|
91
|
-
"content": "/* eslint-disable class-methods-use-this, max-lines */\nconst {UAParser} = require('ua-parser-js');\nconst bodyParser = require('koa-body');\n\nconst IPRange = require('./IPRange');\nconst GeoIP = require('./GeoIP');\n\nconst enableRateLimit = require('./rateLimit');\nconst enableBasicAuth = require('./basicAuth');\nconst enableStaticPaths = require('./staticPaths');\nconst enableBotBanning = require('./botBanning');\n\nconst uaParser = new UAParser();\n\nconst ONE_HOUR = 3600 * 1000;\nconst ONE_DAY = 24 * ONE_HOUR;\nconst ONE_MONTH = 30 * ONE_DAY;\nconst ONE_YEAR = 365 * ONE_DAY;\nconst TEN_YEARS = 10 * ONE_YEAR;\n\nconst PLATFORM_PARAM = 'platform';\nconst PLATFORM_COOKIE = 'platform';\nconst PLATFORM_COOKIE_DURATION = 4 * ONE_HOUR;\nconst APPINFO_PARAM = 'sm_app';\nconst APPINFO_COOKIE = 'sm_app';\nconst APPINFO_HEADER = 'sm-app';\nconst TRACKING_HEADER = 'sm-tracking';\nconst UTM_COOKIE = 'sm_utm';\nconst AFFID_PARAM = 'affid';\nconst SUBAFFID_PARAM = 'subaffid';\nconst COUNTRY_COOKIE = 'country';\nconst AFFID_COOKIE = 'sm_aff';\nconst AFFID_COOKIE_DURATION = ONE_DAY;\nconst REF_PARAM = 'ref';\nconst SESSIONID_COOKIE = 'sid';\nconst COOKIEID_COOKIE = 'id';\nconst COOKIE_PARAM_PREFIX = '_ck_';\nconst USER_TOKEN_COOKIE = 'utok';\nconst FLASH_COOKIE = 'flash';\n// these cookies are httpOnly, should not be readable from js\nconst SENSITIVE_COOKIES = [USER_TOKEN_COOKIE, COOKIEID_COOKIE, SESSIONID_COOKIE];\n\nconst APP_PLATFORMS = new Map([\n\t['android', {}],\n\t['ios', {}],\n\t['wp', {}],\n\t['tizen', {}],\n\t['jio', {}],\n]);\n\n// sometimes request parameters can be an array (like &q=s&q=b)\n// since we expect a string, this will convert array to a string\nfunction handleArray(value, defaultValue = '') {\n\tif (Array.isArray(value)) {\n\t\treturn value[value.length - 1] || value[0] || defaultValue;\n\t}\n\treturn value || defaultValue;\n}\n\nfunction sanitizeCookiePart(value, defaultValue = '') {\n\tif (!value) return defaultValue;\n\treturn handleArray(value).replace(/\\|/g, '!~!').substring(0, 255);\n}\n\nfunction joinCookieParts(parts, defaultValue = '') {\n\treturn parts.map(part => sanitizeCookiePart(part, defaultValue)).join('|');\n}\n\nfunction splitCookieParts(cookie) {\n\treturn cookie.split('|').map(part => part.replace(/!~!/g, '|'));\n}\n\n/**\n * return [subDomain, baseDomain, canHaveSubdomain]\n */\nfunction getDomainParts(domain) {\n\tconst parts = domain.split('.');\n\tif (parts.length <= 1) {\n\t\t// domain is localhost\n\t\treturn ['', domain, false];\n\t}\n\tif (/^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$/.test(domain)) {\n\t\t// domain is an ip\n\t\treturn ['', domain, false];\n\t}\n\n\tif (parts.length <= 2) {\n\t\t// a simple domain like example.com\n\t\treturn ['', domain, true];\n\t}\n\n\tconst secondTld = parts[parts.length - 2];\n\tif (/co|nic|gov/i.test(secondTld)) {\n\t\t// a domain with second level tld, like example.co.uk\n\t\tif (parts.length <= 3) {\n\t\t\treturn ['', domain, true];\n\t\t}\n\t}\n\n\treturn [parts[0], parts.slice(1).join('.'), true];\n}\n\nfunction randomId(length) {\n\tlet id = '';\n\twhile (id.length < length) {\n\t\tid += Math.random().toString(36).substring(2);\n\t}\n\treturn id.substring(0, length);\n}\n\nfunction getIntegerKey(key) {\n\tif (typeof key === 'number') return key;\n\tif (typeof key === 'string') {\n\t\tlet value = 0;\n\t\tfor (let i = 0; i < key.length; ++i) {\n\t\t\tvalue = ((value << 5) - value) + key.charCodeAt(i); // value * 31 + c\n\t\t\tvalue |= 0; // to 32bit integer\n\t\t}\n\t\treturn value;\n\t}\n\treturn key;\n}\n\nfunction addQuery(url, query = {}) {\n\tconst uri = new URL(url, 'http://localhost');\n\tif (typeof query === 'string') {\n\t\tquery = new URLSearchParams(query);\n\t\tfor (const [key, val] of query) {\n\t\t\turi.searchParams.set(key, val);\n\t\t}\n\t}\n\telse {\n\t\tfor (const [key, val] of Object.entries(query)) {\n\t\t\turi.searchParams.set(key, val);\n\t\t}\n\t}\n\treturn `${uri.pathname}${uri.search}`;\n}\n\nconst isProduction = (process.env.NODE_ENV === 'production');\n\nclass Request {\n\t/**\n\t * @param {import('koa').Context} ctx\n\t */\n\tconstructor(ctx, options = {}) {\n\t\tthis.ctx = ctx;\n\t\tthis.options = options;\n\t}\n\n\n\t/** @type {Routes.installer} */\n\tstatic install(app, options = {}) {\n\t\tenableStaticPaths(app, options.staticPaths);\n\t\tapp.use(bodyParser({\n\t\t\tmultipart: true,\n\t\t}));\n\t\tif (options.middleware) {\n\t\t\tapp.use(this.middleware(options));\n\t\t}\n\t\tenableBasicAuth(app, options.basicAuth);\n\t\tenableBotBanning(app, options.banBots);\n\t\tenableRateLimit(app, options.rateLimit);\n\t}\n\n\t/** @returns {Routes.middleware} */\n\tstatic middleware(options) {\n\t\treturn async (ctx, next) => {\n\t\t\tctx.$req = await this.from(ctx, options);\n\t\t\tawait next();\n\t\t};\n\t}\n\n\t/**\n\t * @param {import('koa').Context} ctx\n\t */\n\tstatic async from(ctx, options) {\n\t\tconst req = new this(ctx, options);\n\t\tawait req.init();\n\t\treturn req;\n\t}\n\n\tappPlatforms() {\n\t\treturn APP_PLATFORMS;\n\t}\n\n\t/**\n\t * get or set request header value\n\t * @param {string} name name of the header\n\t * @param {string} value value to set, if not given header value is return\n\t * @returns {string}\n\t * get: header value, or empty string if header not found\n\t * set: empty string\n\t * @example\n\t * // get header value\n\t * ctx.$req.header('user-agent')\n\t * // set header value\n\t * ctx.$req.header('ETag', '1234')\n\t */\n\theader(name, value) {\n\t\t// get header value\n\t\tif (value === undefined) {\n\t\t\treturn this.ctx.headers[name.toLowerCase()] || '';\n\t\t}\n\n\t\t// set header value\n\t\tthis.ctx.set(name, value);\n\t\treturn '';\n\t}\n\n\t/**\n\t * Get value from either from request body or query params\n\t * @param {string} key\n\t * @param {string} defaultValue\n\t * @return {any}\n\t */\n\tparam(key, defaultValue = '') {\n\t\tconst ctx = this.ctx;\n\t\tlet paramValue;\n\n\t\tif (ctx.request.body &&\n\t\t\tctx.request.body.fields &&\n\t\t\t(key in ctx.request.body.fields)\n\t\t) {\n\t\t\tparamValue = ctx.request.body.fields[key];\n\t\t}\n\t\tif (ctx.request.body &&\n\t\t\t(key in ctx.request.body)\n\t\t) {\n\t\t\tparamValue = ctx.request.body[key];\n\t\t}\n\t\telse {\n\t\t\tparamValue = ctx.query[key];\n\t\t}\n\n\t\tparamValue = paramValue || defaultValue;\n\n\t\tif (typeof paramValue === 'string') return paramValue.trim();\n\t\tif (Array.isArray(paramValue)) return paramValue.map(v => v.trim());\n\t\treturn paramValue;\n\t}\n\n\tparamInt(key, defaultValue = 0) {\n\t\tconst value = this.param(key, null);\n\t\tif (value === null) return defaultValue;\n\n\t\tconst intVal = Number(value);\n\t\tif (Number.isNaN(intVal)) {\n\t\t\treturn defaultValue;\n\t\t}\n\n\t\treturn intVal;\n\t}\n\n\tparamBool(key, defaultValue = false) {\n\t\tconst value = this.param(key, null);\n\t\tif (value === null) return defaultValue;\n\n\t\tif (\n\t\t\tvalue === 0 ||\n\t\t\tvalue === '0' ||\n\t\t\tvalue === 'false' ||\n\t\t\tvalue === 'no' ||\n\t\t\tvalue === 'off'\n\t\t) return false;\n\n\t\treturn true;\n\t}\n\n\tparamId(key, defaultValue = '') {\n\t\tconst value = this.param(key, null);\n\t\tif (value === null) return defaultValue;\n\t\tif (/^[A-Za-z0-9]{1,20}$/.test(value)) return value;\n\t\treturn defaultValue;\n\t}\n\n\t/**\n\t * get a string parameter and escape it for xss attacks\n\t * @param {string} key\n\t * @param {string} defaultValue\n\t * @returns {string} value of the param\n\t */\n\tparamXSS(key, defaultValue = '') {\n\t\tconst param = this.param(key, null);\n\t\tif (param === null) return defaultValue;\n\n\t\treturn param.replace(/([<>\"'])/g, (match, g1) => {\n\t\t\tswitch (g1) {\n\t\t\t\tcase '<': return '<';\n\t\t\t\tcase '>': return '>';\n\t\t\t\tcase '\"': return '"';\n\t\t\t\tcase '\\'': return ''';\n\t\t\t\tdefault: return g1;\n\t\t\t}\n\t\t});\n\t}\n\n\tfile(key) {\n\t\treturn this.files(key)[0] || null;\n\t}\n\n\tfiles(key) {\n\t\tif (this.ctx.request.files && this.ctx.request.files[key]) {\n\t\t\tconst result = this.ctx.request.files[key];\n\t\t\tif (!Array.isArray(result)) return [result];\n\t\t\treturn result.filter(Boolean);\n\t\t}\n\n\t\treturn [];\n\t}\n\n\tbearerToken() {\n\t\tconst authorization = this.ctx.headers.authorization;\n\t\tif (!authorization) return '';\n\n\t\tconst parts = authorization.split(' ');\n\t\tif (parts.length !== 2) return '';\n\t\tif (parts[0] !== 'Bearer') return '';\n\n\t\treturn parts[1];\n\t}\n\n\tapiToken() {\n\t\treturn this.header('x-api-token');\n\t}\n\n\t// initialize a request\n\t// set important cookies and all\n\tasync init() {\n\t\t// don't allow other domains to embed our iframe\n\t\tif (isProduction) {\n\t\t\tconst domain = this.baseDomain();\n\t\t\tthis.ctx.set('Content-Security-Policy', `frame-ancestors https://*.${domain}`);\n\t\t}\n\n\t\tthis.handlePlatformModification();\n\t\tthis.setUTMCookie();\n\t\tthis.setAffidCookie();\n\t\tthis.handleFlashMessage();\n\n\t\t// in case of visit out from app we need to set cookies from params\n\t\tif (this.ctx.query.installId) {\n\t\t\tawait this.setCookiesFromParams();\n\t\t}\n\t}\n\n\tisHttp() {\n\t\tif (this._isHttp === undefined) {\n\t\t\tconst ctx = this.ctx;\n\t\t\tthis._isHttp = (ctx.headers.origin || '').startsWith('http:') || (ctx.headers.host || '').includes(':');\n\t\t}\n\t\treturn this._isHttp;\n\t}\n\n\t/**\n\t * @typedef {Object} CustomCookieOpts\n\t * @property {boolean} [onlyCache]\n\t * @property {boolean} [onlyCacheIfExists]\n\t * @property {number} [days]\n\t * @property {number} [years]\n\t */\n\n\t/**\n\t * @template {string | number | boolean | undefined} V\n\t * @param {string} name\n\t * @param {V} value\n\t * @param {CustomCookieOpts & import('cookies').SetOption} [options]\n\t * @returns {V extends undefined ? string : null}\n\t */\n\tcookie(name, value, options = {}) {\n\t\tconst cookies = this._cookies || (this._cookies = {});\n\n\t\tif (value === undefined) {\n\t\t\tif (name in cookies) {\n\t\t\t\treturn cookies[name];\n\t\t\t}\n\n\t\t\tconst existing = this.ctx.cookies.get(name) || '';\n\t\t\treturn decodeURIComponent(existing);\n\t\t}\n\n\t\tcookies[name] = value;\n\n\t\t// only set the cookie in cache\n\t\t// don't set it in real\n\t\tif (options.onlyCache) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// set the cookie only if does not exist\n\t\t// but always set it in cache\n\t\tif (options.onlyCacheIfExists) {\n\t\t\tif (this.ctx.cookies.get(name)) return null;\n\t\t}\n\n\t\t// clone options\n\t\toptions = Object.assign({}, options);\n\n\t\tif (options.domain === '*') {\n\t\t\toptions.domain = this.baseDomain();\n\t\t}\n\t\tif (!('path' in options)) {\n\t\t\toptions.path = '/';\n\t\t}\n\t\tif ('days' in options) {\n\t\t\toptions.maxAge = options.days * ONE_DAY;\n\t\t}\n\t\tif ('years' in options) {\n\t\t\toptions.maxAge = options.years * ONE_YEAR;\n\t\t}\n\n\t\tif (!('httpOnly' in options)) {\n\t\t\tif (!SENSITIVE_COOKIES.includes(name)) {\n\t\t\t\toptions.httpOnly = false;\n\t\t\t}\n\t\t}\n\n\t\tconst isHttp = this.isHttp();\n\t\tif (options.httpOnly && !isHttp) {\n\t\t\toptions.secure = true;\n\t\t}\n\n\t\tif (!('sameSite' in options)) {\n\t\t\t// sameSite = none means intentionally send the cookie in 3rd party contexts\n\t\t\toptions.sameSite = isHttp ? false : 'none';\n\t\t}\n\n\t\tif (value) {\n\t\t\tvalue = encodeURIComponent(value);\n\t\t}\n\n\t\tthis.ctx.cookies.set(name, value, options);\n\t\treturn null;\n\t}\n\n\t/**\n\t * Used for sending tracking info from other servers/sites\n\t * @template {string | undefined} T\n\t * @param {T} key\n\t * @returns {T extends string ? string : Object.<string, string>}\n\t */\n\ttrackingHeader(key) {\n\t\tif (!this._trackingHeader) {\n\t\t\tconst header = this.header(TRACKING_HEADER);\n\t\t\tif (!header) {\n\t\t\t\tthis._trackingHeader = {};\n\t\t\t}\n\t\t\telse {\n\t\t\t\ttry {\n\t\t\t\t\tthis._trackingHeader = JSON.parse(header) || {};\n\t\t\t\t}\n\t\t\t\tcatch (e) {\n\t\t\t\t\tthis._trackingHeader = {};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn key ? this._trackingHeader[key] : this._trackingHeader;\n\t}\n\n\tuserAgent() {\n\t\treturn this.trackingHeader('user-agent') || this.header('user-agent');\n\t}\n\n\treferer() {\n\t\treturn this.trackingHeader('referer') || this.header('referer');\n\t}\n\n\trefererName() {\n\t\treturn this._refererName;\n\t}\n\n\tparseUserAgent() {\n\t\tif (!this._ua) {\n\t\t\tthis._ua = uaParser.setUA(this.userAgent()).getResult() || {};\n\t\t}\n\t\treturn this._ua;\n\t}\n\n\tbrowser() {\n\t\tconst ua = this.parseUserAgent();\n\t\tconst deviceType = (ua && ua.device && ua.device.type) || '';\n\t\tconst browserName = (ua && ua.browser && ua.browser.name) || '';\n\n\t\tif (deviceType === 'mobile') {\n\t\t\tif (browserName === 'Chrome') return 'Chrome Mobile';\n\t\t\tif (browserName === 'Firefox') return 'Firefox Mobile';\n\t\t}\n\n\t\treturn browserName;\n\t}\n\n\tbrowserVersion() {\n\t\tconst ua = this.parseUserAgent();\n\t\tlet browerVersion = (ua.browser && ua.browser.version) || '';\n\t\tif (browerVersion) browerVersion = ' ' + browerVersion;\n\t\treturn this.browser() + browerVersion;\n\t}\n\n\tbrowserVersionRaw() {\n\t\tconst ua = this.parseUserAgent();\n\t\tconst version = (ua.browser && ua.browser.version) || '';\n\t\treturn String(version);\n\t}\n\n\tos() {\n\t\tconst ua = this.parseUserAgent();\n\t\treturn (ua.os && ua.os.name) || '';\n\t}\n\n\tosVersion() {\n\t\tconst ua = this.parseUserAgent();\n\t\tlet osVersion = (ua.os && ua.os.version) || '';\n\t\tif (osVersion) osVersion = ' ' + osVersion;\n\t\treturn this.os() + osVersion;\n\t}\n\n\tsmUserAgent() {\n\t\treturn this.header('sm-user-agent');\n\t}\n\n\tappUserAgent() {\n\t\treturn this.smUserAgent() || this.userAgent();\n\t}\n\n\tgetAppInfoFromUserAgent() {\n\t\treturn null;\n\t}\n\n\t_getAppInfoFromString(infoStr, separator = '#') {\n\t\tif (!infoStr) return null;\n\t\t// eslint-disable-next-line prefer-const\n\t\tlet [platform, appVersion, installId] = infoStr.split(separator);\n\t\tplatform = platform.toLowerCase();\n\n\t\tconst appInfo = {\n\t\t\tplatform,\n\t\t\tappVersion,\n\t\t\tinstallId,\n\t\t};\n\n\t\tif (this.appPlatforms().has(platform)) {\n\t\t\tappInfo.isMobileApp = true;\n\t\t}\n\n\t\treturn appInfo;\n\t}\n\n\tgetAppInfoFromParam() {\n\t\tconst appInfoParam = this.ctx.query[APPINFO_PARAM];\n\t\tif (appInfoParam) {\n\t\t\tthis.cookie(APPINFO_COOKIE, appInfoParam, {\n\t\t\t\tpath: '/',\n\t\t\t\tmaxAge: TEN_YEARS,\n\t\t\t\tdomain: '*',\n\t\t\t});\n\n\t\t\treturn this._getAppInfoFromString(appInfoParam, ':');\n\t\t}\n\n\t\tconst appInfoCookie = this.cookie(APPINFO_COOKIE);\n\t\tif (appInfoCookie) {\n\t\t\treturn this._getAppInfoFromString(appInfoParam, ':');\n\t\t}\n\n\t\treturn null;\n\t}\n\n\tgetAppInfoFromHeader() {\n\t\treturn this._getAppInfoFromString(this.ctx.headers[APPINFO_HEADER]);\n\t}\n\n\tgetAppInfo() {\n\t\tconst appInfoHeader = this.getAppInfoFromHeader();\n\t\tif (appInfoHeader) return appInfoHeader;\n\n\t\tconst appInfoParam = this.getAppInfoFromParam();\n\t\tif (appInfoParam) return appInfoParam;\n\n\t\tconst appInfoUserAgent = this.getAppInfoFromUserAgent();\n\t\tif (appInfoUserAgent) return appInfoUserAgent;\n\n\t\treturn {};\n\t}\n\n\t/*\n\t * get the app related info using a header / query parameter / user agent\n\t * this means that you can have the url as sm_app=android\n\t * and it'll automatically identify it as an android app\n\t */\n\tappInfo(param = null) {\n\t\tif (!this._appInfo) {\n\t\t\tthis._appInfo = this.getAppInfo() || {};\n\t\t}\n\t\treturn param ? this._appInfo[param] : this._appInfo;\n\t}\n\n\tinstallId() {\n\t\treturn this.appInfo('installId') || this.ctx.query.installId || '';\n\t}\n\n\tappVersion() {\n\t\treturn this.appInfo('appVersion') || this.ctx.query.appVersion || '';\n\t}\n\n\tisAndroidApp() {\n\t\treturn this.appInfo('platform') === 'android';\n\t}\n\n\tisIOSApp() {\n\t\treturn this.appInfo('platform') === 'ios';\n\t}\n\n\tisWPApp() {\n\t\treturn this.appInfo('platform') === 'wp';\n\t}\n\n\tisTizenApp() {\n\t\treturn this.appInfo('platform') === 'tizen';\n\t}\n\n\tisJIOApp() {\n\t\treturn this.appInfo('platform') === 'jio';\n\t}\n\n\tisMobileApp() {\n\t\t// for setPlatform cases\n\t\tconst platform = this._platform;\n\t\tif (platform) {\n\t\t\tif (platform === 'mobile_app') return true;\n\t\t\treturn false;\n\t\t}\n\n\t\treturn !!this.appInfo('isMobileApp');\n\t}\n\n\tisMobileWeb() {\n\t\t// for setPlatform cases\n\t\tconst platform = this._platform;\n\t\tif (platform) {\n\t\t\tif (platform === 'mobile_web') return true;\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!this._isMobileWeb) {\n\t\t\tconst ua = this.parseUserAgent();\n\t\t\tthis._isMobileWeb = (ua && ua.device && ua.device.type === 'mobile') || false;\n\t\t}\n\t\treturn this._isMobileWeb;\n\t}\n\n\tisMobile() {\n\t\treturn this.isMobileApp() || this.isMobileWeb();\n\t}\n\n\tplatform() {\n\t\tif (!this._platform) {\n\t\t\tif (this.isMobileApp()) {\n\t\t\t\tthis._platform = 'mobile_app';\n\t\t\t}\n\t\t\telse if (this.isMobileWeb()) {\n\t\t\t\tthis._platform = 'mobile_web';\n\t\t\t}\n\t\t\telse if (this.isAPI()) {\n\t\t\t\tthis._platform = 'api';\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthis._platform = 'desktop';\n\t\t\t}\n\t\t}\n\n\t\treturn this._platform;\n\t}\n\n\tsetPlatform(platform) {\n\t\tif (!platform) return false;\n\t\tplatform = handleArray(platform);\n\t\tconst appPlatform = platform.replace('_app', '');\n\n\t\tif (this.appPlatforms().has(appPlatform)) {\n\t\t\tthis._platform = 'mobile_app';\n\t\t\tthis._subPlatform = `${appPlatform}_app`;\n\t\t\treturn false;\n\t\t}\n\n\t\tswitch (platform) {\n\t\t\tcase 'mobile':\n\t\t\tcase 'mobile_web':\n\t\t\t\tthis._platform = 'mobile_web';\n\t\t\t\treturn true;\n\t\t\tcase 'www':\n\t\t\tcase 'desktop':\n\t\t\t\tthis._platform = 'desktop';\n\t\t\t\treturn true;\n\t\t\tcase 'mobile_app':\n\t\t\t\tthis._platform = 'mobile_app';\n\t\t\t\treturn true;\n\t\t\tdefault:\n\t\t\t\treturn false;\n\t\t}\n\t}\n\n\tsubPlatform() {\n\t\tif (!this._subPlatform) {\n\t\t\tconst appPlatform = this.appInfo('platform');\n\t\t\tif (appPlatform && this.isMobileApp()) {\n\t\t\t\tthis._subPlatform = `${appPlatform}_app`;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthis._subPlatform = this.platform();\n\t\t\t}\n\t\t}\n\t\treturn this._subPlatform;\n\t}\n\n\tisDesktop() {\n\t\t// for setPlatform cases\n\t\tconst platform = this._platform;\n\t\tif (platform) {\n\t\t\tif (platform === 'desktop') return true;\n\t\t\treturn false;\n\t\t}\n\n\t\treturn this.platform() === 'desktop';\n\t}\n\n\t/**\n\t * @typedef {Object} UTMObject\n\t * @property {string} source\n\t * @property {string} medium\n\t * @property {string} campaign\n\t * @property {string} term\n\t * @property {string} content\n\t * @property {string} sourceMedium\n\t */\n\n\t/**\n\t * @template {string | null} P\n\t * @param {P} [param]\n\t * @returns {P extends null ? UTMObject : P extends string ? string : UTMObject}\n\t */\n\tutm(param = null) {\n\t\tif (!this._utm) {\n\t\t\tconst utmCookie = this.cookie(UTM_COOKIE);\n\t\t\tif (!utmCookie) {\n\t\t\t\tthis._utm = {};\n\t\t\t}\n\t\t\telse {\n\t\t\t\tconst [source, medium, campaign, term, content] = splitCookieParts(utmCookie);\n\t\t\t\tthis._utm = {\n\t\t\t\t\tsource,\n\t\t\t\t\tmedium,\n\t\t\t\t\tcampaign,\n\t\t\t\t\tterm,\n\t\t\t\t\tcontent,\n\t\t\t\t\tsourceMedium: `${source}/${medium}`,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\treturn param ? this._utm[param] : this._utm;\n\t}\n\n\t/**\n\t * @returns {string}\n\t */\n\tref() {\n\t\treturn this.param(REF_PARAM).replace(/[^a-zA-Z0-9_.:~-]+/g, '-');\n\t}\n\n\t_affidSubaffid() {\n\t\tconst affidCookie = this.cookie(AFFID_COOKIE);\n\t\tif (!affidCookie) return ['', ''];\n\n\t\tconst [affid, subaffid] = affidCookie.split('|');\n\t\treturn [affid || '', subaffid || ''];\n\t}\n\n\taffid() {\n\t\treturn this._affidSubaffid()[0];\n\t}\n\n\tsubaffid() {\n\t\treturn this._affidSubaffid()[1];\n\t}\n\n\tsubAffid() {\n\t\treturn this.subaffid();\n\t}\n\n\tcookieId() {\n\t\tlet cookieId = this.cookie(COOKIEID_COOKIE);\n\t\tif (!cookieId) {\n\t\t\tconst version = '1';\n\t\t\tcookieId = version + randomId(15);\n\t\t\tthis.cookie(COOKIEID_COOKIE, cookieId, {\n\t\t\t\tmaxAge: TEN_YEARS,\n\t\t\t\tdomain: '*',\n\t\t\t});\n\t\t}\n\n\t\treturn cookieId;\n\t}\n\n\t// cookie id that's existing (not set in this request)\n\texistingCookieId() {\n\t\treturn this.ctx.cookies.get(COOKIEID_COOKIE);\n\t}\n\n\tsessionId() {\n\t\tlet sessionId = this.cookie(SESSIONID_COOKIE);\n\t\tif (!sessionId) {\n\t\t\tconst version = '1';\n\t\t\tsessionId = version + randomId(15);\n\t\t\tthis.cookie(SESSIONID_COOKIE, sessionId, {\n\t\t\t\tdomain: '*',\n\t\t\t});\n\t\t}\n\n\t\treturn sessionId;\n\t}\n\n\t// session id that's existing (not set in this request)\n\t// TODO: testing & take params into account\n\texistingSessionId() {\n\t\treturn this.ctx.cookies.get(SESSIONID_COOKIE);\n\t}\n\n\tisOriginRequest() {\n\t\tif (!isProduction) return true;\n\n\t\tconst baseDomain = this.options.baseDomain;\n\t\tif (!baseDomain) return true;\n\n\t\tconst origin = this.ctx.headers.origin;\n\n\t\t// this is needed because firefox currently does not\n\t\t// send origin with form submit requests (sends with xhr)\n\t\t// so this might cause csrf on firefox\n\t\tif (!origin) return true;\n\n\t\tconst matches = origin.match(/^((http|https):\\/\\/)?([^/:]+)[/]?/i);\n\t\tif (!matches) return false;\n\t\tif (('.' + matches[3]).endsWith('.' + baseDomain)) return true;\n\t\treturn false;\n\t}\n\n\t/**\n\t * @returns {string}\n\t */\n\tip() {\n\t\tif (!this._ip) {\n\t\t\tif (this.trackingHeader('ip')) {\n\t\t\t\tthis._ip = this.trackingHeader('ip');\n\t\t\t}\n\t\t\t// nginx proxy sets x-real-ip header as real ip address\n\t\t\telse if (this.ctx.headers['x-real-ip']) {\n\t\t\t\tthis._ip = this.ctx.headers['x-real-ip'];\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// ip is of format ::ffff:127.1.0.1, strip ::ffff: from it\n\t\t\t\tthis._ip = this.ctx.ip.replace(/^.*:/, '');\n\t\t\t}\n\t\t}\n\n\t\treturn this._ip;\n\t}\n\n\t/**\n\t * @returns {string}\n\t */\n\trealIP() {\n\t\tif (this._realIP) return this._realIP;\n\n\t\tconst forwardedFor = this.header('x-forwarded-for');\n\t\tif (!forwardedFor) {\n\t\t\tthis._realIP = this.ip();\n\t\t\treturn this._realIP;\n\t\t}\n\n\t\tconst forwardedList = forwardedFor.split(',');\n\t\tfor (let ipAddress of forwardedList) {\n\t\t\tipAddress = ipAddress.trim();\n\n\t\t\t// validate ipv4\n\t\t\tconst ipv4Regex = /(::ffff:)?(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})/;\n\t\t\tconst matches = ipAddress.match(ipv4Regex);\n\t\t\tif (!matches) continue;\n\t\t\tipAddress = matches[2];\n\n\t\t\t// validate not private\n\t\t\tif (IPRange.isPrivateIp(ipAddress)) continue;\n\t\t\tthis._realIP = ipAddress;\n\t\t\treturn this._realIP;\n\t\t}\n\n\t\tthis._realIP = this.ip();\n\t\treturn this._realIP;\n\t}\n\n\t/**\n\t * Is being proxied through local proxy\n\t */\n\tisProxied() {\n\t\treturn this.header('x-real-ip') && this.ctx.ip.endsWith('127.0.0.1');\n\t}\n\n\t/**\n\t * Is request being proxied through an external proxy (like chrome data-saver)\n\t */\n\tisVia() {\n\t\treturn this.header('via') && this.realIP() === this.ip();\n\t}\n\n\tparseUrl() {\n\t\tif (!this._uri) {\n\t\t\tthis._uri = new URL(this.ctx.href);\n\t\t}\n\t\treturn this._uri;\n\t}\n\n\tbaseDomain() {\n\t\tif (!this._domainParts) {\n\t\t\tthis._domainParts = getDomainParts(this.domain());\n\t\t}\n\t\treturn this._domainParts[1];\n\t}\n\n\tdomain() {\n\t\treturn this.ctx.hostname;\n\t}\n\n\tsubDomain() {\n\t\tif (!this._domainParts) {\n\t\t\tthis._domainParts = getDomainParts(this.domain());\n\t\t}\n\t\treturn this._domainParts[0];\n\t}\n\n\tport() {\n\t\tconst port = this.ctx.host.split(':')[1];\n\t\treturn port ? Number(port) : 80;\n\t}\n\n\tisLocalhost() {\n\t\treturn IPRange.isLocalhost(this.ip());\n\t}\n\n\tisGoogleIP() {\n\t\treturn IPRange.isGoogleIp(this.ip());\n\t}\n\n\tisSearchIP() {\n\t\treturn IPRange.isSearchIp(this.ip());\n\t}\n\n\tisGet() {\n\t\treturn this.ctx.method.toLowerCase() === 'get';\n\t}\n\n\tisPost() {\n\t\treturn this.ctx.method.toLowerCase() === 'post';\n\t}\n\n\tisGetLike() {\n\t\treturn ['get', 'head', 'options'].includes(this.ctx.method.toLowerCase());\n\t}\n\n\tisAjax() {\n\t\tif (this.ctx.headers['x-requested-with']) {\n\t\t\treturn this.ctx.headers['x-requested-with'].toLowerCase() === 'xmlhttprequest';\n\t\t}\n\t\treturn false;\n\t}\n\n\tisJSEnabled() {\n\t\treturn !!this.cookie('js');\n\t}\n\n\tnextUrl() {\n\t\tconst ctx = this.ctx;\n\t\tif (ctx.query.next) return ctx.query.next;\n\n\t\tconst paramNext = this.param('next');\n\t\tif (paramNext) return paramNext;\n\n\t\tif (!['/login', '/logout', '/signup', '/user/oauth'].includes(ctx.path)) {\n\t\t\treturn ctx.url;\n\t\t}\n\n\t\treturn '/';\n\t}\n\n\tlogoutUrl(redirectUrl = null) {\n\t\tconst nextUrl = redirectUrl || this.nextUrl();\n\t\treturn '/logout?next=' + encodeURIComponent(nextUrl);\n\t}\n\n\tloginUrl() {\n\t\treturn '/login?next=' + encodeURIComponent(this.nextUrl());\n\t}\n\n\tsignupUrl() {\n\t\treturn '/signup?next=' + encodeURIComponent(this.nextUrl());\n\t}\n\n\tmobileUrl() {\n\t\treturn addQuery(this.ctx.url, {[PLATFORM_PARAM]: 'mobile'});\n\t}\n\n\tdesktopUrl() {\n\t\treturn addQuery(this.ctx.url, {[PLATFORM_PARAM]: 'desktop'});\n\t}\n\n\tredirect(url, qs = true) {\n\t\tif (qs === true) {\n\t\t\turl = addQuery(url, this.ctx.querystring);\n\t\t}\n\t\telse if (typeof qs === 'object' || typeof qs === 'string') {\n\t\t\turl = addQuery(url, qs);\n\t\t}\n\n\t\treturn this.ctx.redirect(url);\n\t}\n\n\tredirectPermanent(url, qs = true) {\n\t\tif (qs === true) {\n\t\t\turl = addQuery(url, this.ctx.querystring);\n\t\t}\n\t\telse if (typeof qs === 'object' || typeof qs === 'string') {\n\t\t\turl = addQuery(url, qs);\n\t\t}\n\n\t\tthis.ctx.status = 301;\n\t\treturn this.ctx.redirect(url);\n\t}\n\n\t// this is when user visits an out link from our app\n\t// we send all the cookies as params\n\t// so we need to set the cookies on browser if they don't exist\n\tasync setCookiesFromParams() {\n\t\tconst query = this.ctx.query;\n\n\t\tconst utmCookie = query[COOKIE_PARAM_PREFIX + UTM_COOKIE];\n\t\tif (utmCookie) {\n\t\t\tthis.cookie(UTM_COOKIE, utmCookie, {\n\t\t\t\tmaxAge: ONE_MONTH,\n\t\t\t\tonlyCacheIfExists: true,\n\t\t\t\tdomain: '*',\n\t\t\t});\n\t\t}\n\n\t\tconst idCookie = query[COOKIE_PARAM_PREFIX + COOKIEID_COOKIE];\n\t\tif (idCookie) {\n\t\t\tthis.cookie(COOKIEID_COOKIE, idCookie, {\n\t\t\t\tmaxAge: TEN_YEARS,\n\t\t\t\tonlyCacheIfExists: true,\n\t\t\t\tdomain: '*',\n\t\t\t});\n\t\t}\n\n\t\tconst sessionIdCookie = query[COOKIE_PARAM_PREFIX + SESSIONID_COOKIE];\n\t\tif (sessionIdCookie) {\n\t\t\tthis.cookie(SESSIONID_COOKIE, sessionIdCookie, {\n\t\t\t\tonlyCacheIfExists: true,\n\t\t\t\tdomain: '*',\n\t\t\t});\n\t\t}\n\n\t\tconst affidCookie = query[COOKIE_PARAM_PREFIX + AFFID_COOKIE];\n\t\tif (affidCookie) {\n\t\t\tthis.cookie(AFFID_COOKIE, affidCookie, {\n\t\t\t\tmaxAge: ONE_DAY,\n\t\t\t\tonlyCacheIfExists: true,\n\t\t\t\tdomain: '*',\n\t\t\t});\n\t\t}\n\n\t\tconst countryCookie = query[COOKIE_PARAM_PREFIX + COUNTRY_COOKIE];\n\t\tif (countryCookie) {\n\t\t\tthis.cookie(COUNTRY_COOKIE, countryCookie, {\n\t\t\t\tmaxAge: TEN_YEARS,\n\t\t\t});\n\t\t}\n\t}\n\n\tparseReferer() {\n\t\tconst referer = this.referer();\n\t\tif (!referer) {\n\t\t\treturn {\n\t\t\t\tname: '',\n\t\t\t\tsource: 'direct',\n\t\t\t\tmedium: 'direct',\n\t\t\t\tterm: '',\n\t\t\t};\n\t\t}\n\n\t\tconst refererUri = new URL(referer);\n\t\tlet host = refererUri.hostname;\n\t\tconst baseDomain = this.baseDomain();\n\n\t\tif (host.endsWith(baseDomain)) {\n\t\t\treturn {\n\t\t\t\tname: host,\n\t\t\t\tsource: host,\n\t\t\t\tmedium: 'direct',\n\t\t\t\tterm: '',\n\t\t\t};\n\t\t}\n\n\t\thost = host.replace(/^(?:www|m|shop|mobile|lm|l)\\./, '')\n\t\t\t.replace('search.yahoo', 'yahoo');\n\n\t\t// extract search engine names\n\t\tconst searchRegex = /\\.(images\\.google|google|yahoo|bing|ask|duckduckgo|yandex|baidu|babylon|avg|wow|reliancenetconnect|webcrawler|inspsearch|speedbit|searches|search)\\./;\n\t\tconst matches = `.${host}`.match(searchRegex);\n\n\t\tif (matches) {\n\t\t\tlet refererSource = matches[1];\n\t\t\tif (['search', 'searches'].includes(refererSource)) {\n\t\t\t\trefererSource = host;\n\t\t\t}\n\n\t\t\tconst query = refererUri.query(true);\n\t\t\tconst term = query.q || query.searchfor || query.pq || 'not_available';\n\n\t\t\treturn {\n\t\t\t\tname: host,\n\t\t\t\tsource: refererSource,\n\t\t\t\tmedium: 'organic',\n\t\t\t\tterm: handleArray(term).trim(),\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tname: host,\n\t\t\tsource: host,\n\t\t\tmedium: 'referral',\n\t\t\tterm: '',\n\t\t};\n\t}\n\n\tsetUTMCookie() {\n\t\tconst ctx = this.ctx;\n\n\t\tlet source = ctx.query.utm_source || '';\n\t\tlet medium = ctx.query.utm_medium || '';\n\t\tlet campaign = ctx.query.utm_campaign || '';\n\t\tlet term = ctx.query.utm_term || '';\n\t\tconst content = ctx.query.utm_content || '';\n\n\t\tif (ctx.query.gclid) {\n\t\t\tsource = source || 'google';\n\t\t\tmedium = medium || 'cpc';\n\t\t\tcampaign = campaign || 'google_cpc';\n\t\t}\n\n\t\tconst utmExists = Boolean(source || medium || campaign);\n\t\tconst referer = this.parseReferer();\n\t\tthis._refererName = referer.name;\n\n\t\tif (!utmExists) {\n\t\t\tsource = referer.source;\n\t\t\tmedium = referer.medium;\n\t\t\tterm = referer.term;\n\t\t}\n\n\t\t// if the medium is direct then only set cookie if it doesn't already exist\n\t\tconst shouldSetCookie = Boolean(utmExists || medium !== 'direct' || !this.cookie(UTM_COOKIE));\n\t\tif (!shouldSetCookie) {\n\t\t\t// fix existing utm cookies (not urlencoded)\n\t\t\t// TODO: remove later\n\t\t\tconst existing = this.ctx.cookies.get(UTM_COOKIE) || '';\n\t\t\tif (!existing.includes('|')) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tthis.cookie(\n\t\t\tUTM_COOKIE,\n\t\t\tjoinCookieParts([source, medium, campaign, term, content], 'none'), {\n\t\t\t\tmaxAge: ONE_MONTH,\n\t\t\t\tdomain: '*',\n\t\t\t},\n\t\t);\n\t}\n\n\tsetAffidCookie() {\n\t\tconst affid = this.ctx.query[AFFID_PARAM];\n\t\tconst subaffid = this.ctx.query[SUBAFFID_PARAM];\n\t\tif (!affid) return;\n\n\t\tthis.cookie(\n\t\t\tAFFID_COOKIE,\n\t\t\tjoinCookieParts([affid, subaffid]), {\n\t\t\t\tmaxAge: AFFID_COOKIE_DURATION,\n\t\t\t\tdomain: '*',\n\t\t\t},\n\t\t);\n\t}\n\n\thandlePlatformModification() {\n\t\t// don't change platform in mobile apps\n\t\tif (this.isMobileApp()) return;\n\n\t\tconst platform = this.ctx.query[PLATFORM_PARAM] || this.cookie(PLATFORM_COOKIE);\n\t\tconst setPlatformCookie = this.setPlatform(platform);\n\t\tif (setPlatformCookie) {\n\t\t\tthis.cookie(PLATFORM_COOKIE, platform, {\n\t\t\t\tmaxAge: PLATFORM_COOKIE_DURATION,\n\t\t\t\tdomain: '*',\n\t\t\t});\n\t\t}\n\t}\n\n\tipLocation() {\n\t\tif (this._ipLoc === undefined) {\n\t\t\tconst loc = GeoIP.getSync(this.ip()) || {};\n\t\t\tconst country = loc.country;\n\t\t\tconst city = loc.city;\n\t\t\tconst state = loc.subdivisions && loc.subdivisions[0];\n\t\t\tthis._ipLoc = {\n\t\t\t\tcountry: {\n\t\t\t\t\tname: (country && country.names.en) || '',\n\t\t\t\t\tisoCode: (country && country.iso_code) || '',\n\t\t\t\t},\n\t\t\t\tcity: {\n\t\t\t\t\tname: (city && city.names.en) || '',\n\t\t\t\t\tisoCode: (city && city.iso_code) || '',\n\t\t\t\t},\n\t\t\t\tstate: {\n\t\t\t\t\tname: (state && state.names.en) || '',\n\t\t\t\t\tisoCode: (state && state.iso_code) || '',\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\treturn this._ipLoc;\n\t}\n\n\tipCountry() {\n\t\treturn this.ipLocation().country.name;\n\t}\n\n\tipCity() {\n\t\treturn this.ipLocation().city.name;\n\t}\n\n\tipState() {\n\t\treturn this.ipLocation().state.name;\n\t}\n\n\tgetABNumber(min = 0, max = false) {\n\t\tconst cookieId = this.cookieId() || 'nocookie';\n\t\tconst num = getIntegerKey(cookieId.substr(-4));\n\t\tif (max === false) return num;\n\t\treturn (num % ((max + 1) - min)) + min;\n\t}\n\n\tgetBot() {\n\t\tconst bots = [\n\t\t\t['googlebot', 'googlebot'],\n\t\t\t['googlepreview', 'google web preview'],\n\t\t\t['googlemobile', 'google wireless'],\n\t\t\t['googleadsbot', 'Mediapartners-Google'],\n\n\t\t\t['bingbot', 'bingbot', 'msnbot'],\n\n\t\t\t['yahoobot', 'yahoo'],\n\t\t\t['facebookbot', 'facebook'],\n\t\t\t['alexabot', 'ia_archiver'],\n\n\t\t\t['baidubot', 'baidu'],\n\t\t\t['twitterbot', 'twitter'],\n\n\t\t\t['bot', 'bot', 'spider', 'crawl', 'dig', 'search', 'http', 'url'],\n\t\t];\n\n\t\tconst userAgent = this.userAgent().toLowerCase();\n\t\tif (!userAgent) {\n\t\t\treturn 'emptybot';\n\t\t}\n\n\t\tfor (const bot of bots) {\n\t\t\tconst botName = bot[0];\n\t\t\tfor (let i = 1; i < bot.length; i++) {\n\t\t\t\tif (userAgent.includes(bot[i].toLowerCase())) {\n\t\t\t\t\treturn botName;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tisBot() {\n\t\treturn Boolean(this.getBot());\n\t}\n\n\tsupportsWebp() {\n\t\tconst accept = this.ctx.headers.accept || '';\n\t\treturn accept.includes('image/webp');\n\t}\n\n\tbestImageFormat(defaultFormat = 'jpg') {\n\t\tif (this.supportsWebp()) return 'webp';\n\t\treturn defaultFormat;\n\t}\n\n\thandleFlashMessage() {\n\t\tif (this.isAjax()) return;\n\n\t\tthis._flash = '';\n\t\tconst cookieVal = this.cookie(FLASH_COOKIE);\n\t\tif (!cookieVal) return;\n\n\t\ttry {\n\t\t\t// cookie format is `format:actualMessage`\n\t\t\tconst formatIndex = cookieVal.indexOf(':');\n\t\t\tconst format = cookieVal.substring(0, formatIndex);\n\t\t\tif (format !== 'json') {\n\t\t\t\tthrow new Error(`Unknown flash message format ${format}`);\n\t\t\t}\n\n\t\t\tthis._flash = JSON.parse(cookieVal.substring(formatIndex + 1));\n\t\t}\n\t\tcatch (e) {\n\t\t\tthis._flash = '';\n\t\t\tconsole.error('Error parsing flash message', e);\n\t\t}\n\n\t\tthis.cookie(FLASH_COOKIE, null);\n\t}\n\n\t/**\n\t * get or set a flash message (can be an object too)\n\t * @example\n\t * $req.flash('info', 'hello'); // set a flash message\n\t * $req.flash('info'); // get a flash message\n\t *\n\t * @param {string} key key of the flash message\n\t * @param {*} message message to set (skip to get the message)\n\t * @returns {*} flash message\n\t */\n\tflash(key, message) {\n\t\tif (message === undefined) {\n\t\t\treturn (this._flash && this._flash[key]) || '';\n\t\t}\n\n\t\t// ignore empty flash message\n\t\tif (!message) {\n\t\t\treturn null;\n\t\t}\n\n\t\tlet flashes = this._responseFlash;\n\t\tif (!flashes) {\n\t\t\tflashes = {};\n\t\t\tthis._responseFlash = flashes;\n\t\t}\n\n\t\tflashes[key] = message;\n\n\t\tconst maxAge = 3 * 60 * 1000; // valid till 3 minutes\n\t\tconst cookieValue = `json:${JSON.stringify(flashes)}`;\n\t\tthis.cookie(\n\t\t\tFLASH_COOKIE,\n\t\t\tcookieValue,\n\t\t\t{maxAge, httpOnly: false},\n\t\t);\n\t\treturn null;\n\t}\n\n\tstatic async getDummyCtx({\n\t\tparams = {},\n\t\tquery = {},\n\t\tstate = {},\n\t\tmethod = 'GET',\n\t\tpath = '/',\n\t\turl,\n\t\treq = false,\n\t} = {}) {\n\t\tconst context = {\n\t\t\tquery,\n\t\t\tparams,\n\t\t\tstate,\n\t\t\turl: url || path,\n\t\t\tpath,\n\t\t\tthrow: (...args) => {\n\t\t\t\tthrow new Error(args.join(' '));\n\t\t\t},\n\t\t\tmethod,\n\t\t\tsession: {},\n\t\t\theaders: {},\n\t\t\tcookies: new Map(),\n\t\t\thost: this.options.baseDomain || 'localhost',\n\t\t\thostname: this.options.baseDomain || 'localhost',\n\t\t\tredirect: (...args) => { console.warn('Redirect to', args) },\n\t\t\tset(key, val) {\n\t\t\t\tif (typeof key === 'string') {\n\t\t\t\t\tthis.headers[key] = val;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tObject.assign(this.headers, key);\n\t\t\t},\n\t\t};\n\t\tif (req) context.$req = await this.from(context);\n\t\treturn context;\n\t}\n}\n\nmodule.exports = Request;\n"
|
|
92
|
-
}
|
|
93
|
-
]
|
|
94
|
-
}
|
package/.lh/postinstall.js.json
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"sourceFile": "postinstall.js",
|
|
3
|
-
"activeCommit": 0,
|
|
4
|
-
"commits": [
|
|
5
|
-
{
|
|
6
|
-
"activePatchIndex": 1,
|
|
7
|
-
"patches": [
|
|
8
|
-
{
|
|
9
|
-
"date": 1622571930512,
|
|
10
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n"
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
"date": 1622571942798,
|
|
14
|
-
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -7,5 +7,6 @@\n main().then(() => {\n \tprocess.exit();\n }).catch((err) => {\n \tconsole.error('KoaRequest postinstall error', err);\n+\tprocess.exit(1);\n });\n"
|
|
15
|
-
}
|
|
16
|
-
],
|
|
17
|
-
"date": 1622571930512,
|
|
18
|
-
"name": "Commit-0",
|
|
19
|
-
"content": "const GeoIP = require('./GeoIP');\n\nasync function main() {\n\tawait GeoIP.init();\n}\n\nmain().then(() => {\n\tprocess.exit();\n}).catch((err) => {\n\tconsole.error('KoaRequest postinstall error', err);\n});\n"
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
}
|