bosia 0.1.5 → 0.1.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bosia",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "description": "A fast, batteries-included fullstack framework — SSR · Svelte 5 Runes · Bun · ElysiaJS. File-based routing inspired by SvelteKit. No Node.js, no Vite, no adapters.",
6
6
  "keywords": [
@@ -1,10 +1,16 @@
1
1
  import type { Cookies, CookieOptions } from "./hooks.ts";
2
2
 
3
- // ─── Cookie Validation ───────────────────────────────────
3
+ // ─── Cookie Validation (RFC 6265) ────────────────────────
4
4
  /** Rejects characters that could inject into Set-Cookie headers. */
5
5
  const UNSAFE_COOKIE_VALUE = /[;\r\n]/;
6
6
  const VALID_SAMESITE = new Set(["Strict", "Lax", "None"]);
7
7
 
8
+ /**
9
+ * RFC 6265 §4.1.1: cookie-name is an HTTP token (RFC 2616 §2.2).
10
+ * Must be 1+ chars of ASCII 33-126, excluding separators: ( ) < > @ , ; : \ " / [ ] ? = { }
11
+ */
12
+ const VALID_COOKIE_NAME = /^[!#$%&'*+\-.0-9A-Z^_`a-z|~]+$/;
13
+
8
14
  // ─── Cookie Helpers ──────────────────────────────────────
9
15
 
10
16
  function parseCookies(header: string): Record<string, string> {
@@ -39,7 +45,8 @@ export class CookieJar implements Cookies {
39
45
  }
40
46
 
41
47
  set(name: string, value: string, options?: CookieOptions): void {
42
- let header = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
48
+ if (!VALID_COOKIE_NAME.test(name)) throw new Error(`Invalid cookie name: ${name}`);
49
+ let header = `${name}=${encodeURIComponent(value)}`;
43
50
  const path = options?.path ?? "/";
44
51
  if (UNSAFE_COOKIE_VALUE.test(path)) throw new Error(`Invalid cookie path: ${path}`);
45
52
  header += `; Path=${path}`;
package/src/core/env.ts CHANGED
@@ -21,24 +21,54 @@ const FRAMEWORK_VARS = new Set([
21
21
 
22
22
  // ─── .env File Parser ────────────────────────────────────
23
23
 
24
+ /** Valid JS/TS identifier: starts with letter/underscore, then alphanumeric/underscore. */
25
+ const VALID_ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
26
+
27
+ /** Process escape sequences in double-quoted values. */
28
+ function processEscapes(raw: string): string {
29
+ return raw.replace(/\\(.)/g, (_, ch) => {
30
+ switch (ch) {
31
+ case "n": return "\n";
32
+ case "r": return "\r";
33
+ case "t": return "\t";
34
+ case "\\": return "\\";
35
+ case '"': return '"';
36
+ default: return `\\${ch}`; // preserve unknown escapes
37
+ }
38
+ });
39
+ }
40
+
24
41
  /** Parse a .env file content into key/value pairs. Skips comments and empty lines. */
25
- function parseEnvFile(content: string): Record<string, string> {
42
+ function parseEnvFile(content: string, filename?: string): Record<string, string> {
26
43
  const result: Record<string, string> = {};
27
- for (const line of content.split("\n")) {
28
- const trimmed = line.trim();
44
+ const lines = content.split("\n");
45
+ for (let i = 0; i < lines.length; i++) {
46
+ const trimmed = lines[i].trim();
29
47
  if (!trimmed || trimmed.startsWith("#")) continue;
30
48
  const eqIdx = trimmed.indexOf("=");
31
49
  if (eqIdx === -1) continue;
32
50
  const key = trimmed.slice(0, eqIdx).trim();
51
+ if (!key) continue;
52
+
53
+ // Validate key is a valid identifier (required for codegen)
54
+ if (!VALID_ENV_NAME.test(key)) {
55
+ const loc = filename ? ` in ${filename}` : "";
56
+ throw new Error(
57
+ `Invalid env variable name "${key}"${loc} (line ${i + 1}). ` +
58
+ `Names must start with a letter or underscore and contain only [A-Za-z0-9_].`
59
+ );
60
+ }
61
+
33
62
  let value = trimmed.slice(eqIdx + 1).trim();
34
- // Strip surrounding quotes (single or double)
35
- if (
36
- (value.startsWith('"') && value.endsWith('"')) ||
37
- (value.startsWith("'") && value.endsWith("'"))
38
- ) {
63
+ // Double-quoted: process escape sequences
64
+ if (value.startsWith('"') && value.endsWith('"')) {
65
+ value = processEscapes(value.slice(1, -1));
66
+ }
67
+ // Single-quoted: literal (no escape processing)
68
+ else if (value.startsWith("'") && value.endsWith("'")) {
39
69
  value = value.slice(1, -1);
40
70
  }
41
- if (key) result[key] = value;
71
+ result[key] = value;
42
72
  }
43
73
  return result;
44
74
  }
@@ -75,7 +105,7 @@ export function loadEnv(mode: string, dir?: string): Record<string, string> {
75
105
  const filepath = join(root, filename);
76
106
  if (!existsSync(filepath)) continue;
77
107
  const content = readFileSync(filepath, "utf-8");
78
- const parsed = parseEnvFile(content);
108
+ const parsed = parseEnvFile(content, filename);
79
109
  merged = { ...merged, ...parsed };
80
110
  loaded.push(filename);
81
111
  }