advanced-tls-client 3.0.1 → 3.0.3
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/index.d.ts +29 -5
- package/dist/index.js +233 -397
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
1
2
|
import { Readable, PassThrough } from 'stream';
|
|
2
3
|
interface TLSFingerprint {
|
|
3
4
|
cipherSuites: number[];
|
|
@@ -119,13 +120,36 @@ interface RequestOptions {
|
|
|
119
120
|
content_type?: string;
|
|
120
121
|
uri_modifier?: Function;
|
|
121
122
|
}
|
|
123
|
+
interface Response extends EventEmitter {
|
|
124
|
+
statusCode: number;
|
|
125
|
+
headers: Record<string, string | string[]>;
|
|
126
|
+
body: Buffer | Readable | any;
|
|
127
|
+
text?: string;
|
|
128
|
+
json?: any;
|
|
129
|
+
timing: {
|
|
130
|
+
socket: number;
|
|
131
|
+
lookup: number;
|
|
132
|
+
connect: number;
|
|
133
|
+
secureConnect: number;
|
|
134
|
+
response: number;
|
|
135
|
+
end: number;
|
|
136
|
+
total: number;
|
|
137
|
+
};
|
|
138
|
+
fingerprints: {
|
|
139
|
+
ja3: string;
|
|
140
|
+
ja3Hash: string;
|
|
141
|
+
akamai: string;
|
|
142
|
+
};
|
|
143
|
+
bytes?: number;
|
|
144
|
+
raw?: Buffer;
|
|
145
|
+
cookies?: Record<string, string>;
|
|
146
|
+
parser?: string;
|
|
147
|
+
}
|
|
122
148
|
declare class ProfileRegistry {
|
|
123
149
|
private static getGrease;
|
|
124
150
|
static chromeMobile143Android(): ChromeProfile;
|
|
125
151
|
static chrome133PSK(): ChromeProfile;
|
|
126
152
|
static chrome133(): ChromeProfile;
|
|
127
|
-
static firefox117(): ChromeProfile;
|
|
128
|
-
static safariIOS18(): ChromeProfile;
|
|
129
153
|
static get(name: string): ChromeProfile;
|
|
130
154
|
static get latest(): ChromeProfile;
|
|
131
155
|
static get latestMobile(): ChromeProfile;
|
|
@@ -139,12 +163,12 @@ declare class AdvancedTLSClient {
|
|
|
139
163
|
private cleanupInterval;
|
|
140
164
|
private defaults;
|
|
141
165
|
constructor(profileName?: string);
|
|
142
|
-
setup
|
|
143
|
-
request(uri: string, data?: any, options?: RequestOptions, callback?:
|
|
166
|
+
private setup;
|
|
167
|
+
request(uri: string, data?: any, options?: RequestOptions, callback?: (err: any, res?: Response) => void): Promise<FullResponse | PassThrough>;
|
|
144
168
|
private generateJA3;
|
|
145
169
|
private buildMultipart;
|
|
146
170
|
private generateMultipart;
|
|
147
171
|
private flatten;
|
|
148
172
|
destroy(): void;
|
|
149
173
|
}
|
|
150
|
-
export { AdvancedTLSClient, ProfileRegistry
|
|
174
|
+
export { AdvancedTLSClient, ProfileRegistry };
|
package/dist/index.js
CHANGED
|
@@ -46,9 +46,9 @@ const stream_1 = require("stream");
|
|
|
46
46
|
const fs = __importStar(require("fs"));
|
|
47
47
|
const path = __importStar(require("path"));
|
|
48
48
|
const url = __importStar(require("url"));
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
49
|
+
const brotliDecompressAsync = (0, util_1.promisify)(zlib.brotliDecompress);
|
|
50
|
+
const gunzipAsync = (0, util_1.promisify)(zlib.gunzip);
|
|
51
|
+
const inflateAsync = (0, util_1.promisify)(zlib.inflate);
|
|
52
52
|
const GREASE_VALUES = [0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a, 0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a, 0x8a8a, 0x9a9a, 0xaaaa, 0xbaba, 0xcaca, 0xdada, 0xeaea, 0xfafa];
|
|
53
53
|
class ProfileRegistry {
|
|
54
54
|
static getGrease() {
|
|
@@ -414,202 +414,16 @@ class ProfileRegistry {
|
|
|
414
414
|
secFetchDest: 'document'
|
|
415
415
|
};
|
|
416
416
|
}
|
|
417
|
-
static firefox117() {
|
|
418
|
-
return {
|
|
419
|
-
name: 'firefox_117',
|
|
420
|
-
version: '117.0',
|
|
421
|
-
tls: {
|
|
422
|
-
cipherSuites: [
|
|
423
|
-
4865,
|
|
424
|
-
4867,
|
|
425
|
-
4866,
|
|
426
|
-
49195,
|
|
427
|
-
49199,
|
|
428
|
-
52393,
|
|
429
|
-
52392,
|
|
430
|
-
49196,
|
|
431
|
-
49200,
|
|
432
|
-
159,
|
|
433
|
-
158,
|
|
434
|
-
49161,
|
|
435
|
-
49162,
|
|
436
|
-
156,
|
|
437
|
-
157,
|
|
438
|
-
47,
|
|
439
|
-
53
|
|
440
|
-
],
|
|
441
|
-
extensions: [0, 23, 65281, 10, 11, 35, 16, 5, 34, 51, 43, 13, 45, 28, 21],
|
|
442
|
-
supportedGroups: [
|
|
443
|
-
29,
|
|
444
|
-
23,
|
|
445
|
-
24,
|
|
446
|
-
25,
|
|
447
|
-
256,
|
|
448
|
-
257
|
|
449
|
-
],
|
|
450
|
-
signatureAlgorithms: [
|
|
451
|
-
1027,
|
|
452
|
-
1283,
|
|
453
|
-
1539,
|
|
454
|
-
2052,
|
|
455
|
-
2053,
|
|
456
|
-
2054,
|
|
457
|
-
1025,
|
|
458
|
-
1281,
|
|
459
|
-
1537,
|
|
460
|
-
513,
|
|
461
|
-
515
|
|
462
|
-
],
|
|
463
|
-
supportedVersions: [
|
|
464
|
-
772,
|
|
465
|
-
771
|
|
466
|
-
],
|
|
467
|
-
ecPointFormats: [0],
|
|
468
|
-
alpnProtocols: ['h2', 'http/1.1'],
|
|
469
|
-
pskKeyExchangeModes: [1],
|
|
470
|
-
compressionMethods: [0],
|
|
471
|
-
grease: false
|
|
472
|
-
},
|
|
473
|
-
http2: {
|
|
474
|
-
windowUpdate: 12517377,
|
|
475
|
-
headerTableSize: 65536,
|
|
476
|
-
enablePush: 0,
|
|
477
|
-
initialWindowSize: 131072,
|
|
478
|
-
maxFrameSize: 16384,
|
|
479
|
-
settingsOrder: [1, 4, 5],
|
|
480
|
-
connectionPreface: Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'),
|
|
481
|
-
priorityFrames: [
|
|
482
|
-
{ streamId: 3, weight: 200, dependency: 0, exclusive: false },
|
|
483
|
-
{ streamId: 5, weight: 100, dependency: 0, exclusive: false },
|
|
484
|
-
{ streamId: 7, weight: 0, dependency: 0, exclusive: false },
|
|
485
|
-
{ streamId: 9, weight: 0, dependency: 7, exclusive: false },
|
|
486
|
-
{ streamId: 11, weight: 0, dependency: 3, exclusive: false },
|
|
487
|
-
{ streamId: 13, weight: 240, dependency: 0, exclusive: false }
|
|
488
|
-
],
|
|
489
|
-
pseudoHeaderOrder: [':method', ':path', ':authority', ':scheme'],
|
|
490
|
-
headerPriority: { streamDep: 13, exclusive: false, weight: 41 },
|
|
491
|
-
headerOrder: []
|
|
492
|
-
},
|
|
493
|
-
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:117.0) Gecko/20100101 Firefox/117.0',
|
|
494
|
-
secChUa: '',
|
|
495
|
-
secChUaPlatform: '',
|
|
496
|
-
secChUaMobile: '',
|
|
497
|
-
secFetchSite: 'none',
|
|
498
|
-
secFetchMode: 'navigate',
|
|
499
|
-
secFetchDest: 'document'
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
static safariIOS18() {
|
|
503
|
-
const grease1 = this.getGrease();
|
|
504
|
-
const grease2 = this.getGrease();
|
|
505
|
-
return {
|
|
506
|
-
name: 'safari_ios_18_0',
|
|
507
|
-
version: '18.0',
|
|
508
|
-
tls: {
|
|
509
|
-
cipherSuites: [
|
|
510
|
-
grease1,
|
|
511
|
-
4865,
|
|
512
|
-
4866,
|
|
513
|
-
4867,
|
|
514
|
-
49196,
|
|
515
|
-
49195,
|
|
516
|
-
52393,
|
|
517
|
-
49200,
|
|
518
|
-
49199,
|
|
519
|
-
52392,
|
|
520
|
-
159,
|
|
521
|
-
158,
|
|
522
|
-
49162,
|
|
523
|
-
49161,
|
|
524
|
-
157,
|
|
525
|
-
156,
|
|
526
|
-
53,
|
|
527
|
-
47,
|
|
528
|
-
49188,
|
|
529
|
-
49187,
|
|
530
|
-
60
|
|
531
|
-
],
|
|
532
|
-
extensions: [
|
|
533
|
-
grease2,
|
|
534
|
-
0,
|
|
535
|
-
23,
|
|
536
|
-
65281,
|
|
537
|
-
10,
|
|
538
|
-
11,
|
|
539
|
-
16,
|
|
540
|
-
5,
|
|
541
|
-
13,
|
|
542
|
-
18,
|
|
543
|
-
51,
|
|
544
|
-
45,
|
|
545
|
-
43,
|
|
546
|
-
21
|
|
547
|
-
],
|
|
548
|
-
supportedGroups: [
|
|
549
|
-
29,
|
|
550
|
-
23,
|
|
551
|
-
24,
|
|
552
|
-
25
|
|
553
|
-
],
|
|
554
|
-
signatureAlgorithms: [
|
|
555
|
-
1027,
|
|
556
|
-
2052,
|
|
557
|
-
1025,
|
|
558
|
-
1283,
|
|
559
|
-
513,
|
|
560
|
-
2053,
|
|
561
|
-
2053,
|
|
562
|
-
1281,
|
|
563
|
-
2054,
|
|
564
|
-
1537,
|
|
565
|
-
515
|
|
566
|
-
],
|
|
567
|
-
supportedVersions: [
|
|
568
|
-
772,
|
|
569
|
-
771,
|
|
570
|
-
770,
|
|
571
|
-
769
|
|
572
|
-
],
|
|
573
|
-
ecPointFormats: [0],
|
|
574
|
-
alpnProtocols: ['h2', 'http/1.1'],
|
|
575
|
-
pskKeyExchangeModes: [1],
|
|
576
|
-
compressionMethods: [0],
|
|
577
|
-
grease: true
|
|
578
|
-
},
|
|
579
|
-
http2: {
|
|
580
|
-
windowUpdate: 10420225,
|
|
581
|
-
headerTableSize: 4096,
|
|
582
|
-
enablePush: 0,
|
|
583
|
-
maxConcurrentStreams: 100,
|
|
584
|
-
initialWindowSize: 2097152,
|
|
585
|
-
maxFrameSize: 16384,
|
|
586
|
-
settingsOrder: [2, 3, 4, 8, 9],
|
|
587
|
-
connectionPreface: Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'),
|
|
588
|
-
priorityFrames: [],
|
|
589
|
-
pseudoHeaderOrder: [':method', ':scheme', ':authority', ':path'],
|
|
590
|
-
headerOrder: []
|
|
591
|
-
},
|
|
592
|
-
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1',
|
|
593
|
-
secChUa: '',
|
|
594
|
-
secChUaPlatform: '',
|
|
595
|
-
secChUaMobile: '?1',
|
|
596
|
-
secFetchSite: 'none',
|
|
597
|
-
secFetchMode: 'navigate',
|
|
598
|
-
secFetchDest: 'document'
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
417
|
static get(name) {
|
|
602
418
|
switch (name.toLowerCase()) {
|
|
603
419
|
case 'chrome_mobile_143_android': return this.chromeMobile143Android();
|
|
604
420
|
case 'chrome_133_psk': return this.chrome133PSK();
|
|
605
421
|
case 'chrome_133': return this.chrome133();
|
|
606
|
-
|
|
607
|
-
case 'safari_ios_18_0': return this.safariIOS18();
|
|
608
|
-
default: return this.chrome133();
|
|
422
|
+
default: return this.chromeMobile143Android();
|
|
609
423
|
}
|
|
610
424
|
}
|
|
611
425
|
static get latest() {
|
|
612
|
-
return this.
|
|
426
|
+
return this.chromeMobile143Android();
|
|
613
427
|
}
|
|
614
428
|
static get latestMobile() {
|
|
615
429
|
return this.chromeMobile143Android();
|
|
@@ -675,16 +489,15 @@ class CookieJar {
|
|
|
675
489
|
}
|
|
676
490
|
}
|
|
677
491
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
})(Protocol || (Protocol = {}));
|
|
492
|
+
const Protocol = {
|
|
493
|
+
HTTP1: 'http1',
|
|
494
|
+
HTTP2: 'http2'
|
|
495
|
+
};
|
|
683
496
|
class UnifiedClientManager extends events_1.EventEmitter {
|
|
684
497
|
constructor(tlsSocket, profile, hostname, cookieJar) {
|
|
685
498
|
super();
|
|
686
499
|
this.http2Client = null;
|
|
687
|
-
this.negotiatedProtocol =
|
|
500
|
+
this.negotiatedProtocol = 'http1';
|
|
688
501
|
this.tlsSocket = tlsSocket;
|
|
689
502
|
this.profile = profile;
|
|
690
503
|
this.hostname = hostname;
|
|
@@ -693,17 +506,17 @@ class UnifiedClientManager extends events_1.EventEmitter {
|
|
|
693
506
|
async initialize() {
|
|
694
507
|
const alpn = this.tlsSocket.alpnProtocol;
|
|
695
508
|
if (alpn === 'h2') {
|
|
696
|
-
this.negotiatedProtocol =
|
|
509
|
+
this.negotiatedProtocol = 'http2';
|
|
697
510
|
await this.createHttp2Session();
|
|
698
511
|
}
|
|
699
512
|
else {
|
|
700
|
-
this.negotiatedProtocol =
|
|
513
|
+
this.negotiatedProtocol = 'http1';
|
|
701
514
|
}
|
|
702
515
|
}
|
|
703
516
|
async createHttp2Session() {
|
|
704
517
|
return new Promise((resolve, reject) => {
|
|
705
518
|
const settings = {};
|
|
706
|
-
this.profile.http2.settingsOrder.forEach(id => {
|
|
519
|
+
this.profile.http2.settingsOrder.forEach((id) => {
|
|
707
520
|
switch (id) {
|
|
708
521
|
case 1:
|
|
709
522
|
settings.headerTableSize = this.profile.http2.headerTableSize;
|
|
@@ -711,17 +524,9 @@ class UnifiedClientManager extends events_1.EventEmitter {
|
|
|
711
524
|
case 2:
|
|
712
525
|
settings.enablePush = this.profile.http2.enablePush === 1;
|
|
713
526
|
break;
|
|
714
|
-
case 3:
|
|
715
|
-
if (this.profile.http2.maxConcurrentStreams)
|
|
716
|
-
settings.maxConcurrentStreams = this.profile.http2.maxConcurrentStreams;
|
|
717
|
-
break;
|
|
718
527
|
case 4:
|
|
719
528
|
settings.initialWindowSize = this.profile.http2.initialWindowSize;
|
|
720
529
|
break;
|
|
721
|
-
case 5:
|
|
722
|
-
if (this.profile.http2.maxFrameSize)
|
|
723
|
-
settings.maxFrameSize = this.profile.http2.maxFrameSize;
|
|
724
|
-
break;
|
|
725
530
|
case 6:
|
|
726
531
|
if (this.profile.http2.maxHeaderListSize)
|
|
727
532
|
settings.maxHeaderListSize = this.profile.http2.maxHeaderListSize;
|
|
@@ -732,7 +537,7 @@ class UnifiedClientManager extends events_1.EventEmitter {
|
|
|
732
537
|
createConnection: () => this.tlsSocket,
|
|
733
538
|
settings
|
|
734
539
|
});
|
|
735
|
-
this.http2Client.once('connect',
|
|
540
|
+
this.http2Client.once('connect', resolve);
|
|
736
541
|
this.http2Client.once('error', reject);
|
|
737
542
|
this.http2Client.once('timeout', () => reject(new Error('HTTP/2 session timeout')));
|
|
738
543
|
});
|
|
@@ -749,7 +554,7 @@ class UnifiedClientManager extends events_1.EventEmitter {
|
|
|
749
554
|
delete cleanHeaders['Upgrade'];
|
|
750
555
|
delete cleanHeaders['transfer-encoding'];
|
|
751
556
|
delete cleanHeaders['Transfer-Encoding'];
|
|
752
|
-
if (this.negotiatedProtocol ===
|
|
557
|
+
if (this.negotiatedProtocol === 'http2' && this.http2Client && !this.http2Client.destroyed) {
|
|
753
558
|
await this.requestHttp2(path, options, timingStart, cleanHeaders, post_data, out, callback);
|
|
754
559
|
}
|
|
755
560
|
else {
|
|
@@ -757,13 +562,12 @@ class UnifiedClientManager extends events_1.EventEmitter {
|
|
|
757
562
|
}
|
|
758
563
|
}
|
|
759
564
|
async requestHttp2(path, options, timingStart, headers, post_data, out, callback) {
|
|
760
|
-
|
|
761
|
-
throw new Error('HTTP/2 session not available');
|
|
565
|
+
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
762
566
|
const reqHeaders = {
|
|
763
567
|
':method': options.method?.toUpperCase() || 'GET',
|
|
764
568
|
':authority': this.hostname,
|
|
765
569
|
':scheme': 'https',
|
|
766
|
-
':path':
|
|
570
|
+
':path': normalizedPath,
|
|
767
571
|
...headers
|
|
768
572
|
};
|
|
769
573
|
const stream = this.http2Client.request(reqHeaders);
|
|
@@ -774,84 +578,112 @@ class UnifiedClientManager extends events_1.EventEmitter {
|
|
|
774
578
|
ja3Hash: crypto.createHash('md5').update(this.generateJA3()).digest('hex'),
|
|
775
579
|
akamai: `1:${this.profile.http2.headerTableSize};2:${this.profile.http2.enablePush};4:${this.profile.http2.initialWindowSize};6:${this.profile.http2.maxHeaderListSize ?? ''}|00|0|m,a,s,p`
|
|
776
580
|
};
|
|
777
|
-
stream.once('response', (hdrs) => {
|
|
778
|
-
response.statusCode = hdrs[':status'];
|
|
779
|
-
response.headers = hdrs;
|
|
780
|
-
if (hdrs['set-cookie'])
|
|
781
|
-
this.cookieJar.parseSetCookie(this.hostname, hdrs['set-cookie']);
|
|
782
|
-
response.timing.response = Date.now() - timingStart;
|
|
783
|
-
out.emit('header', response.statusCode, hdrs);
|
|
784
|
-
out.emit('headers', hdrs);
|
|
785
|
-
});
|
|
786
581
|
const chunks = [];
|
|
787
582
|
const rawChunks = [];
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
583
|
+
let finished = false;
|
|
584
|
+
let hasHeaders = false;
|
|
585
|
+
const finish = async (error) => {
|
|
586
|
+
if (finished)
|
|
587
|
+
return;
|
|
588
|
+
finished = true;
|
|
589
|
+
stream.removeAllListeners();
|
|
590
|
+
if (error) {
|
|
591
|
+
out.emit('error', error);
|
|
592
|
+
if (callback)
|
|
593
|
+
setImmediate(() => callback(error));
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
796
596
|
let body = Buffer.concat(chunks);
|
|
797
597
|
response.bytes = body.length;
|
|
798
598
|
response.raw = Buffer.concat(rawChunks);
|
|
799
599
|
if (options.decompress !== false && response.headers) {
|
|
800
|
-
const
|
|
801
|
-
const encoding = Array.isArray(encodingHeader) ? encodingHeader[0]?.toLowerCase() : typeof encodingHeader === 'string' ? encodingHeader.toLowerCase() : undefined;
|
|
600
|
+
const encoding = response.headers['content-encoding']?.toLowerCase();
|
|
802
601
|
if (encoding) {
|
|
803
602
|
try {
|
|
804
603
|
if (encoding.includes('gzip'))
|
|
805
|
-
body = await
|
|
604
|
+
body = await gunzipAsync(body);
|
|
806
605
|
else if (encoding.includes('br'))
|
|
807
|
-
body = await
|
|
606
|
+
body = await brotliDecompressAsync(body);
|
|
808
607
|
else if (encoding.includes('deflate'))
|
|
809
|
-
body = await
|
|
810
|
-
}
|
|
811
|
-
catch (e) {
|
|
812
|
-
console.error('Decompression error:', e);
|
|
608
|
+
body = await inflateAsync(body);
|
|
813
609
|
}
|
|
610
|
+
catch { }
|
|
814
611
|
}
|
|
815
612
|
}
|
|
816
613
|
response.body = body;
|
|
817
614
|
try {
|
|
818
615
|
response.text = body.toString('utf-8');
|
|
819
|
-
|
|
820
|
-
const ct = Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : typeof contentTypeHeader === 'string' ? contentTypeHeader : undefined;
|
|
821
|
-
if (ct && ct.includes('application/json')) {
|
|
616
|
+
if (response.headers['content-type']?.includes('application/json')) {
|
|
822
617
|
response.json = JSON.parse(response.text);
|
|
823
618
|
}
|
|
824
619
|
}
|
|
825
620
|
catch { }
|
|
826
621
|
response.timing.end = Date.now() - timingStart;
|
|
827
622
|
response.timing.total = response.timing.end;
|
|
828
|
-
out.
|
|
623
|
+
if (!out.writableEnded && !out.destroyed)
|
|
624
|
+
out.end();
|
|
625
|
+
};
|
|
626
|
+
stream.once('response', (hdrs) => {
|
|
627
|
+
hasHeaders = true;
|
|
628
|
+
response.statusCode = hdrs[':status'];
|
|
629
|
+
response.headers = hdrs;
|
|
630
|
+
if (hdrs['set-cookie'])
|
|
631
|
+
this.cookieJar.parseSetCookie(this.hostname, hdrs['set-cookie']);
|
|
632
|
+
response.timing.response = Date.now() - timingStart;
|
|
633
|
+
out.emit('header', response.statusCode, hdrs);
|
|
634
|
+
out.emit('headers', hdrs);
|
|
829
635
|
if (callback)
|
|
830
|
-
callback(null, response
|
|
636
|
+
setImmediate(() => callback(null, response));
|
|
831
637
|
});
|
|
832
|
-
stream.
|
|
833
|
-
out.
|
|
834
|
-
|
|
835
|
-
|
|
638
|
+
stream.on('data', (chunk) => {
|
|
639
|
+
if (finished || out.destroyed || out.writableEnded)
|
|
640
|
+
return;
|
|
641
|
+
const bufferChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
642
|
+
rawChunks.push(bufferChunk);
|
|
643
|
+
chunks.push(bufferChunk);
|
|
644
|
+
if (!out.write(bufferChunk))
|
|
645
|
+
stream.pause();
|
|
836
646
|
});
|
|
647
|
+
out.on('drain', () => stream.resume());
|
|
648
|
+
stream.once('end', finish);
|
|
649
|
+
stream.once('error', finish);
|
|
837
650
|
if (post_data) {
|
|
838
|
-
if (Buffer.isBuffer(post_data) || typeof post_data === 'string')
|
|
839
|
-
stream.
|
|
840
|
-
|
|
841
|
-
|
|
651
|
+
if (Buffer.isBuffer(post_data) || typeof post_data === 'string') {
|
|
652
|
+
if (!stream.closed && !stream.destroyed && stream.writable) {
|
|
653
|
+
try {
|
|
654
|
+
stream.write(post_data);
|
|
655
|
+
}
|
|
656
|
+
catch { }
|
|
657
|
+
}
|
|
658
|
+
setImmediate(() => {
|
|
659
|
+
if (!stream.closed && !stream.destroyed && stream.writable) {
|
|
660
|
+
stream.end();
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
else if (post_data instanceof stream_1.Readable) {
|
|
665
|
+
post_data.pipe(stream, { end: true });
|
|
666
|
+
post_data.on('error', err => stream.destroy(err));
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
setImmediate(() => {
|
|
670
|
+
if (stream.writable)
|
|
671
|
+
stream.end();
|
|
672
|
+
});
|
|
673
|
+
}
|
|
842
674
|
}
|
|
843
675
|
else {
|
|
844
|
-
|
|
676
|
+
setImmediate(() => {
|
|
677
|
+
if (stream.writable)
|
|
678
|
+
stream.end();
|
|
679
|
+
});
|
|
845
680
|
}
|
|
846
681
|
}
|
|
847
682
|
async requestHttp1(path, options, timingStart, headers, post_data, out, callback) {
|
|
848
683
|
const reqOptions = {
|
|
849
684
|
method: options.method?.toUpperCase() || 'GET',
|
|
850
685
|
path,
|
|
851
|
-
headers: {
|
|
852
|
-
host: this.hostname,
|
|
853
|
-
...headers
|
|
854
|
-
},
|
|
686
|
+
headers: { host: this.hostname, ...headers },
|
|
855
687
|
createConnection: () => this.tlsSocket
|
|
856
688
|
};
|
|
857
689
|
const req = https.request(reqOptions);
|
|
@@ -862,7 +694,55 @@ class UnifiedClientManager extends events_1.EventEmitter {
|
|
|
862
694
|
ja3Hash: crypto.createHash('md5').update(this.generateJA3()).digest('hex'),
|
|
863
695
|
akamai: ''
|
|
864
696
|
};
|
|
697
|
+
const chunks = [];
|
|
698
|
+
const rawChunks = [];
|
|
699
|
+
let finished = false;
|
|
700
|
+
let hasHeaders = false;
|
|
701
|
+
const finish = async (error, res) => {
|
|
702
|
+
if (finished)
|
|
703
|
+
return;
|
|
704
|
+
finished = true;
|
|
705
|
+
req.removeAllListeners();
|
|
706
|
+
if (res)
|
|
707
|
+
res.removeAllListeners();
|
|
708
|
+
if (error) {
|
|
709
|
+
out.emit('error', error);
|
|
710
|
+
if (callback)
|
|
711
|
+
setImmediate(() => callback(error));
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
let body = Buffer.concat(chunks);
|
|
715
|
+
response.bytes = body.length;
|
|
716
|
+
response.raw = Buffer.concat(rawChunks);
|
|
717
|
+
if (options.decompress !== false && res?.headers) {
|
|
718
|
+
const encoding = (res.headers['content-encoding'] || '').toLowerCase();
|
|
719
|
+
if (encoding) {
|
|
720
|
+
try {
|
|
721
|
+
if (encoding.includes('gzip'))
|
|
722
|
+
body = await gunzipAsync(body);
|
|
723
|
+
else if (encoding.includes('br'))
|
|
724
|
+
body = await brotliDecompressAsync(body);
|
|
725
|
+
else if (encoding.includes('deflate'))
|
|
726
|
+
body = await inflateAsync(body);
|
|
727
|
+
}
|
|
728
|
+
catch { }
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
response.body = body;
|
|
732
|
+
try {
|
|
733
|
+
response.text = body.toString('utf-8');
|
|
734
|
+
if (res.headers['content-type']?.includes('application/json')) {
|
|
735
|
+
response.json = JSON.parse(response.text);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
catch { }
|
|
739
|
+
response.timing.end = Date.now() - timingStart;
|
|
740
|
+
response.timing.total = response.timing.end;
|
|
741
|
+
if (!out.writableEnded && !out.destroyed)
|
|
742
|
+
out.end();
|
|
743
|
+
};
|
|
865
744
|
req.once('response', (res) => {
|
|
745
|
+
hasHeaders = true;
|
|
866
746
|
response.statusCode = res.statusCode;
|
|
867
747
|
response.headers = res.headers;
|
|
868
748
|
if (res.headers['set-cookie'])
|
|
@@ -870,68 +750,31 @@ class UnifiedClientManager extends events_1.EventEmitter {
|
|
|
870
750
|
response.timing.response = Date.now() - timingStart;
|
|
871
751
|
out.emit('header', response.statusCode, res.headers);
|
|
872
752
|
out.emit('headers', res.headers);
|
|
873
|
-
|
|
874
|
-
|
|
753
|
+
if (callback)
|
|
754
|
+
setImmediate(() => callback(null, response));
|
|
875
755
|
res.on('data', (chunk) => {
|
|
876
|
-
|
|
756
|
+
if (finished || out.destroyed || out.writableEnded)
|
|
757
|
+
return;
|
|
877
758
|
const bufferChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
878
759
|
rawChunks.push(bufferChunk);
|
|
879
760
|
chunks.push(bufferChunk);
|
|
880
761
|
out.write(bufferChunk);
|
|
881
762
|
});
|
|
882
|
-
res.once('end',
|
|
883
|
-
|
|
884
|
-
response.bytes = body.length;
|
|
885
|
-
response.raw = Buffer.concat(rawChunks);
|
|
886
|
-
if (options.decompress !== false) {
|
|
887
|
-
const encodingHeader = res.headers['content-encoding'];
|
|
888
|
-
const encoding = Array.isArray(encodingHeader) ? encodingHeader[0]?.toLowerCase() : typeof encodingHeader === 'string' ? encodingHeader.toLowerCase() : undefined;
|
|
889
|
-
if (encoding) {
|
|
890
|
-
try {
|
|
891
|
-
if (encoding.includes('gzip'))
|
|
892
|
-
body = await gunzip(body);
|
|
893
|
-
else if (encoding.includes('br'))
|
|
894
|
-
body = await brotliDecompress(body);
|
|
895
|
-
else if (encoding.includes('deflate'))
|
|
896
|
-
body = await inflate(body);
|
|
897
|
-
}
|
|
898
|
-
catch (e) {
|
|
899
|
-
console.error('Decompression error:', e);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
response.body = body;
|
|
904
|
-
try {
|
|
905
|
-
response.text = body.toString('utf-8');
|
|
906
|
-
const contentTypeHeader = res.headers['content-type'];
|
|
907
|
-
const ct = Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : typeof contentTypeHeader === 'string' ? contentTypeHeader : undefined;
|
|
908
|
-
if (ct && ct.includes('application/json')) {
|
|
909
|
-
response.json = JSON.parse(response.text);
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
catch { }
|
|
913
|
-
response.timing.end = Date.now() - timingStart;
|
|
914
|
-
response.timing.total = response.timing.end;
|
|
915
|
-
out.end();
|
|
916
|
-
if (callback)
|
|
917
|
-
callback(null, response, response.body);
|
|
918
|
-
});
|
|
919
|
-
res.once('error', (err) => {
|
|
920
|
-
out.emit('err', err);
|
|
921
|
-
if (callback)
|
|
922
|
-
callback(err);
|
|
923
|
-
});
|
|
924
|
-
});
|
|
925
|
-
req.once('error', (err) => {
|
|
926
|
-
out.emit('err', err);
|
|
927
|
-
if (callback)
|
|
928
|
-
callback(err);
|
|
763
|
+
res.once('end', () => finish(null, res));
|
|
764
|
+
res.once('error', finish);
|
|
929
765
|
});
|
|
766
|
+
req.once('error', finish);
|
|
930
767
|
if (post_data) {
|
|
931
|
-
if (Buffer.isBuffer(post_data) || typeof post_data === 'string')
|
|
932
|
-
req.
|
|
933
|
-
|
|
768
|
+
if (Buffer.isBuffer(post_data) || typeof post_data === 'string') {
|
|
769
|
+
req.write(post_data);
|
|
770
|
+
req.end();
|
|
771
|
+
}
|
|
772
|
+
else if (post_data instanceof stream_1.Readable) {
|
|
934
773
|
post_data.pipe(req);
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
req.end();
|
|
777
|
+
}
|
|
935
778
|
}
|
|
936
779
|
else {
|
|
937
780
|
req.end();
|
|
@@ -939,9 +782,9 @@ class UnifiedClientManager extends events_1.EventEmitter {
|
|
|
939
782
|
}
|
|
940
783
|
generateJA3() {
|
|
941
784
|
const version = '771';
|
|
942
|
-
const ciphers = this.profile.tls.cipherSuites.filter(c => c < 0xff00).join('-');
|
|
943
|
-
const extensions = this.profile.tls.extensions.filter(e => typeof e === 'number' && e < 0xff00).join('-');
|
|
944
|
-
const curves = this.profile.tls.supportedGroups.filter(g => g < 0xff00).join('-');
|
|
785
|
+
const ciphers = this.profile.tls.cipherSuites.filter((c) => c < 0xff00).join('-');
|
|
786
|
+
const extensions = this.profile.tls.extensions.filter((e) => typeof e === 'number' && e < 0xff00).join('-');
|
|
787
|
+
const curves = this.profile.tls.supportedGroups.filter((g) => g < 0xff00).join('-');
|
|
945
788
|
const ecPoints = this.profile.tls.ecPointFormats.join('-');
|
|
946
789
|
return `${version},${ciphers},${extensions},${curves},${ecPoints}`;
|
|
947
790
|
}
|
|
@@ -950,9 +793,6 @@ class UnifiedClientManager extends events_1.EventEmitter {
|
|
|
950
793
|
this.http2Client.destroy();
|
|
951
794
|
this.http2Client = null;
|
|
952
795
|
}
|
|
953
|
-
getProtocol() {
|
|
954
|
-
return this.negotiatedProtocol;
|
|
955
|
-
}
|
|
956
796
|
}
|
|
957
797
|
class TLSSocketManager extends events_1.EventEmitter {
|
|
958
798
|
constructor(profile, proxy) {
|
|
@@ -983,6 +823,7 @@ class TLSSocketManager extends events_1.EventEmitter {
|
|
|
983
823
|
if (this.proxy) {
|
|
984
824
|
const proxyUrl = new url.URL(this.proxy);
|
|
985
825
|
this.socket = net.connect(+proxyUrl.port || 80, proxyUrl.hostname, () => {
|
|
826
|
+
this.timing.connect = Date.now() - this.startTime;
|
|
986
827
|
let request = `CONNECT ${hostname}:${port} HTTP/1.1\r\nHost: ${hostname}:${port}\r\n`;
|
|
987
828
|
if (proxyUrl.username && proxyUrl.password) {
|
|
988
829
|
const auth = Buffer.from(`${decodeURIComponent(proxyUrl.username)}:${decodeURIComponent(proxyUrl.password)}`).toString('base64');
|
|
@@ -992,14 +833,19 @@ class TLSSocketManager extends events_1.EventEmitter {
|
|
|
992
833
|
this.socket.write(request);
|
|
993
834
|
});
|
|
994
835
|
let response = '';
|
|
995
|
-
|
|
836
|
+
const onData = (chunk) => {
|
|
996
837
|
response += chunk.toString();
|
|
997
838
|
if (response.includes('\r\n\r\n')) {
|
|
998
|
-
|
|
839
|
+
this.socket.removeListener('data', onData);
|
|
840
|
+
if (!response.startsWith('HTTP/1.1 200')) {
|
|
841
|
+
clearTimeout(timeout);
|
|
842
|
+
this.destroy();
|
|
999
843
|
return reject(new Error('Proxy failed: ' + response.split('\r\n')[0]));
|
|
844
|
+
}
|
|
1000
845
|
this.proceedWithTLS(resolve, reject, timeout);
|
|
1001
846
|
}
|
|
1002
|
-
}
|
|
847
|
+
};
|
|
848
|
+
this.socket.on('data', onData);
|
|
1003
849
|
}
|
|
1004
850
|
else {
|
|
1005
851
|
this.socket = new net.Socket();
|
|
@@ -1107,7 +953,7 @@ class AdvancedTLSClient {
|
|
|
1107
953
|
follow_if_same_location: false,
|
|
1108
954
|
use_proxy_from_env_var: true
|
|
1109
955
|
};
|
|
1110
|
-
this.profile = ProfileRegistry.get(profileName || '
|
|
956
|
+
this.profile = ProfileRegistry.get(profileName || 'chrome_mobile_143_android');
|
|
1111
957
|
this.cleanupInterval = setInterval(() => {
|
|
1112
958
|
const now = Date.now();
|
|
1113
959
|
for (const [key, session] of this.sessionCache) {
|
|
@@ -1121,7 +967,7 @@ class AdvancedTLSClient {
|
|
|
1121
967
|
}
|
|
1122
968
|
setup(uri, options) {
|
|
1123
969
|
const config = {
|
|
1124
|
-
headers: {
|
|
970
|
+
headers: {},
|
|
1125
971
|
proxy: options.proxy || this.defaults.proxy,
|
|
1126
972
|
decompress: options.decompress !== undefined ? options.decompress : true
|
|
1127
973
|
};
|
|
@@ -1139,6 +985,10 @@ class AdvancedTLSClient {
|
|
|
1139
985
|
config.headers['upgrade-insecure-requests'] = this.profile.upgradeInsecureRequests;
|
|
1140
986
|
if (this.profile.priority)
|
|
1141
987
|
config.headers['priority'] = this.profile.priority;
|
|
988
|
+
// Override with user headers
|
|
989
|
+
if (options.headers) {
|
|
990
|
+
Object.assign(config.headers, options.headers);
|
|
991
|
+
}
|
|
1142
992
|
return config;
|
|
1143
993
|
}
|
|
1144
994
|
async request(uri, data, options = {}, callback) {
|
|
@@ -1146,6 +996,9 @@ class AdvancedTLSClient {
|
|
|
1146
996
|
callback = options;
|
|
1147
997
|
options = {};
|
|
1148
998
|
}
|
|
999
|
+
if (data && typeof data !== 'object' && !Buffer.isBuffer(data) && !(data instanceof stream_1.Readable)) {
|
|
1000
|
+
throw new Error('Invalid data type');
|
|
1001
|
+
}
|
|
1149
1002
|
const parsed = new url.URL(uri);
|
|
1150
1003
|
const hostname = parsed.hostname;
|
|
1151
1004
|
const port = parsed.port ? +parsed.port : 443;
|
|
@@ -1171,12 +1024,15 @@ class AdvancedTLSClient {
|
|
|
1171
1024
|
session.lastUsed = Date.now();
|
|
1172
1025
|
const config = this.setup(uri, options);
|
|
1173
1026
|
let post_data = null;
|
|
1174
|
-
let json = options.json
|
|
1027
|
+
let json = options.json !== undefined ? options.json : false;
|
|
1028
|
+
if (json === undefined && config.headers['content-type']?.includes('application/json')) {
|
|
1029
|
+
json = true;
|
|
1030
|
+
}
|
|
1175
1031
|
if (data) {
|
|
1176
1032
|
if (options.multipart) {
|
|
1177
1033
|
const boundary = options.boundary || this.defaults.boundary;
|
|
1178
1034
|
post_data = await this.buildMultipart(data, boundary);
|
|
1179
|
-
config.headers['content-type'] =
|
|
1035
|
+
config.headers['content-type'] = `multipart/form-data; boundary=${boundary}`;
|
|
1180
1036
|
}
|
|
1181
1037
|
else if (data instanceof stream_1.Readable) {
|
|
1182
1038
|
post_data = data;
|
|
@@ -1202,78 +1058,62 @@ class AdvancedTLSClient {
|
|
|
1202
1058
|
const cookies = this.cookieJar.getCookies(hostname, parsed.pathname, parsed.protocol === 'https:');
|
|
1203
1059
|
if (cookies)
|
|
1204
1060
|
config.headers['cookie'] = cookies;
|
|
1205
|
-
|
|
1061
|
+
if (options.stream === true) {
|
|
1062
|
+
session.clientManager.request(path, options, startTime, config.headers, post_data, out);
|
|
1063
|
+
return out;
|
|
1064
|
+
}
|
|
1206
1065
|
if (callback) {
|
|
1207
|
-
|
|
1066
|
+
session.clientManager.request(path, options, startTime, config.headers, post_data, out, callback).catch(err => {
|
|
1067
|
+
if (callback)
|
|
1068
|
+
callback(err);
|
|
1069
|
+
});
|
|
1208
1070
|
return out;
|
|
1209
1071
|
}
|
|
1210
|
-
// MODE BARU: Kembalikan object response lengkap
|
|
1211
1072
|
return new Promise((resolve, reject) => {
|
|
1212
1073
|
let statusCode = 0;
|
|
1213
1074
|
let headers = {};
|
|
1214
1075
|
const chunks = [];
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
out.on('data', (chunk) => {
|
|
1223
|
-
chunks.push(chunk);
|
|
1224
|
-
});
|
|
1225
|
-
out.once('end', async () => {
|
|
1076
|
+
let done = false;
|
|
1077
|
+
const complete = async (err) => {
|
|
1078
|
+
if (done)
|
|
1079
|
+
return;
|
|
1080
|
+
done = true;
|
|
1081
|
+
if (err)
|
|
1082
|
+
return reject(err);
|
|
1226
1083
|
let body = Buffer.concat(chunks);
|
|
1227
|
-
// Auto-decompress jika diminta
|
|
1228
1084
|
if (options.decompress !== false && headers) {
|
|
1229
|
-
const
|
|
1230
|
-
const encoding = Array.isArray(encodingHeader)
|
|
1231
|
-
? encodingHeader[0]?.toLowerCase()
|
|
1232
|
-
: typeof encodingHeader === 'string'
|
|
1233
|
-
? encodingHeader.toLowerCase()
|
|
1234
|
-
: undefined;
|
|
1085
|
+
const encoding = headers['content-encoding']?.toLowerCase();
|
|
1235
1086
|
if (encoding) {
|
|
1236
1087
|
try {
|
|
1237
1088
|
if (encoding.includes('gzip'))
|
|
1238
|
-
body = await
|
|
1089
|
+
body = await gunzipAsync(body);
|
|
1239
1090
|
else if (encoding.includes('br'))
|
|
1240
|
-
body = await
|
|
1091
|
+
body = await brotliDecompressAsync(body);
|
|
1241
1092
|
else if (encoding.includes('deflate'))
|
|
1242
|
-
body = await
|
|
1243
|
-
}
|
|
1244
|
-
catch (e) {
|
|
1245
|
-
console.error('Decompression failed:', e);
|
|
1093
|
+
body = await inflateAsync(body);
|
|
1246
1094
|
}
|
|
1095
|
+
catch { }
|
|
1247
1096
|
}
|
|
1248
1097
|
}
|
|
1249
1098
|
const text = body.toString('utf-8');
|
|
1250
|
-
let parsedJson
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1253
|
-
? contentType[0]
|
|
1254
|
-
: typeof contentType === 'string'
|
|
1255
|
-
? contentType
|
|
1256
|
-
: undefined;
|
|
1257
|
-
if (ct && ct.includes('application/json')) {
|
|
1099
|
+
let parsedJson;
|
|
1100
|
+
const ct = headers['content-type']?.toLowerCase();
|
|
1101
|
+
if (ct?.includes('application/json')) {
|
|
1258
1102
|
try {
|
|
1259
1103
|
parsedJson = JSON.parse(text);
|
|
1260
1104
|
}
|
|
1261
1105
|
catch { }
|
|
1262
1106
|
}
|
|
1263
|
-
|
|
1264
|
-
const ja3 = this.generateJA3();
|
|
1265
|
-
const ja3Hash = crypto.createHash('md5').update(ja3).digest('hex');
|
|
1266
|
-
const akamai = `1:${this.profile.http2.headerTableSize};2:${this.profile.http2.enablePush};4:${this.profile.http2.initialWindowSize};6:${this.profile.http2.maxHeaderListSize ?? ''}|00|0|m,a,s,p`;
|
|
1267
|
-
const fullResponse = {
|
|
1107
|
+
resolve({
|
|
1268
1108
|
statusCode,
|
|
1269
1109
|
headers,
|
|
1270
1110
|
body,
|
|
1271
1111
|
text,
|
|
1272
1112
|
json: parsedJson,
|
|
1273
1113
|
fingerprints: {
|
|
1274
|
-
ja3,
|
|
1275
|
-
ja3Hash,
|
|
1276
|
-
akamai
|
|
1114
|
+
ja3: this.generateJA3(),
|
|
1115
|
+
ja3Hash: crypto.createHash('md5').update(this.generateJA3()).digest('hex'),
|
|
1116
|
+
akamai: `1:${this.profile.http2.headerTableSize};2:${this.profile.http2.enablePush};4:${this.profile.http2.initialWindowSize};6:${this.profile.http2.maxHeaderListSize ?? ''}|00|0|m,a,s,p`
|
|
1277
1117
|
},
|
|
1278
1118
|
timing: {
|
|
1279
1119
|
socket: startTime,
|
|
@@ -1284,19 +1124,26 @@ class AdvancedTLSClient {
|
|
|
1284
1124
|
end: Date.now() - startTime,
|
|
1285
1125
|
total: Date.now() - startTime
|
|
1286
1126
|
}
|
|
1287
|
-
};
|
|
1288
|
-
|
|
1127
|
+
});
|
|
1128
|
+
};
|
|
1129
|
+
out.on('header', (status, hdrs) => {
|
|
1130
|
+
statusCode = status;
|
|
1131
|
+
headers = hdrs;
|
|
1289
1132
|
});
|
|
1290
|
-
out.
|
|
1291
|
-
|
|
1292
|
-
|
|
1133
|
+
out.on('data', (chunk) => {
|
|
1134
|
+
if (!done)
|
|
1135
|
+
chunks.push(chunk);
|
|
1136
|
+
});
|
|
1137
|
+
out.once('end', () => complete());
|
|
1138
|
+
out.once('error', complete);
|
|
1139
|
+
session.clientManager.request(path, options, startTime, config.headers, post_data, out).catch(complete);
|
|
1293
1140
|
});
|
|
1294
1141
|
}
|
|
1295
1142
|
generateJA3() {
|
|
1296
1143
|
const version = '771';
|
|
1297
|
-
const ciphers = this.profile.tls.cipherSuites.filter(c => c < 0xff00).join('-');
|
|
1298
|
-
const extensions = this.profile.tls.extensions.filter(e => typeof e === 'number' && e < 0xff00).join('-');
|
|
1299
|
-
const curves = this.profile.tls.supportedGroups.filter(g => g < 0xff00).join('-');
|
|
1144
|
+
const ciphers = this.profile.tls.cipherSuites.filter((c) => c < 0xff00).join('-');
|
|
1145
|
+
const extensions = this.profile.tls.extensions.filter((e) => typeof e === 'number' && e < 0xff00).join('-');
|
|
1146
|
+
const curves = this.profile.tls.supportedGroups.filter((g) => g < 0xff00).join('-');
|
|
1300
1147
|
const ecPoints = this.profile.tls.ecPointFormats.join('-');
|
|
1301
1148
|
return `${version},${ciphers},${extensions},${curves},${ecPoints}`;
|
|
1302
1149
|
}
|
|
@@ -1318,30 +1165,18 @@ class AdvancedTLSClient {
|
|
|
1318
1165
|
done();
|
|
1319
1166
|
continue;
|
|
1320
1167
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
this.generateMultipart(key, part, boundary).then(section => {
|
|
1324
|
-
body += section;
|
|
1325
|
-
done();
|
|
1326
|
-
});
|
|
1327
|
-
}
|
|
1328
|
-
else {
|
|
1329
|
-
const part = (value.buffer || value.file || value.content_type) ? value : { value: value };
|
|
1330
|
-
this.generateMultipart(key, part, boundary).then(section => {
|
|
1331
|
-
body += section;
|
|
1332
|
-
done();
|
|
1333
|
-
});
|
|
1334
|
-
}
|
|
1168
|
+
const part = Buffer.isBuffer(value) ? { buffer: value, content_type: 'application/octet-stream' } : (value.buffer || value.file || value.content_type) ? value : { value };
|
|
1169
|
+
this.generateMultipart(key, part, boundary).then(section => { body += section; done(); });
|
|
1335
1170
|
}
|
|
1336
1171
|
});
|
|
1337
1172
|
}
|
|
1338
1173
|
async generateMultipart(name, part, boundary) {
|
|
1339
|
-
let return_part =
|
|
1340
|
-
return_part +=
|
|
1174
|
+
let return_part = `--${boundary}\r\n`;
|
|
1175
|
+
return_part += `Content-Disposition: form-data; name="${name}"`;
|
|
1341
1176
|
const append = (data, filename) => {
|
|
1342
1177
|
if (data) {
|
|
1343
|
-
return_part +=
|
|
1344
|
-
return_part +=
|
|
1178
|
+
return_part += `; filename="${encodeURIComponent(filename)}"\r\n`;
|
|
1179
|
+
return_part += `Content-Type: ${part.content_type || 'application/octet-stream'}\r\n\r\n`;
|
|
1345
1180
|
return_part += data.toString('binary');
|
|
1346
1181
|
}
|
|
1347
1182
|
return return_part + '\r\n';
|
|
@@ -1354,19 +1189,20 @@ class AdvancedTLSClient {
|
|
|
1354
1189
|
return append(data, filename);
|
|
1355
1190
|
}
|
|
1356
1191
|
else {
|
|
1357
|
-
return_part += '\r\n\r\n';
|
|
1358
|
-
return_part
|
|
1359
|
-
return return_part + '\r\n';
|
|
1192
|
+
return_part += '\r\n\r\n' + String(part.value || '') + '\r\n';
|
|
1193
|
+
return return_part;
|
|
1360
1194
|
}
|
|
1361
1195
|
}
|
|
1362
1196
|
flatten(object, into = {}, prefix) {
|
|
1363
1197
|
for (const key in object) {
|
|
1364
|
-
const prefix_key = prefix ? prefix
|
|
1198
|
+
const prefix_key = prefix ? `${prefix}[${key}]` : key;
|
|
1365
1199
|
const prop = object[key];
|
|
1366
|
-
if (prop && typeof prop === 'object' && !(prop.buffer || prop.file || prop.content_type))
|
|
1200
|
+
if (prop && typeof prop === 'object' && !(prop.buffer || prop.file || prop.content_type)) {
|
|
1367
1201
|
this.flatten(prop, into, prefix_key);
|
|
1368
|
-
|
|
1202
|
+
}
|
|
1203
|
+
else {
|
|
1369
1204
|
into[prefix_key] = prop;
|
|
1205
|
+
}
|
|
1370
1206
|
}
|
|
1371
1207
|
return into;
|
|
1372
1208
|
}
|