maven-proxy 1.0.2 → 1.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/README.md +2 -0
- package/package.json +1 -1
- package/src/cache/cache-path.js +52 -0
- package/src/cache/downloader.js +38 -0
- package/src/proxy/proxy-http-handler.js +44 -1
package/README.md
CHANGED
|
@@ -433,6 +433,8 @@ org.gradle.jvmargs=-Djavax.net.ssl.trustStore=/Users/yize/projects/maven-proxy/d
|
|
|
433
433
|
./gradlew --refresh-dependencies dependencies
|
|
434
434
|
```
|
|
435
435
|
|
|
436
|
+
Note: always set `trustStorePassword` together with `trustStore`. If you use `systemProp.javax.net.ssl.trustStore`, also set `systemProp.javax.net.ssl.trustStorePassword`.
|
|
437
|
+
|
|
436
438
|
### 9.2 npm: Proxy + SSL behavior
|
|
437
439
|
|
|
438
440
|
For local troubleshooting only, you can disable strict SSL temporarily. Recommended long-term approach is to import Root CA and keep strict SSL enabled.
|
package/package.json
CHANGED
package/src/cache/cache-path.js
CHANGED
|
@@ -12,6 +12,54 @@ function safeDecode(pathname) {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function looksLikeMavenVersionSegment(segment) {
|
|
16
|
+
return /^\d[0-9A-Za-z._-]*$/.test(String(segment || ""));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isLikelyMavenFilePath(parts, normalizedPath) {
|
|
20
|
+
if (normalizedPath.endsWith("/") || parts.length === 0) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const last = String(parts[parts.length - 1] || "").toLowerCase();
|
|
25
|
+
if (!last) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (last.startsWith("maven-metadata.")) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const knownSuffixes = [
|
|
34
|
+
".pom",
|
|
35
|
+
".jar",
|
|
36
|
+
".aar",
|
|
37
|
+
".war",
|
|
38
|
+
".zip",
|
|
39
|
+
".module",
|
|
40
|
+
".xml",
|
|
41
|
+
".sha1",
|
|
42
|
+
".md5",
|
|
43
|
+
".sha256",
|
|
44
|
+
".sha512",
|
|
45
|
+
".asc",
|
|
46
|
+
".json",
|
|
47
|
+
".toml",
|
|
48
|
+
".klib",
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
if (knownSuffixes.some((suffix) => last.endsWith(suffix))) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const secondLast = String(parts[parts.length - 2] || "").toLowerCase();
|
|
56
|
+
if (looksLikeMavenVersionSegment(secondLast)) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
15
63
|
export function getCacheFilePath(cacheDir, urlObj, options = {}) {
|
|
16
64
|
const ecosystem = sanitizeSegment(String(options.ecosystem || "generic").toLowerCase());
|
|
17
65
|
const includeHost = options.includeHost ?? ecosystem !== "maven";
|
|
@@ -35,6 +83,10 @@ export function getCacheFilePath(cacheDir, urlObj, options = {}) {
|
|
|
35
83
|
safeParts.unshift(sanitizeSegment(String(urlObj.hostname || "unknown").toLowerCase()));
|
|
36
84
|
}
|
|
37
85
|
|
|
86
|
+
if (ecosystem === "maven" && !isLikelyMavenFilePath(parts, normalized)) {
|
|
87
|
+
safeParts.push("__dir__.json");
|
|
88
|
+
}
|
|
89
|
+
|
|
38
90
|
const npmTarballPath = /\/-\/.+\.tgz$/i.test(lowerNormalized);
|
|
39
91
|
if (ecosystem === "npm" && !npmTarballPath) {
|
|
40
92
|
safeParts.push("__meta__.json");
|
package/src/cache/downloader.js
CHANGED
|
@@ -7,6 +7,31 @@ import { DownloadLogWriter } from "../common/download-log-writer.js";
|
|
|
7
7
|
|
|
8
8
|
const REDIRECT_STATUS = new Set([301, 302, 303, 307, 308]);
|
|
9
9
|
const MAX_REDIRECTS = 5;
|
|
10
|
+
const LOCAL_FS_ERROR_CODES = new Set([
|
|
11
|
+
"EACCES",
|
|
12
|
+
"EPERM",
|
|
13
|
+
"ENOSPC",
|
|
14
|
+
"EROFS",
|
|
15
|
+
"ENOTDIR",
|
|
16
|
+
"EISDIR",
|
|
17
|
+
"EINVAL",
|
|
18
|
+
"EMFILE",
|
|
19
|
+
"ENFILE",
|
|
20
|
+
"EEXIST",
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
function isLocalFsWriteError(error) {
|
|
24
|
+
if (!error || typeof error !== "object") {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (LOCAL_FS_ERROR_CODES.has(error.code)) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const message = String(error.message || "").toLowerCase();
|
|
33
|
+
return message.includes("enotdir") || message.includes("read-only file system");
|
|
34
|
+
}
|
|
10
35
|
|
|
11
36
|
function pickClient(protocol) {
|
|
12
37
|
return protocol === "https:" ? https : http;
|
|
@@ -343,6 +368,19 @@ export class Downloader {
|
|
|
343
368
|
await verifyFileSize(tempPath, metadata.contentLength);
|
|
344
369
|
await fs.promises.rename(tempPath, finalPath);
|
|
345
370
|
} catch (error) {
|
|
371
|
+
if (isLocalFsWriteError(error)) {
|
|
372
|
+
if (!error.statusCode) {
|
|
373
|
+
error.statusCode = 500;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
this.logDownload("local cache write failed", urlObj, {
|
|
377
|
+
code: error.code || "UNKNOWN",
|
|
378
|
+
targetPath: finalPath,
|
|
379
|
+
tempPath,
|
|
380
|
+
message: error.message,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
346
384
|
await removeIfExists(tempPath);
|
|
347
385
|
throw error;
|
|
348
386
|
}
|
|
@@ -5,6 +5,32 @@ import path from "node:path";
|
|
|
5
5
|
import { getCacheFilePath } from "../cache/cache-path.js";
|
|
6
6
|
import { detectPackageEcosystem } from "../common/ecosystem.js";
|
|
7
7
|
|
|
8
|
+
const LOCAL_FS_ERROR_CODES = new Set([
|
|
9
|
+
"EACCES",
|
|
10
|
+
"EPERM",
|
|
11
|
+
"ENOSPC",
|
|
12
|
+
"EROFS",
|
|
13
|
+
"ENOTDIR",
|
|
14
|
+
"EISDIR",
|
|
15
|
+
"EINVAL",
|
|
16
|
+
"EMFILE",
|
|
17
|
+
"ENFILE",
|
|
18
|
+
"EEXIST",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
function isLocalFsWriteError(error) {
|
|
22
|
+
if (!error || typeof error !== "object") {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (LOCAL_FS_ERROR_CODES.has(error.code)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const message = String(error.message || "").toLowerCase();
|
|
31
|
+
return message.includes("enotdir") || message.includes("read-only file system");
|
|
32
|
+
}
|
|
33
|
+
|
|
8
34
|
function pickClient(protocol) {
|
|
9
35
|
return protocol === "https:" ? https : http;
|
|
10
36
|
}
|
|
@@ -166,8 +192,25 @@ export function createHttpRequestHandler({ config, downloader, upstreamProxyMana
|
|
|
166
192
|
res.setHeader("x-cache", "MISS");
|
|
167
193
|
await serveFile(res, req, cachePath);
|
|
168
194
|
} catch (error) {
|
|
195
|
+
if (isLocalFsWriteError(error)) {
|
|
196
|
+
if (!error.statusCode) {
|
|
197
|
+
error.statusCode = 500;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (typeof downloader?.logDownload === "function") {
|
|
201
|
+
downloader.logDownload("local cache write failed", urlObj, {
|
|
202
|
+
code: error.code || "UNKNOWN",
|
|
203
|
+
cachePath,
|
|
204
|
+
message: error.message,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
console.error(`[proxy] local cache write failed cachePath=${cachePath} code=${error.code || "UNKNOWN"} message=${error.message}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
169
211
|
const statusCode = error.statusCode || 502;
|
|
170
|
-
|
|
212
|
+
const label = statusCode === 500 ? "Local cache write failed" : "Download failed";
|
|
213
|
+
sendText(res, statusCode, `${label}: ${error.message}`);
|
|
171
214
|
}
|
|
172
215
|
};
|
|
173
216
|
}
|