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
package/src/repo/repo-server.js
CHANGED
|
@@ -1,120 +1,120 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import http from "node:http";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
function safeJoin(baseDir, requestPath) {
|
|
6
|
-
const pathname = decodeURIComponent(requestPath || "/");
|
|
7
|
-
const normalized = path.posix.normalize(pathname).replace(/^\/+/, "");
|
|
8
|
-
|
|
9
|
-
if (normalized.includes("..")) {
|
|
10
|
-
throw new Error("Path traversal is not allowed");
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return path.join(baseDir, normalized);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async function statIfExists(filePath) {
|
|
17
|
-
try {
|
|
18
|
-
return await fs.promises.stat(filePath);
|
|
19
|
-
} catch (error) {
|
|
20
|
-
if (error.code === "ENOENT") {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
throw error;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function buildRemoteUrl(repoBase, relativePath) {
|
|
28
|
-
const base = repoBase.endsWith("/") ? repoBase : `${repoBase}/`;
|
|
29
|
-
const relative = relativePath.replace(/^\/+/, "");
|
|
30
|
-
return new URL(relative, base);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function buildCandidateRelativePaths(relativePath) {
|
|
34
|
-
const normalized = relativePath.replace(/^\/+/, "");
|
|
35
|
-
const candidates = [normalized];
|
|
36
|
-
|
|
37
|
-
if (normalized.toLowerCase().startsWith("maven2/")) {
|
|
38
|
-
candidates.push(normalized.slice("maven2/".length));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return [...new Set(candidates.filter(Boolean))];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function ensureFromRemoteRepos(config, downloader, filePath, relativePath) {
|
|
45
|
-
if (!downloader) {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const repos = config.repoFallbackRepos || [];
|
|
50
|
-
if (repos.length === 0) {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
let hasNon404Error = false;
|
|
55
|
-
let lastError = null;
|
|
56
|
-
const candidatePaths = buildCandidateRelativePaths(relativePath);
|
|
57
|
-
|
|
58
|
-
for (const repoBase of repos) {
|
|
59
|
-
for (const candidatePath of candidatePaths) {
|
|
60
|
-
const remoteUrl = buildRemoteUrl(repoBase, candidatePath);
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
console.log(`[repo] cache miss, try remote ${remoteUrl.href}`);
|
|
64
|
-
await downloader.ensureCached(remoteUrl, filePath, {});
|
|
65
|
-
return await statIfExists(filePath);
|
|
66
|
-
} catch (error) {
|
|
67
|
-
lastError = error;
|
|
68
|
-
if (error.statusCode !== 404) {
|
|
69
|
-
hasNon404Error = true;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (hasNon404Error && lastError) {
|
|
76
|
-
throw lastError;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function startRepoServer(config, downloader = null) {
|
|
83
|
-
const server = http.createServer(async (req, res) => {
|
|
84
|
-
try {
|
|
85
|
-
const urlObj = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
86
|
-
const relativePath = path.posix.normalize(urlObj.pathname || "/").replace(/^\/+/, "");
|
|
87
|
-
const filePath = safeJoin(config.mavenCacheDir, relativePath);
|
|
88
|
-
let stats = await statIfExists(filePath);
|
|
89
|
-
|
|
90
|
-
if (!stats || !stats.isFile()) {
|
|
91
|
-
stats = await ensureFromRemoteRepos(config, downloader, filePath, relativePath);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (!stats || !stats.isFile()) {
|
|
95
|
-
res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
96
|
-
res.end("Not Found");
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
res.setHeader("content-length", String(stats.size));
|
|
101
|
-
res.setHeader("cache-control", "public, max-age=3600");
|
|
102
|
-
|
|
103
|
-
if (req.method === "HEAD") {
|
|
104
|
-
res.writeHead(200);
|
|
105
|
-
res.end();
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
res.writeHead(200);
|
|
110
|
-
fs.createReadStream(filePath).pipe(res);
|
|
111
|
-
} catch (error) {
|
|
112
|
-
const statusCode = error.statusCode && error.statusCode >= 400 ? 502 : 500;
|
|
113
|
-
res.writeHead(statusCode, { "content-type": "text/plain; charset=utf-8" });
|
|
114
|
-
res.end(`Repo server error: ${error.message}`);
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
server.listen(config.repoPort);
|
|
119
|
-
return server;
|
|
120
|
-
}
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import http from "node:http";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
function safeJoin(baseDir, requestPath) {
|
|
6
|
+
const pathname = decodeURIComponent(requestPath || "/");
|
|
7
|
+
const normalized = path.posix.normalize(pathname).replace(/^\/+/, "");
|
|
8
|
+
|
|
9
|
+
if (normalized.includes("..")) {
|
|
10
|
+
throw new Error("Path traversal is not allowed");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return path.join(baseDir, normalized);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function statIfExists(filePath) {
|
|
17
|
+
try {
|
|
18
|
+
return await fs.promises.stat(filePath);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (error.code === "ENOENT") {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function buildRemoteUrl(repoBase, relativePath) {
|
|
28
|
+
const base = repoBase.endsWith("/") ? repoBase : `${repoBase}/`;
|
|
29
|
+
const relative = relativePath.replace(/^\/+/, "");
|
|
30
|
+
return new URL(relative, base);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function buildCandidateRelativePaths(relativePath) {
|
|
34
|
+
const normalized = relativePath.replace(/^\/+/, "");
|
|
35
|
+
const candidates = [normalized];
|
|
36
|
+
|
|
37
|
+
if (normalized.toLowerCase().startsWith("maven2/")) {
|
|
38
|
+
candidates.push(normalized.slice("maven2/".length));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return [...new Set(candidates.filter(Boolean))];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function ensureFromRemoteRepos(config, downloader, filePath, relativePath) {
|
|
45
|
+
if (!downloader) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const repos = config.repoFallbackRepos || [];
|
|
50
|
+
if (repos.length === 0) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let hasNon404Error = false;
|
|
55
|
+
let lastError = null;
|
|
56
|
+
const candidatePaths = buildCandidateRelativePaths(relativePath);
|
|
57
|
+
|
|
58
|
+
for (const repoBase of repos) {
|
|
59
|
+
for (const candidatePath of candidatePaths) {
|
|
60
|
+
const remoteUrl = buildRemoteUrl(repoBase, candidatePath);
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
console.log(`[repo] cache miss, try remote ${remoteUrl.href}`);
|
|
64
|
+
await downloader.ensureCached(remoteUrl, filePath, {});
|
|
65
|
+
return await statIfExists(filePath);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
lastError = error;
|
|
68
|
+
if (error.statusCode !== 404) {
|
|
69
|
+
hasNon404Error = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (hasNon404Error && lastError) {
|
|
76
|
+
throw lastError;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function startRepoServer(config, downloader = null) {
|
|
83
|
+
const server = http.createServer(async (req, res) => {
|
|
84
|
+
try {
|
|
85
|
+
const urlObj = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
86
|
+
const relativePath = path.posix.normalize(urlObj.pathname || "/").replace(/^\/+/, "");
|
|
87
|
+
const filePath = safeJoin(config.mavenCacheDir, relativePath);
|
|
88
|
+
let stats = await statIfExists(filePath);
|
|
89
|
+
|
|
90
|
+
if (!stats || !stats.isFile()) {
|
|
91
|
+
stats = await ensureFromRemoteRepos(config, downloader, filePath, relativePath);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!stats || !stats.isFile()) {
|
|
95
|
+
res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
96
|
+
res.end("Not Found");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
res.setHeader("content-length", String(stats.size));
|
|
101
|
+
res.setHeader("cache-control", "public, max-age=3600");
|
|
102
|
+
|
|
103
|
+
if (req.method === "HEAD") {
|
|
104
|
+
res.writeHead(200);
|
|
105
|
+
res.end();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
res.writeHead(200);
|
|
110
|
+
fs.createReadStream(filePath).pipe(res);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
const statusCode = error.statusCode && error.statusCode >= 400 ? 502 : 500;
|
|
113
|
+
res.writeHead(statusCode, { "content-type": "text/plain; charset=utf-8" });
|
|
114
|
+
res.end(`Repo server error: ${error.message}`);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
server.listen(config.repoPort);
|
|
119
|
+
return server;
|
|
120
|
+
}
|