@decentnetwork/lan 0.1.112 → 0.1.113

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.
Binary file
Binary file
Binary file
Binary file
@@ -33,6 +33,21 @@ function shellQuote(parts) {
33
33
  .map((p) => "'" + p.replace(/'/g, `'\\''`) + "'")
34
34
  .join(" ");
35
35
  }
36
+ /** Make an incoming filename safe to write to disk AND friendly to open from a
37
+ * shell. Strips path separators + control chars and normalizes exotic Unicode
38
+ * whitespace (e.g. macOS's U+202F narrow no-break space before "PM" in
39
+ * screenshot/recording names) to a plain space, so files don't need shell
40
+ * escaping. Never empty. */
41
+ function sanitizeFileName(name) {
42
+ const cleaned = (name || "file")
43
+ .replace(/[/\\]/g, "_")
44
+ // eslint-disable-next-line no-control-regex
45
+ .replace(/[\u0000-\u001f\u007f]/g, "")
46
+ .replace(/[\u00a0\u2000-\u200f\u2028\u2029\u202f\u205f\u3000\ufeff]/g, " ")
47
+ .replace(/\s+/g, " ")
48
+ .trim();
49
+ return cleaned || "file";
50
+ }
36
51
  import { ConfigLoader, DEFAULT_DORAS } from "../config/loader.js";
37
52
  export class DaemonServer {
38
53
  config;
@@ -598,7 +613,7 @@ export class DaemonServer {
598
613
  const dir = resolve(this.configDir, "downloads");
599
614
  const fs = await import("fs/promises");
600
615
  await fs.mkdir(dir, { recursive: true });
601
- const safe = (p.name || "file").replace(/[/\\]/g, "_");
616
+ const safe = sanitizeFileName(p.name);
602
617
  await fs.writeFile(resolve(dir, safe), Buffer.from(p.data));
603
618
  this.logger.info(`Saved file "${p.name}" (${p.size}B) from ${p.friendId.slice(0, 8)} → ${resolve(dir, safe)}`);
604
619
  // Record in chat history (as a download chip) and push to the UI.
package/dist/ui/server.js CHANGED
@@ -307,9 +307,15 @@ export function startFriendUi(opts) {
307
307
  res.end("not found");
308
308
  return;
309
309
  }
310
+ // Content-Disposition must be ASCII — Node throws ERR_INVALID_CHAR on a
311
+ // non-ASCII header value (filenames from macOS contain U+202F before
312
+ // "PM", and others may be Chinese, etc.). Provide an ASCII-only
313
+ // `filename=` fallback plus the RFC 5987 `filename*=UTF-8''…` with the
314
+ // real (percent-encoded) name for clients that support it.
315
+ const asciiName = name.replace(/[^\x20-\x7e]/g, "_").replace(/"/g, "");
310
316
  res.writeHead(200, {
311
317
  "content-type": "application/octet-stream",
312
- "content-disposition": `attachment; filename="${name.replace(/"/g, "")}"`,
318
+ "content-disposition": `attachment; filename="${asciiName}"; filename*=UTF-8''${encodeURIComponent(name)}`,
313
319
  });
314
320
  res.end(readFileSync(file));
315
321
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.112",
3
+ "version": "0.1.113",
4
4
  "description": "Private virtual LAN for self-hosted services and AI agents, built on Elastos Carrier. NAT-traversal, name service, ACL, all over a peer-to-peer mesh — no public IP required.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",