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 +1 -1
- package/src/core/cookies.ts +9 -2
- package/src/core/env.ts +40 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosia",
|
|
3
|
-
"version": "0.1.
|
|
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": [
|
package/src/core/cookies.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
//
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
}
|