curl-wrap 2.0.0 → 2.1.0
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/README.md +66 -0
- package/index.d.ts +18 -2
- package/index.js +377 -54
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,9 @@ curl.headers({
|
|
|
45
45
|
'User-Agent': 'curl-wrap/1.0',
|
|
46
46
|
'Accept': 'application/json'
|
|
47
47
|
});
|
|
48
|
+
|
|
49
|
+
// clear all headers
|
|
50
|
+
curl.clearHeaders();
|
|
48
51
|
```
|
|
49
52
|
|
|
50
53
|
### Setting Cookies
|
|
@@ -98,6 +101,9 @@ curl.impersonate('firefox');
|
|
|
98
101
|
curl.impersonate('safari');
|
|
99
102
|
curl.impersonate('safariMobile');
|
|
100
103
|
curl.impersonate('edge');
|
|
104
|
+
|
|
105
|
+
// Or create a new instance with impersonation
|
|
106
|
+
const chromeCurl = Curl.impersonate('chrome');
|
|
101
107
|
```
|
|
102
108
|
|
|
103
109
|
### Setting Request Method
|
|
@@ -165,6 +171,66 @@ const response = await curl;
|
|
|
165
171
|
console.log(response.stderr);
|
|
166
172
|
```
|
|
167
173
|
|
|
174
|
+
### Buffer Response
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
curl.asBuffer();
|
|
178
|
+
const response = await curl;
|
|
179
|
+
console.log(response.body); // Buffer object instead of string
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Export as Curl Command
|
|
183
|
+
|
|
184
|
+
```js
|
|
185
|
+
const curl = Curl.url('https://api.example.com')
|
|
186
|
+
.post()
|
|
187
|
+
.header('Content-Type', 'application/json')
|
|
188
|
+
.body({key: 'value'});
|
|
189
|
+
|
|
190
|
+
const curlCommand = await curl.exportAsCurl();
|
|
191
|
+
console.log(curlCommand);
|
|
192
|
+
// Output:
|
|
193
|
+
// curl 'https://api.example.com' \
|
|
194
|
+
// --request POST \
|
|
195
|
+
// --header 'content-type: application/json' \
|
|
196
|
+
// --data-raw '{"key":"value"}'
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Import from Curl Command
|
|
200
|
+
|
|
201
|
+
```js
|
|
202
|
+
const curlCommand = `curl 'https://api.example.com' \
|
|
203
|
+
--request POST \
|
|
204
|
+
--header 'Content-Type: application/json' \
|
|
205
|
+
--header 'Authorization: Bearer token123' \
|
|
206
|
+
--cookie 'session=abc123; user=john' \
|
|
207
|
+
--data-raw '{"name":"John","age":30}'`;
|
|
208
|
+
|
|
209
|
+
const curl = Curl.fromCurl(curlCommand);
|
|
210
|
+
const response = await curl;
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Cloning Requests
|
|
214
|
+
|
|
215
|
+
```js
|
|
216
|
+
const baseCurl = Curl.url('https://api.example.com')
|
|
217
|
+
.header('Authorization', 'Bearer token123')
|
|
218
|
+
.header('Content-Type', 'application/json');
|
|
219
|
+
|
|
220
|
+
// Clone and modify for different endpoints
|
|
221
|
+
const getUserCurl = baseCurl.clone()
|
|
222
|
+
.url('https://api.example.com/user')
|
|
223
|
+
.get();
|
|
224
|
+
const createUserCurl = baseCurl.clone()
|
|
225
|
+
.url('https://api.example.com/user')
|
|
226
|
+
.post()
|
|
227
|
+
.body({name: 'John'});
|
|
228
|
+
|
|
229
|
+
// Both requests share the same headers but have different URLs and methods
|
|
230
|
+
const userResponse = await getUserCurl;
|
|
231
|
+
const createResponse = await createUserCurl;
|
|
232
|
+
```
|
|
233
|
+
|
|
168
234
|
### Method Chaining
|
|
169
235
|
|
|
170
236
|
```js
|
package/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { CookieJar } from "tough-cookie";
|
|
|
3
3
|
interface CurlOptions {
|
|
4
4
|
method?: string; // HTTP method (e.g., GET, POST)
|
|
5
5
|
headers?: Record<string, string>; // Request headers
|
|
6
|
+
cookies?: Record<string, string>; // Request cookies
|
|
6
7
|
body?: string | object | null; // Request body
|
|
7
8
|
timeout?: number; // Timeout in milliseconds
|
|
8
9
|
[key: string]: any; // Allow additional options
|
|
@@ -10,7 +11,7 @@ interface CurlOptions {
|
|
|
10
11
|
|
|
11
12
|
interface CurlResponse {
|
|
12
13
|
url: string;
|
|
13
|
-
body: string; // Response body as a string
|
|
14
|
+
body: string | Buffer; // Response body as a string or Buffer
|
|
14
15
|
headers: Record<string, string>; // Response headers
|
|
15
16
|
statusCode: number; // HTTP status code
|
|
16
17
|
status: number; // alias for .statusCode
|
|
@@ -50,6 +51,8 @@ export class Curl {
|
|
|
50
51
|
static getGlobalCookieJar(): CookieJar;
|
|
51
52
|
|
|
52
53
|
static hasCurlImpersonate(): boolean;
|
|
54
|
+
static impersonate(browser?: BrowserType): Curl;
|
|
55
|
+
static fromCurl(curlCommand: string): Curl;
|
|
53
56
|
|
|
54
57
|
cliCommand(command: string): Curl;
|
|
55
58
|
cliOptions(options: string | string[]): Curl;
|
|
@@ -61,6 +64,7 @@ export class Curl {
|
|
|
61
64
|
|
|
62
65
|
header(headerName: string | object, headerValue?: string): Curl;
|
|
63
66
|
headers(headers: object): Curl;
|
|
67
|
+
clearHeaders(): Curl;
|
|
64
68
|
|
|
65
69
|
json(body?: object): Curl;
|
|
66
70
|
body(body: any, contentType?: string): Curl;
|
|
@@ -78,10 +82,11 @@ export class Curl {
|
|
|
78
82
|
cookieName: string | boolean | object,
|
|
79
83
|
cookieValue?: string
|
|
80
84
|
): Curl;
|
|
81
|
-
cookies(cookies: object | boolean): Curl;
|
|
85
|
+
cookies(cookies: object | boolean | string): Curl;
|
|
82
86
|
|
|
83
87
|
globalCookies(options?: boolean | object): Curl;
|
|
84
88
|
cookieJar(cookieJar: CookieJar, options?: { readOnly?: boolean }): Curl;
|
|
89
|
+
getCookieJar(): CookieJar | undefined;
|
|
85
90
|
cookieFile(fileName: string, options?: object): Curl;
|
|
86
91
|
|
|
87
92
|
timeout(timeout: number): Curl;
|
|
@@ -113,6 +118,12 @@ export class Curl {
|
|
|
113
118
|
|
|
114
119
|
verbose(isVerbose?: boolean): Curl;
|
|
115
120
|
|
|
121
|
+
asBuffer(returnAsBuffer?: boolean): Curl;
|
|
122
|
+
|
|
123
|
+
clone(): Curl;
|
|
124
|
+
|
|
125
|
+
exportAsCurl(): Promise<string>;
|
|
126
|
+
|
|
116
127
|
fetch(): Promise<CurlResponse>;
|
|
117
128
|
|
|
118
129
|
then(
|
|
@@ -130,4 +141,9 @@ export class Curl {
|
|
|
130
141
|
private options: any;
|
|
131
142
|
private _fields: Record<string, any>;
|
|
132
143
|
private _query: Record<string, any>;
|
|
144
|
+
private _cookies: Record<string, any>;
|
|
145
|
+
private _cookieJar?: CookieJar;
|
|
146
|
+
private _readCookieJar?: CookieJar;
|
|
147
|
+
private _cookieFileFn?: () => Promise<void>;
|
|
148
|
+
private _cookieFileFnRes?: () => Promise<void>;
|
|
133
149
|
}
|
package/index.js
CHANGED
|
@@ -2,12 +2,6 @@ const {spawn, spawnSync} = require('child_process');
|
|
|
2
2
|
const fs = require('fs/promises');
|
|
3
3
|
const {CookieJar} = require('tough-cookie');
|
|
4
4
|
|
|
5
|
-
function getDefaultProxyPort(proxyType) {
|
|
6
|
-
if (proxyType === 'http') return 80;
|
|
7
|
-
if (proxyType === 'https') return 443;
|
|
8
|
-
return 1080;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
5
|
/**
|
|
12
6
|
* Returns proxy url based on the proxy options.
|
|
13
7
|
*
|
|
@@ -27,23 +21,80 @@ function makeProxyUrl(proxy, options) {
|
|
|
27
21
|
}
|
|
28
22
|
|
|
29
23
|
const auth = proxy.auth || options.auth || {};
|
|
30
|
-
const uri = new URL(
|
|
24
|
+
const uri = new URL(address);
|
|
31
25
|
if (!uri.port) {
|
|
32
|
-
|
|
26
|
+
const port = proxy.port || options.port;
|
|
27
|
+
if (port) {
|
|
28
|
+
uri.port = port;
|
|
29
|
+
}
|
|
30
|
+
else if (uri.protocol.startsWith('socks')) {
|
|
31
|
+
uri.port = '1080';
|
|
32
|
+
}
|
|
33
33
|
}
|
|
34
|
-
if (!uri.username
|
|
35
|
-
|
|
34
|
+
if (!uri.username) {
|
|
35
|
+
const username = proxy.username || options.username || auth.username;
|
|
36
|
+
if (username) uri.username = username;
|
|
36
37
|
}
|
|
37
|
-
if (!uri.password
|
|
38
|
-
|
|
38
|
+
if (!uri.password) {
|
|
39
|
+
const password = proxy.password || options.password || auth.password;
|
|
40
|
+
if (password) uri.password = password;
|
|
39
41
|
}
|
|
40
42
|
return uri.toString();
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Parse curl command string into arguments array
|
|
47
|
+
*
|
|
48
|
+
* @param {string} command
|
|
49
|
+
* @return {string[]}
|
|
50
|
+
*/
|
|
51
|
+
function parseCommand(command) {
|
|
52
|
+
// Clean up the command - remove line breaks and extra spaces
|
|
53
|
+
command = command.replace(/\\\s*\n/g, ' ').replace(/\s+/g, ' ').trim();
|
|
54
|
+
|
|
55
|
+
const args = [];
|
|
56
|
+
let current = '';
|
|
57
|
+
let inQuotes = false;
|
|
58
|
+
let quoteChar = '';
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < command.length; i++) {
|
|
61
|
+
const char = command[i];
|
|
62
|
+
const nextChar = command[i + 1];
|
|
63
|
+
|
|
64
|
+
if ((char === '"' || char === "'") && !inQuotes) {
|
|
65
|
+
inQuotes = true;
|
|
66
|
+
quoteChar = char;
|
|
67
|
+
}
|
|
68
|
+
else if (char === quoteChar && inQuotes) {
|
|
69
|
+
inQuotes = false;
|
|
70
|
+
quoteChar = '';
|
|
71
|
+
}
|
|
72
|
+
else if (char === ' ' && !inQuotes) {
|
|
73
|
+
if (current.trim()) {
|
|
74
|
+
args.push(current.trim());
|
|
75
|
+
current = '';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else if (char === '\\' && nextChar === quoteChar && inQuotes) {
|
|
79
|
+
current += nextChar;
|
|
80
|
+
i++; // Skip next character
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
current += char;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (current.trim()) {
|
|
88
|
+
args.push(current.trim());
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return args;
|
|
92
|
+
}
|
|
93
|
+
|
|
43
94
|
class CurlResponse {
|
|
44
|
-
constructor() {
|
|
95
|
+
constructor({body = ''} = {}) {
|
|
45
96
|
this.url = '';
|
|
46
|
-
this.body =
|
|
97
|
+
this.body = body;
|
|
47
98
|
this.headers = {};
|
|
48
99
|
this.statusCode = 0;
|
|
49
100
|
this.ip = '';
|
|
@@ -79,6 +130,31 @@ class CurlResponse {
|
|
|
79
130
|
}
|
|
80
131
|
}
|
|
81
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Parse cookies string into object
|
|
135
|
+
*
|
|
136
|
+
* @param {string} cookieStr
|
|
137
|
+
* @return {object}
|
|
138
|
+
*/
|
|
139
|
+
function parseCookies(cookieStr) {
|
|
140
|
+
const cookies = {};
|
|
141
|
+
if (!cookieStr) return cookies;
|
|
142
|
+
const parts = cookieStr.split(';');
|
|
143
|
+
|
|
144
|
+
for (const part of parts) {
|
|
145
|
+
const equalIndex = part.indexOf('=');
|
|
146
|
+
if (equalIndex !== -1) {
|
|
147
|
+
const key = part.slice(0, equalIndex).trim();
|
|
148
|
+
const value = part.slice(equalIndex + 1).trim();
|
|
149
|
+
if (key && value) {
|
|
150
|
+
cookies[key] = value;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return cookies;
|
|
156
|
+
}
|
|
157
|
+
|
|
82
158
|
class Curl {
|
|
83
159
|
/**
|
|
84
160
|
* Creates a new Curl object.
|
|
@@ -289,10 +365,14 @@ class Curl {
|
|
|
289
365
|
return this;
|
|
290
366
|
}
|
|
291
367
|
|
|
368
|
+
/**
|
|
369
|
+
* @typedef {"chrome"|"chromeMobile"|"edge"|"safari"|"safariMobile"|"firefox"} BrowserType
|
|
370
|
+
*/
|
|
371
|
+
|
|
292
372
|
/**
|
|
293
373
|
* impersonate a browser
|
|
294
374
|
*
|
|
295
|
-
* @param {
|
|
375
|
+
* @param {BrowserType} browser browser to impersonate
|
|
296
376
|
* @return {Curl} self
|
|
297
377
|
*/
|
|
298
378
|
impersonate(browser = 'chrome') {
|
|
@@ -564,6 +644,19 @@ class Curl {
|
|
|
564
644
|
return this;
|
|
565
645
|
}
|
|
566
646
|
|
|
647
|
+
/**
|
|
648
|
+
* @static
|
|
649
|
+
* impersonate a browser
|
|
650
|
+
*
|
|
651
|
+
* @param {BrowserType} browser browser to impersonate
|
|
652
|
+
* @return {Curl} self
|
|
653
|
+
*/
|
|
654
|
+
static impersonate(browser = 'chrome') {
|
|
655
|
+
const curl = new this();
|
|
656
|
+
curl.impersonate(browser);
|
|
657
|
+
return curl;
|
|
658
|
+
}
|
|
659
|
+
|
|
567
660
|
/**
|
|
568
661
|
* Set or unset the followRedirect option for the connection.
|
|
569
662
|
*
|
|
@@ -618,6 +711,16 @@ class Curl {
|
|
|
618
711
|
return this;
|
|
619
712
|
}
|
|
620
713
|
|
|
714
|
+
/**
|
|
715
|
+
* Clear all headers
|
|
716
|
+
*
|
|
717
|
+
* @return {Curl} self
|
|
718
|
+
*/
|
|
719
|
+
clearHeaders() {
|
|
720
|
+
this.options.headers = {};
|
|
721
|
+
return this;
|
|
722
|
+
}
|
|
723
|
+
|
|
621
724
|
/**
|
|
622
725
|
* Set the content type as json
|
|
623
726
|
* Optionally also set the body of the request.
|
|
@@ -737,18 +840,15 @@ class Curl {
|
|
|
737
840
|
*/
|
|
738
841
|
cookie(cookieName, cookieValue) {
|
|
739
842
|
if (cookieValue === undefined) {
|
|
740
|
-
this.
|
|
843
|
+
this.cookies(cookieName);
|
|
741
844
|
}
|
|
742
845
|
else if (typeof cookieName === 'string') {
|
|
743
|
-
if (
|
|
744
|
-
delete this._cookies[cookieName];
|
|
745
|
-
}
|
|
746
|
-
else {
|
|
846
|
+
if (typeof cookieValue === 'string') {
|
|
747
847
|
this._cookies[cookieName] = cookieValue;
|
|
748
848
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
849
|
+
else if (cookieValue === null || cookieValue === false) {
|
|
850
|
+
delete this._cookies[cookieName];
|
|
851
|
+
}
|
|
752
852
|
}
|
|
753
853
|
|
|
754
854
|
return this;
|
|
@@ -758,7 +858,7 @@ class Curl {
|
|
|
758
858
|
* Sets multiple cookies.
|
|
759
859
|
* Can be used to enable global cookies, if cookies is set to true.
|
|
760
860
|
*
|
|
761
|
-
* @param {object|boolean} cookies object representing the cookies
|
|
861
|
+
* @param {object|boolean|string} cookies object representing the cookies
|
|
762
862
|
* and their values as key:value pairs.
|
|
763
863
|
* @return {Curl} self
|
|
764
864
|
*/
|
|
@@ -769,6 +869,10 @@ class Curl {
|
|
|
769
869
|
else if (cookies && typeof cookies === 'object') {
|
|
770
870
|
Object.assign(this._cookies, cookies);
|
|
771
871
|
}
|
|
872
|
+
else if (typeof cookies === 'string') {
|
|
873
|
+
const cookies = parseCookies(cookieName);
|
|
874
|
+
Object.assign(this._cookies, cookies);
|
|
875
|
+
}
|
|
772
876
|
|
|
773
877
|
return this;
|
|
774
878
|
}
|
|
@@ -781,8 +885,8 @@ class Curl {
|
|
|
781
885
|
*/
|
|
782
886
|
globalCookies(options = true) {
|
|
783
887
|
if (options === false || options === null) {
|
|
784
|
-
delete this.
|
|
785
|
-
delete this.
|
|
888
|
+
delete this._cookieJar;
|
|
889
|
+
delete this._readCookieJar;
|
|
786
890
|
return this;
|
|
787
891
|
}
|
|
788
892
|
|
|
@@ -802,15 +906,24 @@ class Curl {
|
|
|
802
906
|
delete this._cookieFileFnRes;
|
|
803
907
|
|
|
804
908
|
if (options.readOnly) {
|
|
805
|
-
this.
|
|
909
|
+
this._readCookieJar = cookieJar;
|
|
806
910
|
}
|
|
807
911
|
else {
|
|
808
|
-
this.
|
|
912
|
+
this._cookieJar = cookieJar;
|
|
809
913
|
}
|
|
810
914
|
|
|
811
915
|
return this;
|
|
812
916
|
}
|
|
813
917
|
|
|
918
|
+
/**
|
|
919
|
+
* Get the current cookie jar
|
|
920
|
+
*
|
|
921
|
+
* @return {CookieJar|undefined} the current cookie jar
|
|
922
|
+
*/
|
|
923
|
+
getCookieJar() {
|
|
924
|
+
return this._cookieJar || this._readCookieJar;
|
|
925
|
+
}
|
|
926
|
+
|
|
814
927
|
/**
|
|
815
928
|
* Set the value of cookie jar based on a file (cookie store).
|
|
816
929
|
*
|
|
@@ -820,10 +933,10 @@ class Curl {
|
|
|
820
933
|
cookieFile(fileName, options = {}) {
|
|
821
934
|
const setCookieJar = (cookieJar) => {
|
|
822
935
|
if (options.readOnly) {
|
|
823
|
-
this.
|
|
936
|
+
this._readCookieJar = cookieJar;
|
|
824
937
|
}
|
|
825
938
|
else {
|
|
826
|
-
this.
|
|
939
|
+
this._cookieJar = cookieJar;
|
|
827
940
|
}
|
|
828
941
|
};
|
|
829
942
|
|
|
@@ -841,7 +954,7 @@ class Curl {
|
|
|
841
954
|
|
|
842
955
|
if (!options.readOnly) {
|
|
843
956
|
this._cookieFileFnRes = async () => {
|
|
844
|
-
const cookieJar = this.
|
|
957
|
+
const cookieJar = this._cookieJar;
|
|
845
958
|
if (!cookieJar) return;
|
|
846
959
|
try {
|
|
847
960
|
const contents = JSON.stringify(cookieJar.toJSON());
|
|
@@ -1086,6 +1199,205 @@ class Curl {
|
|
|
1086
1199
|
return this;
|
|
1087
1200
|
}
|
|
1088
1201
|
|
|
1202
|
+
/**
|
|
1203
|
+
* Set if the body is to be returned as a buffer
|
|
1204
|
+
*
|
|
1205
|
+
* @param {boolean} [returnAsBuffer=true]
|
|
1206
|
+
* @return {Curl} self
|
|
1207
|
+
*/
|
|
1208
|
+
asBuffer(returnAsBuffer = true) {
|
|
1209
|
+
this.options.asBuffer = returnAsBuffer;
|
|
1210
|
+
return this;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* Create a clone of this Curl instance with the same options
|
|
1215
|
+
*
|
|
1216
|
+
* @return {Curl} new Curl instance with cloned options
|
|
1217
|
+
*/
|
|
1218
|
+
clone() {
|
|
1219
|
+
const cloned = new this.constructor();
|
|
1220
|
+
|
|
1221
|
+
// Deep clone the options object (only serializable properties)
|
|
1222
|
+
cloned.options = JSON.parse(JSON.stringify(this.options));
|
|
1223
|
+
|
|
1224
|
+
// Copy non-serializable properties
|
|
1225
|
+
if (this._cookieJar) {
|
|
1226
|
+
cloned._cookieJar = this._cookieJar;
|
|
1227
|
+
}
|
|
1228
|
+
if (this._readCookieJar) {
|
|
1229
|
+
cloned._readCookieJar = this._readCookieJar;
|
|
1230
|
+
}
|
|
1231
|
+
if (this._cookieFileFn) {
|
|
1232
|
+
cloned._cookieFileFn = this._cookieFileFn;
|
|
1233
|
+
}
|
|
1234
|
+
if (this._cookieFileFnRes) {
|
|
1235
|
+
cloned._cookieFileFnRes = this._cookieFileFnRes;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
return cloned;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* Export the current request as a curl command string
|
|
1243
|
+
*
|
|
1244
|
+
* @return {Promise<string>} curl command as a string
|
|
1245
|
+
*/
|
|
1246
|
+
async exportAsCurl() {
|
|
1247
|
+
const cmd = this.options.cliCommand || 'curl';
|
|
1248
|
+
const args = await this.getCurlArgs();
|
|
1249
|
+
// Filter out internal options that shouldn't appear in exported commands
|
|
1250
|
+
const skipOptions = ['--silent', '--no-keepalive', '--keepalive', '--write-out', '--insecure', '--location'];
|
|
1251
|
+
const escape = arg => `'${arg.replace(/'/g, "'\"'\"'")}'`;
|
|
1252
|
+
|
|
1253
|
+
const url = args[0];
|
|
1254
|
+
const lines = [`${cmd} ${escape(url)}`];
|
|
1255
|
+
for (let i = 1; i < args.length; i++) {
|
|
1256
|
+
const arg = String(args[i]);
|
|
1257
|
+
const nextArg = String(args[i + 1] ?? '');
|
|
1258
|
+
const isNextArgValue = !nextArg.startsWith('--');
|
|
1259
|
+
if (isNextArgValue) {
|
|
1260
|
+
i++;
|
|
1261
|
+
}
|
|
1262
|
+
// Skip internal options and their values
|
|
1263
|
+
if (skipOptions.includes(arg)) continue;
|
|
1264
|
+
if (isNextArgValue) {
|
|
1265
|
+
const escapedNextArg = /[\s"'`&:;,><|(){}~^%#@!/=+\$\?\*\[\]]/.test(nextArg) ? escape(nextArg) : nextArg;
|
|
1266
|
+
lines.push(`${arg} ${escapedNextArg}`);
|
|
1267
|
+
}
|
|
1268
|
+
else {
|
|
1269
|
+
lines.push(arg);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
return lines.join(' \\\n ');
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
/**
|
|
1277
|
+
* @static
|
|
1278
|
+
* Create a new Curl instance from a curl command string
|
|
1279
|
+
*
|
|
1280
|
+
* @param {string} curlCommand curl command string to parse
|
|
1281
|
+
* @return {Curl} new Curl instance
|
|
1282
|
+
*/
|
|
1283
|
+
static fromCurl(curlCommand) {
|
|
1284
|
+
const curl = new this();
|
|
1285
|
+
curl.options.headers = {};
|
|
1286
|
+
|
|
1287
|
+
// Parse the command into arguments
|
|
1288
|
+
const args = parseCommand(curlCommand);
|
|
1289
|
+
|
|
1290
|
+
let method;
|
|
1291
|
+
let body;
|
|
1292
|
+
|
|
1293
|
+
// Process arguments
|
|
1294
|
+
for (let i = 0; i < args.length; i++) {
|
|
1295
|
+
const arg = args[i];
|
|
1296
|
+
const nextArg = args[i + 1];
|
|
1297
|
+
const isNextArgValue = nextArg && !nextArg.startsWith('-');
|
|
1298
|
+
|
|
1299
|
+
if (i === 0 && !arg.startsWith('curl')) {
|
|
1300
|
+
// First argument should be the URL if no 'curl' command prefix
|
|
1301
|
+
curl.url(arg);
|
|
1302
|
+
}
|
|
1303
|
+
else if (i === 1 && args[0].startsWith('curl')) {
|
|
1304
|
+
// Second argument is URL when first is 'curl'
|
|
1305
|
+
curl.url(arg);
|
|
1306
|
+
}
|
|
1307
|
+
else if (arg === '--request' || arg === '-X') {
|
|
1308
|
+
if (!isNextArgValue) continue;
|
|
1309
|
+
method = nextArg;
|
|
1310
|
+
i++;
|
|
1311
|
+
}
|
|
1312
|
+
else if (arg === '--header' || arg === '-H') {
|
|
1313
|
+
if (!isNextArgValue) continue;
|
|
1314
|
+
const colonIndex = nextArg.indexOf(':');
|
|
1315
|
+
if (colonIndex !== -1) {
|
|
1316
|
+
const headerName = nextArg.slice(0, colonIndex).trim();
|
|
1317
|
+
const headerValue = nextArg.slice(colonIndex + 1).trim();
|
|
1318
|
+
curl.header(headerName, headerValue);
|
|
1319
|
+
}
|
|
1320
|
+
i++;
|
|
1321
|
+
}
|
|
1322
|
+
else if (arg === '--data' || arg === '--data-raw' || arg === '-d') {
|
|
1323
|
+
if (!isNextArgValue) continue;
|
|
1324
|
+
body = nextArg;
|
|
1325
|
+
i++;
|
|
1326
|
+
}
|
|
1327
|
+
else if (arg === '--cookie' || arg === '-b') {
|
|
1328
|
+
if (!isNextArgValue) continue;
|
|
1329
|
+
curl.header('cookie', nextArg);
|
|
1330
|
+
i++;
|
|
1331
|
+
}
|
|
1332
|
+
else if (arg === '--user-agent' || arg === '-A') {
|
|
1333
|
+
if (!isNextArgValue) continue;
|
|
1334
|
+
curl.userAgent(nextArg);
|
|
1335
|
+
i++;
|
|
1336
|
+
}
|
|
1337
|
+
else if (arg === '--referer' || arg === '-e') {
|
|
1338
|
+
if (!isNextArgValue) continue;
|
|
1339
|
+
curl.referer(nextArg);
|
|
1340
|
+
i++;
|
|
1341
|
+
}
|
|
1342
|
+
else if (arg === '--max-time' || arg === '-m') {
|
|
1343
|
+
if (!isNextArgValue) continue;
|
|
1344
|
+
const timeout = Number(nextArg);
|
|
1345
|
+
if (timeout > 0) curl.timeout(timeout);
|
|
1346
|
+
i++;
|
|
1347
|
+
}
|
|
1348
|
+
else if (arg === '--proxy' || arg === '-x') {
|
|
1349
|
+
if (!isNextArgValue) continue;
|
|
1350
|
+
curl.proxy(nextArg);
|
|
1351
|
+
i++;
|
|
1352
|
+
}
|
|
1353
|
+
else if (arg === '--compressed') {
|
|
1354
|
+
curl.compress(true);
|
|
1355
|
+
}
|
|
1356
|
+
else if (arg === '--location' || arg === '-L') {
|
|
1357
|
+
curl.followRedirect(true);
|
|
1358
|
+
}
|
|
1359
|
+
else if (arg === '--max-redirs') {
|
|
1360
|
+
if (!isNextArgValue) continue;
|
|
1361
|
+
const maxRedir = Math.floor(Number(nextArg));
|
|
1362
|
+
if (maxRedir > 0) curl.maxRedirects(maxRedir);
|
|
1363
|
+
i++;
|
|
1364
|
+
}
|
|
1365
|
+
else if (arg === '--verbose' || arg === '-v') {
|
|
1366
|
+
curl.verbose(true);
|
|
1367
|
+
}
|
|
1368
|
+
else if (arg.startsWith('--') || arg.startsWith('-')) {
|
|
1369
|
+
// Ignore internal options that shouldn't be imported
|
|
1370
|
+
const ignoreOptions = ['--silent', '--no-keepalive', '--keepalive', '--write-out', '--insecure'];
|
|
1371
|
+
if (ignoreOptions.includes(arg)) {
|
|
1372
|
+
// If the ignored option has a value, ignore it too
|
|
1373
|
+
if (isNextArgValue) {
|
|
1374
|
+
i++;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
else {
|
|
1378
|
+
// Handle unknown curl options by adding them as cliOptions
|
|
1379
|
+
if (isNextArgValue) {
|
|
1380
|
+
curl.cliOptions([arg, nextArg]);
|
|
1381
|
+
i++; // Skip next argument
|
|
1382
|
+
}
|
|
1383
|
+
else {
|
|
1384
|
+
curl.cliOptions(arg);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
if (body) {
|
|
1391
|
+
curl.body(body);
|
|
1392
|
+
method = method || 'POST';
|
|
1393
|
+
}
|
|
1394
|
+
if (method) {
|
|
1395
|
+
curl.method(method);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
return curl;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1089
1401
|
/**
|
|
1090
1402
|
* Add the options 'fields' to the options body, form or qs
|
|
1091
1403
|
* on the basis of the request method.
|
|
@@ -1165,7 +1477,7 @@ class Curl {
|
|
|
1165
1477
|
await this._cookieFileFn();
|
|
1166
1478
|
}
|
|
1167
1479
|
|
|
1168
|
-
const cookieJar =
|
|
1480
|
+
const cookieJar = this._readCookieJar || this._cookieJar;
|
|
1169
1481
|
if (cookieJar) {
|
|
1170
1482
|
const jarCookies = await cookieJar.getCookies(options.url);
|
|
1171
1483
|
if (jarCookies) {
|
|
@@ -1201,19 +1513,12 @@ class Curl {
|
|
|
1201
1513
|
'%{stderr}===<json>==={"json":%{json},"headers":%{header_json}}===</json>===',
|
|
1202
1514
|
];
|
|
1203
1515
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
const cookieHeader = await this.getCookieHeader();
|
|
1209
|
-
if (cookieHeader) {
|
|
1210
|
-
this.header('cookie', cookieHeader);
|
|
1516
|
+
const cliOptions = options.cliOptions;
|
|
1517
|
+
if (cliOptions) {
|
|
1518
|
+
args.push(...cliOptions);
|
|
1211
1519
|
}
|
|
1212
1520
|
|
|
1213
|
-
|
|
1214
|
-
args.push('--header', `${key}: ${value}`);
|
|
1215
|
-
}
|
|
1216
|
-
if (options.compress) {
|
|
1521
|
+
if (options.compress && !cliOptions?.includes('--compressed')) {
|
|
1217
1522
|
args.push('--compressed');
|
|
1218
1523
|
}
|
|
1219
1524
|
if (options.proxy && options.useProxy !== false) {
|
|
@@ -1235,9 +1540,17 @@ class Curl {
|
|
|
1235
1540
|
args.push('--verbose');
|
|
1236
1541
|
}
|
|
1237
1542
|
|
|
1238
|
-
const
|
|
1239
|
-
if (
|
|
1240
|
-
|
|
1543
|
+
const cookieHeader = await this.getCookieHeader();
|
|
1544
|
+
if (cookieHeader) {
|
|
1545
|
+
this.header('cookie', cookieHeader);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
for (const [key, value] of Object.entries(options.headers)) {
|
|
1549
|
+
args.push('--header', `${key}: ${value}`);
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
if (body) {
|
|
1553
|
+
args.push('--data-raw', body);
|
|
1241
1554
|
}
|
|
1242
1555
|
|
|
1243
1556
|
return args;
|
|
@@ -1245,18 +1558,28 @@ class Curl {
|
|
|
1245
1558
|
|
|
1246
1559
|
async fetch() {
|
|
1247
1560
|
const startTime = Date.now();
|
|
1248
|
-
const
|
|
1561
|
+
const options = this.options;
|
|
1562
|
+
const cmd = options.cliCommand || 'curl';
|
|
1249
1563
|
const args = await this.getCurlArgs();
|
|
1250
1564
|
const curl = spawn(cmd, args);
|
|
1251
|
-
const cookieJar = this.
|
|
1565
|
+
const cookieJar = this._cookieJar;
|
|
1252
1566
|
|
|
1253
|
-
let stdout = '';
|
|
1567
|
+
let stdout = options.asBuffer ? Buffer.from([]) : '';
|
|
1254
1568
|
let stderr = '';
|
|
1255
|
-
const response = new CurlResponse(
|
|
1569
|
+
const response = new CurlResponse({
|
|
1570
|
+
body: options.asBuffer ? Buffer.from([]) : '',
|
|
1571
|
+
});
|
|
1256
1572
|
return new Promise((resolve, reject) => {
|
|
1257
|
-
|
|
1258
|
-
stdout
|
|
1259
|
-
|
|
1573
|
+
if (options.asBuffer) {
|
|
1574
|
+
curl.stdout.on('data', (data) => {
|
|
1575
|
+
stdout = Buffer.concat([stdout, data]);
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
else {
|
|
1579
|
+
curl.stdout.on('data', (data) => {
|
|
1580
|
+
stdout += data;
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1260
1583
|
|
|
1261
1584
|
curl.stderr.on('data', (data) => {
|
|
1262
1585
|
stderr += data;
|
|
@@ -1270,7 +1593,7 @@ class Curl {
|
|
|
1270
1593
|
curl.on('close', async (code) => {
|
|
1271
1594
|
response.timeTaken = Date.now() - startTime;
|
|
1272
1595
|
response.exitCode = code;
|
|
1273
|
-
response.url =
|
|
1596
|
+
response.url = options.url;
|
|
1274
1597
|
response.body = stdout;
|
|
1275
1598
|
stderr = stderr.replace(/===<json>===(.*)===<\/json>===/s, (match, p1) => {
|
|
1276
1599
|
try {
|