maven-proxy 1.0.1 → 1.0.2
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 +466 -420
- package/bin/maven-proxy.js +585 -573
- package/package.json +54 -54
- package/scripts/truststore.js +96 -96
- package/src/cache/cache-path.js +50 -50
- package/src/cache/downloader.js +350 -350
- package/src/cert/cert-manager.js +194 -194
- package/src/cert/truststore-utils.js +383 -289
- package/src/common/console-log-file.js +61 -61
- package/src/common/daily-log-file.js +78 -78
- package/src/common/domain-match.js +39 -39
- package/src/common/download-log-writer.js +26 -26
- package/src/common/ecosystem.js +63 -63
- package/src/common/java-home.js +327 -327
- package/src/config/config.js +224 -213
- package/src/index.js +93 -93
- package/src/proxy/proxy-connect-handler.js +173 -173
- package/src/proxy/proxy-http-handler.js +187 -187
- package/src/proxy/proxy-server.js +35 -35
- package/src/proxy/upstream-proxy.js +236 -236
- package/src/repo/repo-server.js +120 -120
|
@@ -1,187 +1,187 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import http from "node:http";
|
|
3
|
-
import https from "node:https";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { getCacheFilePath } from "../cache/cache-path.js";
|
|
6
|
-
import { detectPackageEcosystem } from "../common/ecosystem.js";
|
|
7
|
-
|
|
8
|
-
function pickClient(protocol) {
|
|
9
|
-
return protocol === "https:" ? https : http;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function sanitizeHeaders(headers = {}) {
|
|
13
|
-
const result = { ...headers };
|
|
14
|
-
const blocked = [
|
|
15
|
-
"proxy-connection",
|
|
16
|
-
"proxy-authorization",
|
|
17
|
-
"proxy-authenticate",
|
|
18
|
-
"connection",
|
|
19
|
-
"keep-alive",
|
|
20
|
-
"transfer-encoding",
|
|
21
|
-
"upgrade",
|
|
22
|
-
"te",
|
|
23
|
-
"trailer",
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
for (const key of blocked) {
|
|
27
|
-
delete result[key];
|
|
28
|
-
delete result[key.toLowerCase()];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return result;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async function statIfFile(filePath) {
|
|
35
|
-
try {
|
|
36
|
-
const stats = await fs.promises.stat(filePath);
|
|
37
|
-
return stats.isFile() ? stats : null;
|
|
38
|
-
} catch (error) {
|
|
39
|
-
if (error.code === "ENOENT") {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
throw error;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function sendText(res, statusCode, message) {
|
|
47
|
-
res.writeHead(statusCode, { "content-type": "text/plain; charset=utf-8" });
|
|
48
|
-
res.end(message);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function buildUrl(req, forcedProtocol = null) {
|
|
52
|
-
const raw = req.url || "/";
|
|
53
|
-
if (/^https?:\/\//i.test(raw)) {
|
|
54
|
-
return new URL(raw);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const host = req.headers.host || req.socket.__mitmHost;
|
|
58
|
-
if (!host) {
|
|
59
|
-
throw new Error("Missing host header");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const protocol = forcedProtocol || "http:";
|
|
63
|
-
return new URL(`${protocol}//${host}${raw}`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async function serveFile(res, req, filePath) {
|
|
67
|
-
const stats = await statIfFile(filePath);
|
|
68
|
-
if (!stats) {
|
|
69
|
-
sendText(res, 404, "Not Found");
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
res.setHeader("content-length", String(stats.size));
|
|
74
|
-
if (!res.hasHeader("x-cache")) {
|
|
75
|
-
res.setHeader("x-cache", "HIT");
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (req.method === "HEAD") {
|
|
79
|
-
res.writeHead(200);
|
|
80
|
-
res.end();
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
res.writeHead(200);
|
|
85
|
-
fs.createReadStream(filePath).pipe(res);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function forwardDirectRequest(req, res, urlObj, timeoutMs, upstreamProxyManager = null) {
|
|
89
|
-
const client = pickClient(urlObj.protocol);
|
|
90
|
-
const headers = sanitizeHeaders(req.headers);
|
|
91
|
-
headers.host = urlObj.host;
|
|
92
|
-
const agent = upstreamProxyManager ? upstreamProxyManager.getAgentForUrl(urlObj) : undefined;
|
|
93
|
-
|
|
94
|
-
if (agent) {
|
|
95
|
-
console.log(`[proxy] direct forward via upstream host=${urlObj.hostname} protocol=${urlObj.protocol}`);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const upstreamReq = client.request(
|
|
99
|
-
{
|
|
100
|
-
protocol: urlObj.protocol,
|
|
101
|
-
hostname: urlObj.hostname,
|
|
102
|
-
port: urlObj.port || (urlObj.protocol === "https:" ? 443 : 80),
|
|
103
|
-
method: req.method,
|
|
104
|
-
path: `${urlObj.pathname}${urlObj.search}`,
|
|
105
|
-
headers,
|
|
106
|
-
agent,
|
|
107
|
-
},
|
|
108
|
-
(upstreamRes) => {
|
|
109
|
-
res.writeHead(upstreamRes.statusCode || 502, upstreamRes.headers);
|
|
110
|
-
upstreamRes.pipe(res);
|
|
111
|
-
},
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
upstreamReq.setTimeout(timeoutMs, () => {
|
|
115
|
-
upstreamReq.destroy(new Error(`Upstream timeout after ${timeoutMs}ms`));
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
upstreamReq.on("error", (error) => {
|
|
119
|
-
if (!res.headersSent) {
|
|
120
|
-
sendText(res, 502, `Proxy forward failed: ${error.message}`);
|
|
121
|
-
} else {
|
|
122
|
-
res.destroy(error);
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
req.pipe(upstreamReq);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function createHttpRequestHandler({ config, downloader, upstreamProxyManager = null, matchesDomain }) {
|
|
130
|
-
return async function handleHttpRequestPath(req, res, forcedProtocol = null) {
|
|
131
|
-
let urlObj;
|
|
132
|
-
try {
|
|
133
|
-
urlObj = buildUrl(req, forcedProtocol);
|
|
134
|
-
} catch (error) {
|
|
135
|
-
sendText(res, 400, `Bad request: ${error.message}`);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const method = (req.method || "GET").toUpperCase();
|
|
140
|
-
if (method !== "GET" && method !== "HEAD") {
|
|
141
|
-
forwardDirectRequest(req, res, urlObj, config.downloadTimeoutMs, upstreamProxyManager);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
let cachePath;
|
|
146
|
-
try {
|
|
147
|
-
const ecosystem = detectPackageEcosystem(urlObj, config, matchesDomain);
|
|
148
|
-
cachePath = getCacheFilePath(config.cacheDir, urlObj, {
|
|
149
|
-
ecosystem,
|
|
150
|
-
includeHost: ecosystem !== "maven",
|
|
151
|
-
});
|
|
152
|
-
} catch (error) {
|
|
153
|
-
sendText(res, 400, `Invalid cache path: ${error.message}`);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const existing = await statIfFile(cachePath);
|
|
158
|
-
if (existing) {
|
|
159
|
-
await serveFile(res, req, cachePath);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
try {
|
|
164
|
-
await fs.promises.mkdir(path.dirname(cachePath), { recursive: true });
|
|
165
|
-
await downloader.ensureCached(urlObj, cachePath, req.headers);
|
|
166
|
-
res.setHeader("x-cache", "MISS");
|
|
167
|
-
await serveFile(res, req, cachePath);
|
|
168
|
-
} catch (error) {
|
|
169
|
-
const statusCode = error.statusCode || 502;
|
|
170
|
-
sendText(res, statusCode, `Download failed: ${error.message}`);
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export function createMitmHttpServer(handleHttpRequestPath) {
|
|
176
|
-
const server = http.createServer((req, res) => {
|
|
177
|
-
handleHttpRequestPath(req, res, "https:").catch((error) => {
|
|
178
|
-
sendText(res, 500, `MITM request failed: ${error.message}`);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
server.on("clientError", (error, socket) => {
|
|
183
|
-
socket.destroy(error);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
return server;
|
|
187
|
-
}
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import http from "node:http";
|
|
3
|
+
import https from "node:https";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { getCacheFilePath } from "../cache/cache-path.js";
|
|
6
|
+
import { detectPackageEcosystem } from "../common/ecosystem.js";
|
|
7
|
+
|
|
8
|
+
function pickClient(protocol) {
|
|
9
|
+
return protocol === "https:" ? https : http;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function sanitizeHeaders(headers = {}) {
|
|
13
|
+
const result = { ...headers };
|
|
14
|
+
const blocked = [
|
|
15
|
+
"proxy-connection",
|
|
16
|
+
"proxy-authorization",
|
|
17
|
+
"proxy-authenticate",
|
|
18
|
+
"connection",
|
|
19
|
+
"keep-alive",
|
|
20
|
+
"transfer-encoding",
|
|
21
|
+
"upgrade",
|
|
22
|
+
"te",
|
|
23
|
+
"trailer",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
for (const key of blocked) {
|
|
27
|
+
delete result[key];
|
|
28
|
+
delete result[key.toLowerCase()];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function statIfFile(filePath) {
|
|
35
|
+
try {
|
|
36
|
+
const stats = await fs.promises.stat(filePath);
|
|
37
|
+
return stats.isFile() ? stats : null;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error.code === "ENOENT") {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function sendText(res, statusCode, message) {
|
|
47
|
+
res.writeHead(statusCode, { "content-type": "text/plain; charset=utf-8" });
|
|
48
|
+
res.end(message);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildUrl(req, forcedProtocol = null) {
|
|
52
|
+
const raw = req.url || "/";
|
|
53
|
+
if (/^https?:\/\//i.test(raw)) {
|
|
54
|
+
return new URL(raw);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const host = req.headers.host || req.socket.__mitmHost;
|
|
58
|
+
if (!host) {
|
|
59
|
+
throw new Error("Missing host header");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const protocol = forcedProtocol || "http:";
|
|
63
|
+
return new URL(`${protocol}//${host}${raw}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function serveFile(res, req, filePath) {
|
|
67
|
+
const stats = await statIfFile(filePath);
|
|
68
|
+
if (!stats) {
|
|
69
|
+
sendText(res, 404, "Not Found");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
res.setHeader("content-length", String(stats.size));
|
|
74
|
+
if (!res.hasHeader("x-cache")) {
|
|
75
|
+
res.setHeader("x-cache", "HIT");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (req.method === "HEAD") {
|
|
79
|
+
res.writeHead(200);
|
|
80
|
+
res.end();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
res.writeHead(200);
|
|
85
|
+
fs.createReadStream(filePath).pipe(res);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function forwardDirectRequest(req, res, urlObj, timeoutMs, upstreamProxyManager = null) {
|
|
89
|
+
const client = pickClient(urlObj.protocol);
|
|
90
|
+
const headers = sanitizeHeaders(req.headers);
|
|
91
|
+
headers.host = urlObj.host;
|
|
92
|
+
const agent = upstreamProxyManager ? upstreamProxyManager.getAgentForUrl(urlObj) : undefined;
|
|
93
|
+
|
|
94
|
+
if (agent) {
|
|
95
|
+
console.log(`[proxy] direct forward via upstream host=${urlObj.hostname} protocol=${urlObj.protocol}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const upstreamReq = client.request(
|
|
99
|
+
{
|
|
100
|
+
protocol: urlObj.protocol,
|
|
101
|
+
hostname: urlObj.hostname,
|
|
102
|
+
port: urlObj.port || (urlObj.protocol === "https:" ? 443 : 80),
|
|
103
|
+
method: req.method,
|
|
104
|
+
path: `${urlObj.pathname}${urlObj.search}`,
|
|
105
|
+
headers,
|
|
106
|
+
agent,
|
|
107
|
+
},
|
|
108
|
+
(upstreamRes) => {
|
|
109
|
+
res.writeHead(upstreamRes.statusCode || 502, upstreamRes.headers);
|
|
110
|
+
upstreamRes.pipe(res);
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
upstreamReq.setTimeout(timeoutMs, () => {
|
|
115
|
+
upstreamReq.destroy(new Error(`Upstream timeout after ${timeoutMs}ms`));
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
upstreamReq.on("error", (error) => {
|
|
119
|
+
if (!res.headersSent) {
|
|
120
|
+
sendText(res, 502, `Proxy forward failed: ${error.message}`);
|
|
121
|
+
} else {
|
|
122
|
+
res.destroy(error);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
req.pipe(upstreamReq);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function createHttpRequestHandler({ config, downloader, upstreamProxyManager = null, matchesDomain }) {
|
|
130
|
+
return async function handleHttpRequestPath(req, res, forcedProtocol = null) {
|
|
131
|
+
let urlObj;
|
|
132
|
+
try {
|
|
133
|
+
urlObj = buildUrl(req, forcedProtocol);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
sendText(res, 400, `Bad request: ${error.message}`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const method = (req.method || "GET").toUpperCase();
|
|
140
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
141
|
+
forwardDirectRequest(req, res, urlObj, config.downloadTimeoutMs, upstreamProxyManager);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let cachePath;
|
|
146
|
+
try {
|
|
147
|
+
const ecosystem = detectPackageEcosystem(urlObj, config, matchesDomain);
|
|
148
|
+
cachePath = getCacheFilePath(config.cacheDir, urlObj, {
|
|
149
|
+
ecosystem,
|
|
150
|
+
includeHost: ecosystem !== "maven",
|
|
151
|
+
});
|
|
152
|
+
} catch (error) {
|
|
153
|
+
sendText(res, 400, `Invalid cache path: ${error.message}`);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const existing = await statIfFile(cachePath);
|
|
158
|
+
if (existing) {
|
|
159
|
+
await serveFile(res, req, cachePath);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
await fs.promises.mkdir(path.dirname(cachePath), { recursive: true });
|
|
165
|
+
await downloader.ensureCached(urlObj, cachePath, req.headers);
|
|
166
|
+
res.setHeader("x-cache", "MISS");
|
|
167
|
+
await serveFile(res, req, cachePath);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
const statusCode = error.statusCode || 502;
|
|
170
|
+
sendText(res, statusCode, `Download failed: ${error.message}`);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function createMitmHttpServer(handleHttpRequestPath) {
|
|
176
|
+
const server = http.createServer((req, res) => {
|
|
177
|
+
handleHttpRequestPath(req, res, "https:").catch((error) => {
|
|
178
|
+
sendText(res, 500, `MITM request failed: ${error.message}`);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
server.on("clientError", (error, socket) => {
|
|
183
|
+
socket.destroy(error);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return server;
|
|
187
|
+
}
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
import http from "node:http";
|
|
2
|
-
import { createHttpRequestHandler, createMitmHttpServer } from "./proxy-http-handler.js";
|
|
3
|
-
import { attachConnectHandler } from "./proxy-connect-handler.js";
|
|
4
|
-
|
|
5
|
-
function sendText(res, statusCode, message) {
|
|
6
|
-
res.writeHead(statusCode, { "content-type": "text/plain; charset=utf-8" });
|
|
7
|
-
res.end(message);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function startProxyServer(config, certManager, downloader, matchesDomain, upstreamProxyManager = null) {
|
|
11
|
-
const handleHttpRequestPath = createHttpRequestHandler({
|
|
12
|
-
config,
|
|
13
|
-
downloader,
|
|
14
|
-
upstreamProxyManager,
|
|
15
|
-
matchesDomain,
|
|
16
|
-
});
|
|
17
|
-
const mitmHttpServer = createMitmHttpServer(handleHttpRequestPath);
|
|
18
|
-
|
|
19
|
-
const server = http.createServer((req, res) => {
|
|
20
|
-
handleHttpRequestPath(req, res, null).catch((error) => {
|
|
21
|
-
sendText(res, 500, `Proxy request failed: ${error.message}`);
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
attachConnectHandler(server, {
|
|
26
|
-
config,
|
|
27
|
-
certManager,
|
|
28
|
-
matchesDomain,
|
|
29
|
-
upstreamProxyManager,
|
|
30
|
-
mitmHttpServer,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
server.listen(config.proxyPort);
|
|
34
|
-
return { proxyServer: server, mitmHttpServer };
|
|
35
|
-
}
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import { createHttpRequestHandler, createMitmHttpServer } from "./proxy-http-handler.js";
|
|
3
|
+
import { attachConnectHandler } from "./proxy-connect-handler.js";
|
|
4
|
+
|
|
5
|
+
function sendText(res, statusCode, message) {
|
|
6
|
+
res.writeHead(statusCode, { "content-type": "text/plain; charset=utf-8" });
|
|
7
|
+
res.end(message);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function startProxyServer(config, certManager, downloader, matchesDomain, upstreamProxyManager = null) {
|
|
11
|
+
const handleHttpRequestPath = createHttpRequestHandler({
|
|
12
|
+
config,
|
|
13
|
+
downloader,
|
|
14
|
+
upstreamProxyManager,
|
|
15
|
+
matchesDomain,
|
|
16
|
+
});
|
|
17
|
+
const mitmHttpServer = createMitmHttpServer(handleHttpRequestPath);
|
|
18
|
+
|
|
19
|
+
const server = http.createServer((req, res) => {
|
|
20
|
+
handleHttpRequestPath(req, res, null).catch((error) => {
|
|
21
|
+
sendText(res, 500, `Proxy request failed: ${error.message}`);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
attachConnectHandler(server, {
|
|
26
|
+
config,
|
|
27
|
+
certManager,
|
|
28
|
+
matchesDomain,
|
|
29
|
+
upstreamProxyManager,
|
|
30
|
+
mitmHttpServer,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
server.listen(config.proxyPort);
|
|
34
|
+
return { proxyServer: server, mitmHttpServer };
|
|
35
|
+
}
|