curlie 1.0.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 +202 -0
- package/bin/index.js +70 -0
- package/bin/localfile.txt +1 -0
- package/eslint.config.js +7 -0
- package/lib/dnsHandler.js +141 -0
- package/lib/fileHandler.js +77 -0
- package/lib/flags.js +141 -0
- package/lib/ftpHandler.js +123 -0
- package/lib/httpHandler.js +316 -0
- package/lib/monitor.js +61 -0
- package/lib/utils.js +588 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# nodeCurl
|
|
2
|
+
|
|
3
|
+
A curl-like CLI tool for Node.js - transfer data from HTTP, HTTPS, FTP servers and local files.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g curlie
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
nodecurl [options] <url>
|
|
15
|
+
nodecurl ftp://<host>/<path>
|
|
16
|
+
nodecurl file://<path>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Options
|
|
20
|
+
|
|
21
|
+
### HTTP Options
|
|
22
|
+
|
|
23
|
+
| Flag | Description |
|
|
24
|
+
|------|-------------|
|
|
25
|
+
| `-X, --request <method>` | HTTP method (GET, POST, PUT, DELETE, etc) |
|
|
26
|
+
| `-d, --data <data>` | HTTP POST data |
|
|
27
|
+
| `--json <json>` | JSON data (sets Content-Type: application/json) |
|
|
28
|
+
| `-H, --header <header>` | Add custom header (Header: Value) |
|
|
29
|
+
| `-A, --user-agent <name>` | User-Agent string |
|
|
30
|
+
| `-e, --referer <URL>` | Referer URL |
|
|
31
|
+
| `-b, --cookie <data>` | Cookie string or @filename |
|
|
32
|
+
| `-c, --cookie-jar <file>` | Save cookies to file |
|
|
33
|
+
| `-i, --include` | Include response headers in output |
|
|
34
|
+
| `-v, --verbose` | Show request/response details |
|
|
35
|
+
| `-k, --insecure` | Allow insecure SSL connections |
|
|
36
|
+
| `-L, --location` | Follow redirects |
|
|
37
|
+
| `--max-redirs <num>` | Max redirects to follow |
|
|
38
|
+
| `--retry <num>` | Retry on transient errors |
|
|
39
|
+
| `--limit-rate <speed>` | Limit transfer speed |
|
|
40
|
+
|
|
41
|
+
### Output Options
|
|
42
|
+
|
|
43
|
+
| Flag | Description |
|
|
44
|
+
|------|-------------|
|
|
45
|
+
| `-o, --output <file>` | Write to file |
|
|
46
|
+
| `-O, --remote-name` | Write using remote filename |
|
|
47
|
+
| `--output-dir <dir>` | Output directory |
|
|
48
|
+
| `-I, --head` | Fetch headers only |
|
|
49
|
+
| `-R, --remote-time` | Preserve remote timestamp |
|
|
50
|
+
| `--no-clobber` | Don't overwrite files |
|
|
51
|
+
| `--create-dirs` | Create directories |
|
|
52
|
+
|
|
53
|
+
### Connection Options
|
|
54
|
+
|
|
55
|
+
| Flag | Description |
|
|
56
|
+
|------|-------------|
|
|
57
|
+
| `--max-time <seconds>` | Total timeout |
|
|
58
|
+
| `--connect-timeout <seconds>` | Connection timeout |
|
|
59
|
+
| `--limit-rate <speed>` | Speed limit (e.g. 1M, 100K) |
|
|
60
|
+
| `-Z, --parallel <urls>` | Parallel downloads |
|
|
61
|
+
| `--parallel-max <num>` | Max parallel connections |
|
|
62
|
+
|
|
63
|
+
### DNS Options
|
|
64
|
+
|
|
65
|
+
| Flag | Description |
|
|
66
|
+
|------|-------------|
|
|
67
|
+
| `--resolve <host:port:addr>` | Custom address for host:port |
|
|
68
|
+
| `--dns-servers <addrs>` | DNS servers (comma-separated) |
|
|
69
|
+
| `--doh-url <url>` | DNS-over-HTTPS URL |
|
|
70
|
+
| `-4, --ipv4` | IPv4 only |
|
|
71
|
+
| `-6, --ipv6` | IPv6 only |
|
|
72
|
+
|
|
73
|
+
### FTP Options
|
|
74
|
+
|
|
75
|
+
| Flag | Description |
|
|
76
|
+
|------|-------------|
|
|
77
|
+
| `-u, --user <user:password>` | Authentication |
|
|
78
|
+
| `-l, --list-only` | List directory contents |
|
|
79
|
+
| `-I, --head` | Get file metadata only |
|
|
80
|
+
| `-o, --output <file>` | Download to local file |
|
|
81
|
+
| `-T, --upload <file>` | Upload local file |
|
|
82
|
+
| `-a, --append` | Append to remote file |
|
|
83
|
+
| `-Q, --quote <command>` | Command before transfer |
|
|
84
|
+
| `-v, --verbose` | Show protocol details |
|
|
85
|
+
|
|
86
|
+
### Authentication
|
|
87
|
+
|
|
88
|
+
| Flag | Description |
|
|
89
|
+
|------|-------------|
|
|
90
|
+
| `-u, --user <user:password>` | Server user and password |
|
|
91
|
+
| `-k, --insecure` | Allow insecure server connections |
|
|
92
|
+
|
|
93
|
+
## Examples
|
|
94
|
+
|
|
95
|
+
### Basic Requests
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Simple GET request
|
|
99
|
+
nodecurl https://example.com
|
|
100
|
+
|
|
101
|
+
# With SSL skip (insecure)
|
|
102
|
+
nodecurl -k https://example.com
|
|
103
|
+
|
|
104
|
+
# POST with data
|
|
105
|
+
nodecurl -X POST -d "name=test&value=123" https://api.example.com
|
|
106
|
+
|
|
107
|
+
# POST with JSON
|
|
108
|
+
nodecurl --json '{"name":"test"}' https://api.example.com
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### File Downloads
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Save to specific file
|
|
115
|
+
nodecurl -o page.html https://example.com/page
|
|
116
|
+
|
|
117
|
+
# Save with remote filename
|
|
118
|
+
nodecurl -O https://example.com/file.txt
|
|
119
|
+
|
|
120
|
+
# Save to directory
|
|
121
|
+
nodecurl --output-dir ./downloads https://example.com/file
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Headers & Cookies
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Custom headers
|
|
128
|
+
nodecurl -H "Authorization: Bearer token" https://api.example.com
|
|
129
|
+
|
|
130
|
+
# Custom User-Agent
|
|
131
|
+
nodecurl -A "MyBot/1.0" https://example.com
|
|
132
|
+
|
|
133
|
+
# Save cookies
|
|
134
|
+
nodecurl -c cookies.txt https://example.com
|
|
135
|
+
|
|
136
|
+
# Send cookies
|
|
137
|
+
nodecurl -b cookies.txt https://example.com
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Redirects
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Follow redirects (default)
|
|
144
|
+
nodecurl -L https://example.com/redirect
|
|
145
|
+
|
|
146
|
+
# Limit redirects
|
|
147
|
+
nodecurl -L --max-redirs 3 https://example.com/redirect
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### FTP
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# List directory
|
|
154
|
+
nodecurl ftp://ftp.example.com/
|
|
155
|
+
|
|
156
|
+
# Download file
|
|
157
|
+
nodecurl -o local.txt ftp://ftp.example.com/file.txt
|
|
158
|
+
|
|
159
|
+
# Upload file
|
|
160
|
+
nodecurl -T local.txt ftp://ftp.example.com/upload/
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Advanced
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# Custom DNS resolve
|
|
167
|
+
nodecurl --resolve example.com:443:127.0.0.1 https://example.com
|
|
168
|
+
|
|
169
|
+
# DNS-over-HTTPS
|
|
170
|
+
nodecurl --doh-url https://dns.google/resolve https://example.com
|
|
171
|
+
|
|
172
|
+
# Speed limit
|
|
173
|
+
nodecurl --limit-rate 1M https://example.com/large-file
|
|
174
|
+
|
|
175
|
+
# Parallel downloads
|
|
176
|
+
nodecurl -Z url1 url2 url3
|
|
177
|
+
|
|
178
|
+
# Verbose output
|
|
179
|
+
nodecurl -v https://example.com
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Help
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
nodecurl -h # Show all options
|
|
186
|
+
nodecurl http -h # HTTP options only
|
|
187
|
+
nodecurl ftp -h # FTP options only
|
|
188
|
+
nodecurl dns -h # DNS options only
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Exit Codes
|
|
192
|
+
|
|
193
|
+
- `0` - Success
|
|
194
|
+
- `1` - General error
|
|
195
|
+
- `2` - Parse error
|
|
196
|
+
- `3` - Network error
|
|
197
|
+
- `4` - SSL error
|
|
198
|
+
- `28` - Operation timeout
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
MIT
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseFlags } from "../lib/flags.js";
|
|
3
|
+
import { ftpList } from "../lib/ftpHandler.js";
|
|
4
|
+
import { expandLocalVars, isFileURL, isFtp, showHelp } from "../lib/utils.js";
|
|
5
|
+
import { readLocalFile, createFile, readHeaders } from "../lib/fileHandler.js";
|
|
6
|
+
import { parallelRequests, request } from "../lib/httpHandler.js";
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const flags = parseFlags(args);
|
|
10
|
+
const nonFlagArgs = args.filter((arg) => !arg.startsWith("-"));
|
|
11
|
+
const module = nonFlagArgs[0];
|
|
12
|
+
|
|
13
|
+
let url = args.find(
|
|
14
|
+
(arg) =>
|
|
15
|
+
arg.startsWith("http://") ||
|
|
16
|
+
arg.startsWith("https://") ||
|
|
17
|
+
arg.startsWith("file://") ||
|
|
18
|
+
arg.startsWith("ftp://"),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
let urls = args.filter((item) => !item.startsWith("--")) || [];
|
|
22
|
+
|
|
23
|
+
url = expandLocalVars(url);
|
|
24
|
+
|
|
25
|
+
if (flags.user === undefined) flags.user = "anonymous:anonymous";
|
|
26
|
+
|
|
27
|
+
const main = async () => {
|
|
28
|
+
if (!url && !flags.help && !flags.parallel) {
|
|
29
|
+
console.log("Error: Usage: ./index.js [options] <url>");
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (flags.help) {
|
|
35
|
+
showHelp(module);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (isFtp(url)) {
|
|
40
|
+
await ftpList(url, flags);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (isFileURL(url)) {
|
|
45
|
+
if (flags.upload) {
|
|
46
|
+
createFile(url, flags);
|
|
47
|
+
} else if (flags.head) {
|
|
48
|
+
await readHeaders(url, flags);
|
|
49
|
+
} else {
|
|
50
|
+
readLocalFile(url, flags);
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (flags.parallel) {
|
|
56
|
+
await parallelRequests(urls, flags);
|
|
57
|
+
} else {
|
|
58
|
+
let method = flags.method || (flags.upload ? "PUT" : "GET");
|
|
59
|
+
await request(url, method, flags);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
(async () => {
|
|
64
|
+
try {
|
|
65
|
+
await main();
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error("Error:", error.message);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hello ftp
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import { defineConfig } from "eslint/config";
|
|
4
|
+
|
|
5
|
+
export default defineConfig([
|
|
6
|
+
{ files: ["**/*.{js,mjs,cjs}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: globals.browser } },
|
|
7
|
+
]);
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import dns from "dns";
|
|
2
|
+
import https from "https";
|
|
3
|
+
import { URL } from "url";
|
|
4
|
+
|
|
5
|
+
export function createDnsLookup(flags = {}) {
|
|
6
|
+
const resolveMap = parseResolveFlags(flags.resolve);
|
|
7
|
+
|
|
8
|
+
const customServers = flags.dnsServers || null;
|
|
9
|
+
const useIPv4 = !!flags.ipv4;
|
|
10
|
+
const useIPv6 = !!flags.ipv6;
|
|
11
|
+
|
|
12
|
+
if (customServers) {
|
|
13
|
+
try {
|
|
14
|
+
const servers = customServers.flatMap((s) => s.split(","));
|
|
15
|
+
dns.setServers(servers);
|
|
16
|
+
} catch (err) {
|
|
17
|
+
console.warn("[dns] failed to set DNS servers:", err.message);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (flags.dohUrl) {
|
|
22
|
+
return function lookup(hostname, opts = {}, cb) {
|
|
23
|
+
dohLookup(flags.dohUrl, hostname, opts, useIPv4, useIPv6, cb);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (resolveMap.size > 0) {
|
|
28
|
+
return function lookup(hostname, opts = {}, cb) {
|
|
29
|
+
const port = opts.port;
|
|
30
|
+
const key = port ? `${hostname}:${port}` : null;
|
|
31
|
+
|
|
32
|
+
if (key && resolveMap.has(key)) {
|
|
33
|
+
const ip = resolveMap.get(key);
|
|
34
|
+
const family = ip.includes(":") ? 6 : 4;
|
|
35
|
+
|
|
36
|
+
if (opts.all) {
|
|
37
|
+
return cb(null, [{ address: ip, family }]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return cb(null, ip, family);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
defaultLookup(hostname, opts, useIPv4, useIPv6, cb);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return function lookup(hostname, opts = {}, cb) {
|
|
48
|
+
defaultLookup(hostname, opts, useIPv4, useIPv6, cb);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function parseResolveFlags(list) {
|
|
53
|
+
const map = new Map();
|
|
54
|
+
if (!list) return map;
|
|
55
|
+
|
|
56
|
+
if (typeof list === "string") list = [list];
|
|
57
|
+
|
|
58
|
+
for (const entry of list) {
|
|
59
|
+
const [host, port, addr] = entry.split(":");
|
|
60
|
+
if (host && port && addr) {
|
|
61
|
+
map.set(`${host}:${port}`, addr);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return map;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
function defaultLookup(hostname, opts, force4, force6, cb) {
|
|
69
|
+
const family = force4 ? 4 : force6 ? 6 : undefined;
|
|
70
|
+
|
|
71
|
+
dns.lookup(hostname, { ...opts, family }, (err, res, fam) => {
|
|
72
|
+
if (err) {
|
|
73
|
+
console.error("[dns] lookup error:", err.message);
|
|
74
|
+
return cb(err);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (Array.isArray(res)) {
|
|
78
|
+
if (opts.all) {
|
|
79
|
+
return cb(null, res);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const first = res[0];
|
|
83
|
+
return cb(null, first.address, first.family);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
cb(null, res, fam);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function dohLookup(dohUrl, hostname, opts, force4, force6, cb) {
|
|
91
|
+
try {
|
|
92
|
+
const wantIPv6 = force6 && !force4;
|
|
93
|
+
const rrType = wantIPv6 ? 28 : 1;
|
|
94
|
+
|
|
95
|
+
const url = new URL(dohUrl);
|
|
96
|
+
url.searchParams.set("name", hostname);
|
|
97
|
+
url.searchParams.set("type", wantIPv6 ? "AAAA" : "A");
|
|
98
|
+
|
|
99
|
+
https
|
|
100
|
+
.get(url, { headers: { accept: "application/dns-json" } }, (res) => {
|
|
101
|
+
let body = "";
|
|
102
|
+
|
|
103
|
+
res.on("data", (chunk) => (body += chunk));
|
|
104
|
+
res.on("end", () => {
|
|
105
|
+
try {
|
|
106
|
+
const json = JSON.parse(body);
|
|
107
|
+
|
|
108
|
+
const answer = json.Answer?.find(
|
|
109
|
+
(a) =>
|
|
110
|
+
a.type === rrType &&
|
|
111
|
+
typeof a.data === "string" &&
|
|
112
|
+
isValidIP(a.data),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (!answer) {
|
|
116
|
+
return cb(new Error("[dns:doh] no usable DNS answer"));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const address = answer.data;
|
|
120
|
+
const family = wantIPv6 ? 6 : 4;
|
|
121
|
+
|
|
122
|
+
if (opts.all) {
|
|
123
|
+
return cb(null, [{ address, family }]);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
cb(null, address, family);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
cb(err);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
})
|
|
132
|
+
.on("error", cb);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
cb(err);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
function isValidIP(ip) {
|
|
140
|
+
return typeof ip === "string" && (ip.includes(".") || ip.includes(":"));
|
|
141
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { writeOutput, expandLocalVars } from "./utils.js";
|
|
3
|
+
import { stat as statAsync } from "fs/promises";
|
|
4
|
+
|
|
5
|
+
export function readLocalFile(url, flags) {
|
|
6
|
+
const filePath = url.replace("file://", "");
|
|
7
|
+
|
|
8
|
+
if (!fs.existsSync(filePath)) {
|
|
9
|
+
console.error("File does not exist:", filePath);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const stat = fs.statSync(filePath);
|
|
14
|
+
|
|
15
|
+
if (flags.head) {
|
|
16
|
+
writeOutput(
|
|
17
|
+
`Status: 200 OK\nContent-Length: ${stat.size}\nContent-Type: text/plain\n`,
|
|
18
|
+
flags,
|
|
19
|
+
);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (flags["list-only"]) {
|
|
24
|
+
if (!stat.isDirectory()) {
|
|
25
|
+
console.error("-l works only on directories");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const items = fs.readdirSync(filePath).join("\n");
|
|
29
|
+
writeOutput(items, flags);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let content = fs.readFileSync(filePath);
|
|
34
|
+
writeOutput(content, flags);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createFile(url, flags) {
|
|
38
|
+
const targetPath = url.replace("file://", "");
|
|
39
|
+
let data;
|
|
40
|
+
|
|
41
|
+
if (flags.upload === "-") {
|
|
42
|
+
data = fs.readFileSync(0);
|
|
43
|
+
} else {
|
|
44
|
+
const uploadPath = expandLocalVars(flags.upload);
|
|
45
|
+
if (!fs.existsSync(uploadPath)) {
|
|
46
|
+
console.error("Upload file does not exist:", uploadPath);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
data = fs.readFileSync(uploadPath);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const mode = flags["create-file-mode"]
|
|
53
|
+
? parseInt(flags["create-file-mode"], 8)
|
|
54
|
+
: 0o644;
|
|
55
|
+
|
|
56
|
+
fs.writeFileSync(targetPath, data, { mode });
|
|
57
|
+
fs.chmodSync(targetPath, mode);
|
|
58
|
+
console.log(`Created file: ${targetPath} (${mode.toString(8)})`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function readHeaders(url, flags) {
|
|
62
|
+
const filePath = url.replace("file://", "");
|
|
63
|
+
try {
|
|
64
|
+
const stats = await statAsync(filePath);
|
|
65
|
+
|
|
66
|
+
const headers =
|
|
67
|
+
`Status: 200 OK\n` +
|
|
68
|
+
`Content-Length: ${stats.size}\n` +
|
|
69
|
+
`Last-Modified: ${stats.mtime.toUTCString()}\n`;
|
|
70
|
+
|
|
71
|
+
// writeOutput(headers, flags);
|
|
72
|
+
console.log(headers)
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error("File not found:", filePath);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
}
|
package/lib/flags.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const FLAG_DEFS = {
|
|
2
|
+
"-o": { key: "output", type: "string" },
|
|
3
|
+
"-l": { key: "listOnly", type: "boolean" },
|
|
4
|
+
"--list-only": { key: "listOnly", type: "boolean" },
|
|
5
|
+
"-4": { key: "ipv4", type: "boolean" },
|
|
6
|
+
"-6": { key: "ipv6", type: "boolean" },
|
|
7
|
+
"-h": { key: "help", type: "boolean" },
|
|
8
|
+
|
|
9
|
+
// output module
|
|
10
|
+
"-O": { key: "remoteName", type: "boolean" },
|
|
11
|
+
"-I": { key: "head", type: "boolean" },
|
|
12
|
+
"-R": { key: "preserveTimeStamp", type: "boolean" },
|
|
13
|
+
"--create-dirs": { key: "createDirs", type: "boolean" },
|
|
14
|
+
"--create-file-mode": { key: "create-file-mode", type: "boolean" },
|
|
15
|
+
"--no-clobber": { key: "noClobber", type: "boolean" },
|
|
16
|
+
"-N": { key: "noBuffer", type: "boolean" },
|
|
17
|
+
"--output-dir": { key: "outputDir", type: "string" },
|
|
18
|
+
|
|
19
|
+
// ftp module
|
|
20
|
+
"-Q": { key: "quote", type: "array" },
|
|
21
|
+
"-a": { key: "append", type: "boolean" },
|
|
22
|
+
"--append": { key: "append", type: "boolean" },
|
|
23
|
+
|
|
24
|
+
"--retry": { key: "retry", type: "number" },
|
|
25
|
+
|
|
26
|
+
// connection module
|
|
27
|
+
"--limit-rate": { key: "rateLimit", type: "number" },
|
|
28
|
+
"--max-time": { key: "maxTimeout", type: "number" },
|
|
29
|
+
"--connect-timeout": { key: "timeout", type: "number" },
|
|
30
|
+
"-Y": { key: "speedLimit", type: "number" },
|
|
31
|
+
"--speed-limit": { key: "speedLimit", type: "number" },
|
|
32
|
+
"-y": { key: "speedTime", type: "number" },
|
|
33
|
+
"--speed-time": { key: "speedTime", type: "number" },
|
|
34
|
+
"-Z": { key: "parallel", type: "array" },
|
|
35
|
+
"--parallel": { key: "parallel", type: "array" },
|
|
36
|
+
"--parallel-max": { key: "parallelMax", type: "number" },
|
|
37
|
+
"--parallel-immediate": { key: "parallelImmediate", type: "boolean" },
|
|
38
|
+
"--max-filesize": { key: "maxFilesize", type: "number" },
|
|
39
|
+
// ---------------- //
|
|
40
|
+
|
|
41
|
+
// DNS / resolving
|
|
42
|
+
"--dns-servers": { key: "dnsServers", type: "array" },
|
|
43
|
+
"--doh-url": { key: "dohUrl", type: "string" },
|
|
44
|
+
"--doh-insecure": { key: "dohInsecure", type: "boolean" },
|
|
45
|
+
"--doh-cert-status": { key: "dohCertStatus", type: "boolean" },
|
|
46
|
+
|
|
47
|
+
"--connect-to": { key: "connectTo", type: "array" },
|
|
48
|
+
"--dns-interface": { key: "dnsInterface", type: "string" },
|
|
49
|
+
"--dns-ipv4-addr": { key: "dnsIpv4Addr", type: "string" },
|
|
50
|
+
"--dns-ipv6-addr": { key: "dnsIpv6Addr", type: "string" },
|
|
51
|
+
// ----------------- //
|
|
52
|
+
|
|
53
|
+
"-u": { key: "user", type: "string" },
|
|
54
|
+
"-T": { key: "upload", type: "string" },
|
|
55
|
+
"--upload-file": { key: "upload", type: "string" },
|
|
56
|
+
|
|
57
|
+
"-X": { key: "method", type: "string" },
|
|
58
|
+
"-d": { key: "data", type: "string" },
|
|
59
|
+
"--data": { key: "data", type: "string" },
|
|
60
|
+
"--json": { key: "json", type: "string" },
|
|
61
|
+
"-H": { key: "headers", type: "array" },
|
|
62
|
+
"--header": { key: "headers", type: "array" },
|
|
63
|
+
"--POST": { key: "POST", type: "boolean" },
|
|
64
|
+
"--PUT": { key: "PUT", type: "boolean" },
|
|
65
|
+
"--DELETE": { key: "DELETE", type: "boolean" },
|
|
66
|
+
"--GET": { key: "GET", type: "boolean" },
|
|
67
|
+
"--resolve": { key: "resolve", type: "array" },
|
|
68
|
+
|
|
69
|
+
// SSL/TLS
|
|
70
|
+
"-k": { key: "insecure", type: "boolean" },
|
|
71
|
+
"--insecure": { key: "insecure", type: "boolean" },
|
|
72
|
+
|
|
73
|
+
// Redirects
|
|
74
|
+
"-L": { key: "location", type: "boolean" },
|
|
75
|
+
"--location": { key: "location", type: "boolean" },
|
|
76
|
+
"--max-redirs": { key: "maxRedirs", type: "number" },
|
|
77
|
+
|
|
78
|
+
// Headers
|
|
79
|
+
"-A": { key: "userAgent", type: "string" },
|
|
80
|
+
"--user-agent": { key: "userAgent", type: "string" },
|
|
81
|
+
"-e": { key: "referer", type: "string" },
|
|
82
|
+
"--referer": { key: "referer", type: "string" },
|
|
83
|
+
|
|
84
|
+
// Output
|
|
85
|
+
"-i": { key: "include", type: "boolean" },
|
|
86
|
+
"--include": { key: "include", type: "boolean" },
|
|
87
|
+
"-v": { key: "verbose", type: "boolean" },
|
|
88
|
+
"--verbose": { key: "verbose", type: "boolean" },
|
|
89
|
+
|
|
90
|
+
// Cookies
|
|
91
|
+
"-c": { key: "cookieJar", type: "string" },
|
|
92
|
+
"--cookie-jar": { key: "cookieJar", type: "string" },
|
|
93
|
+
"-b": { key: "cookie", type: "string" },
|
|
94
|
+
"--cookie": { key: "cookie", type: "string" },
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export function parseFlags(args) {
|
|
98
|
+
const flags = {};
|
|
99
|
+
|
|
100
|
+
for (let i = 0; i < args.length; i++) {
|
|
101
|
+
let arg = args[i];
|
|
102
|
+
let value;
|
|
103
|
+
|
|
104
|
+
// Handle --key=value
|
|
105
|
+
if (arg.includes("=")) {
|
|
106
|
+
const parts = arg.split("=");
|
|
107
|
+
arg = parts[0];
|
|
108
|
+
value = parts.slice(1).join("=");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const def = FLAG_DEFS[arg];
|
|
112
|
+
if (!def) continue;
|
|
113
|
+
|
|
114
|
+
switch (def.type) {
|
|
115
|
+
case "boolean":
|
|
116
|
+
flags[def.key] = true;
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
case "string":
|
|
120
|
+
case "number":
|
|
121
|
+
if (value === undefined) value = args[++i];
|
|
122
|
+
flags[def.key] = def.type === "number" ? Number(value) : value;
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
case "array":
|
|
126
|
+
if (value === undefined) value = args[++i];
|
|
127
|
+
flags[def.key] ??= [];
|
|
128
|
+
if (def.key === "headers") {
|
|
129
|
+
// Parse "Key: Value" to object
|
|
130
|
+
const [k, ...v] = value.split(":");
|
|
131
|
+
flags.headers ??= {};
|
|
132
|
+
flags.headers[k.trim()] = v.join(":").trim();
|
|
133
|
+
} else {
|
|
134
|
+
flags[def.key].push(value);
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return flags;
|
|
141
|
+
}
|