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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maven-proxy",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Maven proxy with cache, HTTPS MITM for selected domains, and local repo publishing",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -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");
@@ -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
- sendText(res, statusCode, `Download failed: ${error.message}`);
212
+ const label = statusCode === 500 ? "Local cache write failed" : "Download failed";
213
+ sendText(res, statusCode, `${label}: ${error.message}`);
171
214
  }
172
215
  };
173
216
  }