h3 1.15.4 → 1.15.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.
Files changed (3) hide show
  1. package/dist/index.cjs +30 -11
  2. package/dist/index.mjs +30 -11
  3. package/package.json +39 -36
package/dist/index.cjs CHANGED
@@ -3,8 +3,8 @@
3
3
  const ufo = require('ufo');
4
4
  const cookieEs = require('cookie-es');
5
5
  const radix3 = require('radix3');
6
- const destr = require('destr');
7
6
  const defu = require('defu');
7
+ const destr = require('destr');
8
8
  const crypto = require('uncrypto');
9
9
  const ironWebcrypto = require('iron-webcrypto');
10
10
  const nodeMockHttp = require('node-mock-http');
@@ -418,7 +418,9 @@ function readRawBody(event, encoding = "utf8") {
418
418
  });
419
419
  return encoding ? promise2.then((buff) => buff.toString(encoding)) : promise2;
420
420
  }
421
- if (!Number.parseInt(event.node.req.headers["content-length"] || "") && !String(event.node.req.headers["transfer-encoding"] ?? "").split(",").map((e) => e.trim()).filter(Boolean).includes("chunked")) {
421
+ if (!Number.parseInt(event.node.req.headers["content-length"] || "") && !/\bchunked\b/i.test(
422
+ String(event.node.req.headers["transfer-encoding"] ?? "")
423
+ )) {
422
424
  return Promise.resolve(void 0);
423
425
  }
424
426
  const promise = event.node.req[RawBodySymbol] = new Promise(
@@ -1440,11 +1442,16 @@ async function updateSession(event, config, update) {
1440
1442
  async function sealSession(event, config) {
1441
1443
  const sessionName = config.name || DEFAULT_NAME;
1442
1444
  const session = event.context.sessions?.[sessionName] || await getSession(event, config);
1443
- const sealed = await ironWebcrypto.seal(config.crypto || crypto__default, session, config.password, {
1444
- ...ironWebcrypto.defaults,
1445
- ttl: config.maxAge ? config.maxAge * 1e3 : 0,
1446
- ...config.seal
1447
- });
1445
+ const sealed = await ironWebcrypto.seal(
1446
+ config.crypto || crypto__default,
1447
+ session,
1448
+ config.password,
1449
+ {
1450
+ ...ironWebcrypto.defaults,
1451
+ ttl: config.maxAge ? config.maxAge * 1e3 : 0,
1452
+ ...config.seal
1453
+ }
1454
+ );
1448
1455
  return sealed;
1449
1456
  }
1450
1457
  async function unsealSession(_event, config, sealed) {
@@ -1481,22 +1488,28 @@ function clearSession(event, config) {
1481
1488
  function formatEventStreamMessage(message) {
1482
1489
  let result = "";
1483
1490
  if (message.id) {
1484
- result += `id: ${message.id}
1491
+ result += `id: ${_sanitizeSingleLine(message.id)}
1485
1492
  `;
1486
1493
  }
1487
1494
  if (message.event) {
1488
- result += `event: ${message.event}
1495
+ result += `event: ${_sanitizeSingleLine(message.event)}
1489
1496
  `;
1490
1497
  }
1491
1498
  if (typeof message.retry === "number" && Number.isInteger(message.retry)) {
1492
1499
  result += `retry: ${message.retry}
1493
1500
  `;
1494
1501
  }
1495
- result += `data: ${message.data}
1496
-
1502
+ const data = typeof message.data === "string" ? message.data : "";
1503
+ for (const line of data.split("\n")) {
1504
+ result += `data: ${line}
1497
1505
  `;
1506
+ }
1507
+ result += "\n";
1498
1508
  return result;
1499
1509
  }
1510
+ function _sanitizeSingleLine(value) {
1511
+ return value.replace(/[\n\r]/g, "");
1512
+ }
1500
1513
  function formatEventStreamMessages(messages) {
1501
1514
  let result = "";
1502
1515
  for (const msg of messages) {
@@ -1661,6 +1674,12 @@ async function serveStatic(event, options) {
1661
1674
  const originalId = ufo.decodePath(
1662
1675
  ufo.withLeadingSlash(ufo.withoutTrailingSlash(ufo.parseURL(event.path).pathname))
1663
1676
  );
1677
+ if (originalId.includes("..")) {
1678
+ if (!options.fallthrough) {
1679
+ throw createError({ statusCode: 404 });
1680
+ }
1681
+ return false;
1682
+ }
1664
1683
  const acceptEncodings = parseAcceptEncoding(
1665
1684
  getRequestHeader(event, "accept-encoding"),
1666
1685
  options.encodings
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { withoutTrailingSlash, withoutBase, getQuery as getQuery$1, decode, decodePath, withLeadingSlash, parseURL, joinURL } from 'ufo';
2
2
  import { parse as parse$1, serialize, parseSetCookie } from 'cookie-es';
3
3
  import { createRouter as createRouter$1, toRouteMatcher } from 'radix3';
4
- import destr from 'destr';
5
4
  import { defu } from 'defu';
5
+ import destr from 'destr';
6
6
  import crypto from 'uncrypto';
7
7
  import { seal, defaults, unseal } from 'iron-webcrypto';
8
8
  import { IncomingMessage, ServerResponse } from 'node-mock-http';
@@ -411,7 +411,9 @@ function readRawBody(event, encoding = "utf8") {
411
411
  });
412
412
  return encoding ? promise2.then((buff) => buff.toString(encoding)) : promise2;
413
413
  }
414
- if (!Number.parseInt(event.node.req.headers["content-length"] || "") && !String(event.node.req.headers["transfer-encoding"] ?? "").split(",").map((e) => e.trim()).filter(Boolean).includes("chunked")) {
414
+ if (!Number.parseInt(event.node.req.headers["content-length"] || "") && !/\bchunked\b/i.test(
415
+ String(event.node.req.headers["transfer-encoding"] ?? "")
416
+ )) {
415
417
  return Promise.resolve(void 0);
416
418
  }
417
419
  const promise = event.node.req[RawBodySymbol] = new Promise(
@@ -1433,11 +1435,16 @@ async function updateSession(event, config, update) {
1433
1435
  async function sealSession(event, config) {
1434
1436
  const sessionName = config.name || DEFAULT_NAME;
1435
1437
  const session = event.context.sessions?.[sessionName] || await getSession(event, config);
1436
- const sealed = await seal(config.crypto || crypto, session, config.password, {
1437
- ...defaults,
1438
- ttl: config.maxAge ? config.maxAge * 1e3 : 0,
1439
- ...config.seal
1440
- });
1438
+ const sealed = await seal(
1439
+ config.crypto || crypto,
1440
+ session,
1441
+ config.password,
1442
+ {
1443
+ ...defaults,
1444
+ ttl: config.maxAge ? config.maxAge * 1e3 : 0,
1445
+ ...config.seal
1446
+ }
1447
+ );
1441
1448
  return sealed;
1442
1449
  }
1443
1450
  async function unsealSession(_event, config, sealed) {
@@ -1474,22 +1481,28 @@ function clearSession(event, config) {
1474
1481
  function formatEventStreamMessage(message) {
1475
1482
  let result = "";
1476
1483
  if (message.id) {
1477
- result += `id: ${message.id}
1484
+ result += `id: ${_sanitizeSingleLine(message.id)}
1478
1485
  `;
1479
1486
  }
1480
1487
  if (message.event) {
1481
- result += `event: ${message.event}
1488
+ result += `event: ${_sanitizeSingleLine(message.event)}
1482
1489
  `;
1483
1490
  }
1484
1491
  if (typeof message.retry === "number" && Number.isInteger(message.retry)) {
1485
1492
  result += `retry: ${message.retry}
1486
1493
  `;
1487
1494
  }
1488
- result += `data: ${message.data}
1489
-
1495
+ const data = typeof message.data === "string" ? message.data : "";
1496
+ for (const line of data.split("\n")) {
1497
+ result += `data: ${line}
1490
1498
  `;
1499
+ }
1500
+ result += "\n";
1491
1501
  return result;
1492
1502
  }
1503
+ function _sanitizeSingleLine(value) {
1504
+ return value.replace(/[\n\r]/g, "");
1505
+ }
1493
1506
  function formatEventStreamMessages(messages) {
1494
1507
  let result = "";
1495
1508
  for (const msg of messages) {
@@ -1654,6 +1667,12 @@ async function serveStatic(event, options) {
1654
1667
  const originalId = decodePath(
1655
1668
  withLeadingSlash(withoutTrailingSlash(parseURL(event.path).pathname))
1656
1669
  );
1670
+ if (originalId.includes("..")) {
1671
+ if (!options.fallthrough) {
1672
+ throw createError({ statusCode: 404 });
1673
+ }
1674
+ return false;
1675
+ }
1657
1676
  const acceptEncodings = parseAcceptEncoding(
1658
1677
  getRequestHeader(event, "accept-encoding"),
1659
1678
  options.encodings
package/package.json CHANGED
@@ -1,10 +1,16 @@
1
1
  {
2
2
  "name": "h3",
3
- "version": "1.15.4",
3
+ "version": "1.15.6",
4
4
  "description": "Minimal H(TTP) framework built for high performance and portability.",
5
- "repository": "h3js/h3",
6
5
  "license": "MIT",
6
+ "repository": "h3js/h3",
7
+ "files": [
8
+ "dist"
9
+ ],
7
10
  "sideEffects": false,
11
+ "main": "./dist/index.cjs",
12
+ "module": "./dist/index.mjs",
13
+ "types": "./dist/index.d.ts",
8
14
  "exports": {
9
15
  "./package.json": "./package.json",
10
16
  ".": {
@@ -13,24 +19,17 @@
13
19
  "require": "./dist/index.cjs"
14
20
  }
15
21
  },
16
- "main": "./dist/index.cjs",
17
- "module": "./dist/index.mjs",
18
- "types": "./dist/index.d.ts",
19
- "files": [
20
- "dist"
21
- ],
22
22
  "scripts": {
23
23
  "build": "unbuild",
24
24
  "dev": "vitest",
25
25
  "lint": "eslint --cache . && prettier -c src test playground examples docs",
26
26
  "lint:fix": "eslint --cache . --fix && prettier -c src test playground examples docs -w",
27
+ "prepack": "pnpm build",
27
28
  "play": "listhen -w ./playground/app.ts",
28
29
  "profile": "0x -o -D .profile -P 'autocannon -c 100 -p 10 -d 40 http://localhost:$PORT' ./playground/server.cjs",
29
- "release": "pnpm test && pnpm build && changelogen --release --publish --publishTag latest && git push --follow-tags",
30
- "test": "pnpm lint && vitest --run --coverage"
31
- },
32
- "resolutions": {
33
- "h3": "^1.14.0"
30
+ "release": "pnpm test && pnpm build && changelogen --release --publish --publishTag 1x && git push --follow-tags",
31
+ "test": "pnpm lint && vitest --run --coverage && pnpm test:types",
32
+ "test:types": "tsgo --noEmit"
34
33
  },
35
34
  "dependencies": {
36
35
  "cookie-es": "^1.2.2",
@@ -38,38 +37,42 @@
38
37
  "defu": "^6.1.4",
39
38
  "destr": "^2.0.5",
40
39
  "iron-webcrypto": "^1.2.1",
41
- "node-mock-http": "^1.0.2",
40
+ "node-mock-http": "^1.0.4",
42
41
  "radix3": "^1.1.2",
43
- "ufo": "^1.6.1",
42
+ "ufo": "^1.6.3",
44
43
  "uncrypto": "^0.1.3"
45
44
  },
46
45
  "devDependencies": {
47
46
  "0x": "^6.0.0",
48
- "@types/express": "^5.0.3",
49
- "@types/node": "^24.1.0",
50
- "@types/supertest": "^6.0.3",
51
- "@vitest/coverage-v8": "^3.2.4",
47
+ "@types/express": "^5.0.6",
48
+ "@types/node": "^25.3.5",
49
+ "@types/supertest": "^7.2.0",
50
+ "@typescript/native-preview": "latest",
51
+ "@vitest/coverage-v8": "^4.0.18",
52
52
  "autocannon": "^8.0.0",
53
- "automd": "^0.4.0",
53
+ "automd": "^0.4.3",
54
54
  "changelogen": "^0.6.2",
55
55
  "connect": "^3.7.0",
56
- "eslint": "^9.32.0",
57
- "eslint-config-unjs": "^0.5.0",
58
- "express": "^5.1.0",
56
+ "eslint": "^10.0.3",
57
+ "eslint-config-unjs": "^0.6.2",
58
+ "express": "^5.2.1",
59
59
  "get-port": "^7.1.0",
60
- "h3": "^1.15.3",
61
- "jiti": "^2.5.1",
60
+ "h3": "^1.15.5",
61
+ "jiti": "^2.6.1",
62
62
  "listhen": "^1.9.0",
63
- "node-fetch-native": "^1.6.6",
64
- "prettier": "^3.6.2",
65
- "react": "^19.1.1",
66
- "react-dom": "^19.1.1",
67
- "supertest": "^7.1.4",
68
- "typescript": "^5.8.3",
69
- "unbuild": "^3.6.0",
70
- "undici": "^7.12.0",
71
- "vitest": "^3.2.4",
72
- "zod": "^4.0.14"
63
+ "node-fetch-native": "^1.6.7",
64
+ "prettier": "^3.8.1",
65
+ "react": "^19.2.4",
66
+ "react-dom": "^19.2.4",
67
+ "supertest": "^7.2.2",
68
+ "typescript": "^5.9.3",
69
+ "unbuild": "^3.6.1",
70
+ "undici": "^7.22.0",
71
+ "vitest": "^4.0.18",
72
+ "zod": "^4.3.6"
73
+ },
74
+ "resolutions": {
75
+ "h3": "^1.14.0"
73
76
  },
74
- "packageManager": "pnpm@10.2.0"
77
+ "packageManager": "pnpm@10.28.0"
75
78
  }