h3 1.6.0 → 1.6.2

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/dist/index.cjs CHANGED
@@ -108,10 +108,10 @@ class H3Error extends Error {
108
108
  toJSON() {
109
109
  const obj = {
110
110
  message: this.message,
111
- statusCode: this.statusCode
111
+ statusCode: sanitizeStatusCode(this.statusCode, 500)
112
112
  };
113
113
  if (this.statusMessage) {
114
- obj.statusMessage = this.statusMessage;
114
+ obj.statusMessage = sanitizeStatusMessage(this.statusMessage);
115
115
  }
116
116
  if (this.data !== void 0) {
117
117
  obj.data = this.data;
@@ -150,15 +150,24 @@ function createError(input) {
150
150
  err.data = input.data;
151
151
  }
152
152
  if (input.statusCode) {
153
- err.statusCode = input.statusCode;
153
+ err.statusCode = sanitizeStatusCode(input.statusCode, err.statusCode);
154
154
  } else if (input.status) {
155
- err.statusCode = input.status;
155
+ err.statusCode = sanitizeStatusCode(input.status, err.statusCode);
156
156
  }
157
157
  if (input.statusMessage) {
158
158
  err.statusMessage = input.statusMessage;
159
159
  } else if (input.statusText) {
160
160
  err.statusMessage = input.statusText;
161
161
  }
162
+ if (err.statusMessage) {
163
+ const originalMessage = err.statusMessage;
164
+ const sanitizedMessage = sanitizeStatusMessage(err.statusMessage);
165
+ if (sanitizedMessage !== originalMessage) {
166
+ console.warn(
167
+ "[h3] Please prefer using `message` for longer error messages instead of `statusMessage`. In the future `statusMessage` will be sanitized by default."
168
+ );
169
+ }
170
+ }
162
171
  if (input.fatal !== void 0) {
163
172
  err.fatal = input.fatal;
164
173
  }
@@ -185,12 +194,7 @@ function sendError(event, error, debug) {
185
194
  return;
186
195
  }
187
196
  const _code = Number.parseInt(h3Error.statusCode);
188
- if (_code) {
189
- event.node.res.statusCode = _code;
190
- }
191
- if (h3Error.statusMessage) {
192
- event.node.res.statusMessage = h3Error.statusMessage;
193
- }
197
+ setResponseStatus(event, _code, h3Error.statusMessage);
194
198
  event.node.res.setHeader("content-type", MIMES.json);
195
199
  event.node.res.end(JSON.stringify(responseBody, void 0, 2));
196
200
  }
@@ -451,6 +455,23 @@ function splitCookiesString(cookiesString) {
451
455
  return cookiesStrings;
452
456
  }
453
457
 
458
+ const DISALLOWED_STATUS_CHARS = /[^\u0009\u0020-\u007E]/g;
459
+ function sanitizeStatusMessage(statusMessage = "") {
460
+ return statusMessage.replace(DISALLOWED_STATUS_CHARS, "");
461
+ }
462
+ function sanitizeStatusCode(statusCode, defaultStatusCode = 200) {
463
+ if (!statusCode) {
464
+ return defaultStatusCode;
465
+ }
466
+ if (typeof statusCode === "string") {
467
+ statusCode = Number.parseInt(statusCode, 10);
468
+ }
469
+ if (statusCode < 100 || statusCode > 999) {
470
+ return defaultStatusCode;
471
+ }
472
+ return statusCode;
473
+ }
474
+
454
475
  const PayloadMethods = /* @__PURE__ */ new Set(["PATCH", "POST", "PUT", "DELETE"]);
455
476
  const ignoredHeaders = /* @__PURE__ */ new Set([
456
477
  "transfer-encoding",
@@ -488,8 +509,11 @@ async function sendProxy(event, target, opts = {}) {
488
509
  headers: opts.headers,
489
510
  ...opts.fetchOptions
490
511
  });
491
- event.node.res.statusCode = response.status;
492
- event.node.res.statusMessage = response.statusText;
512
+ event.node.res.statusCode = sanitizeStatusCode(
513
+ response.status,
514
+ event.node.res.statusCode
515
+ );
516
+ event.node.res.statusMessage = sanitizeStatusMessage(response.statusText);
493
517
  for (const [key, value] of response.headers.entries()) {
494
518
  if (key === "content-encoding") {
495
519
  continue;
@@ -594,16 +618,21 @@ function send(event, data, type) {
594
618
  });
595
619
  }
596
620
  function sendNoContent(event, code = 204) {
597
- event.node.res.statusCode = code;
621
+ event.node.res.statusCode = sanitizeStatusCode(code, 204);
598
622
  if (event.node.res.statusCode === 204) {
599
623
  event.node.res.removeHeader("content-length");
600
624
  }
601
625
  event.node.res.end();
602
626
  }
603
627
  function setResponseStatus(event, code, text) {
604
- event.node.res.statusCode = code;
628
+ if (code) {
629
+ event.node.res.statusCode = sanitizeStatusCode(
630
+ code,
631
+ event.node.res.statusCode
632
+ );
633
+ }
605
634
  if (text) {
606
- event.node.res.statusMessage = text;
635
+ event.node.res.statusMessage = sanitizeStatusMessage(text);
607
636
  }
608
637
  }
609
638
  function getResponseStatus(event) {
@@ -618,7 +647,10 @@ function defaultContentType(event, type) {
618
647
  }
619
648
  }
620
649
  function sendRedirect(event, location, code = 302) {
621
- event.node.res.statusCode = code;
650
+ event.node.res.statusCode = sanitizeStatusCode(
651
+ code,
652
+ event.node.res.statusCode
653
+ );
622
654
  event.node.res.setHeader("location", location);
623
655
  const encodedLoc = location.replace(/"/g, "%22");
624
656
  const html = `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${encodedLoc}"></head></html>`;
@@ -1069,10 +1101,13 @@ class H3Event {
1069
1101
  this.res.setHeader(key, value);
1070
1102
  }
1071
1103
  if (response.status) {
1072
- this.res.statusCode = response.status;
1104
+ this.res.statusCode = sanitizeStatusCode(
1105
+ response.status,
1106
+ this.res.statusCode
1107
+ );
1073
1108
  }
1074
1109
  if (response.statusText) {
1075
- this.res.statusMessage = response.statusText;
1110
+ this.res.statusMessage = sanitizeStatusMessage(response.statusText);
1076
1111
  }
1077
1112
  if (response.redirected) {
1078
1113
  this.res.setHeader("location", response.url);
@@ -1464,6 +1499,8 @@ exports.proxyRequest = proxyRequest;
1464
1499
  exports.readBody = readBody;
1465
1500
  exports.readMultipartFormData = readMultipartFormData;
1466
1501
  exports.readRawBody = readRawBody;
1502
+ exports.sanitizeStatusCode = sanitizeStatusCode;
1503
+ exports.sanitizeStatusMessage = sanitizeStatusMessage;
1467
1504
  exports.sealSession = sealSession;
1468
1505
  exports.send = send;
1469
1506
  exports.sendError = sendError;
package/dist/index.d.ts CHANGED
@@ -346,7 +346,7 @@ declare function send(event: H3Event, data?: any, type?: string): Promise<void>;
346
346
  * @param code status code to be send. By default, it is `204 No Content`.
347
347
  */
348
348
  declare function sendNoContent(event: H3Event, code?: number): void;
349
- declare function setResponseStatus(event: H3Event, code: number, text?: string): void;
349
+ declare function setResponseStatus(event: H3Event, code?: number, text?: string): void;
350
350
  declare function getResponseStatus(event: H3Event): number;
351
351
  declare function getResponseStatusText(event: H3Event): string;
352
352
  declare function defaultContentType(event: H3Event, type?: string): void;
@@ -384,6 +384,9 @@ declare function isCorsOriginAllowed(origin: ReturnType<typeof getRequestHeaders
384
384
  declare function appendCorsPreflightHeaders(event: H3Event, options: H3CorsOptions): void;
385
385
  declare function appendCorsHeaders(event: H3Event, options: H3CorsOptions): void;
386
386
 
387
+ declare function sanitizeStatusMessage(statusMessage?: string): string;
388
+ declare function sanitizeStatusCode(statusCode: string | number, defaultStatusCode?: number): number;
389
+
387
390
  type RouterMethod = Lowercase<HTTPMethod>;
388
391
  type RouterUse = (path: string, handler: EventHandler, method?: RouterMethod | RouterMethod[]) => Router;
389
392
  type AddRouteShortcuts = Record<RouterMethod, RouterUse>;
@@ -399,4 +402,4 @@ interface CreateRouterOptions {
399
402
  }
400
403
  declare function createRouter(opts?: CreateRouterOptions): Router;
401
404
 
402
- export { AddRouteShortcuts, App, AppOptions, AppUse, CacheConditions, CreateRouterOptions, DynamicEventHandler, Encoding, EventHandler, EventHandlerResponse, H3CorsOptions, H3Error, H3Event, H3EventContext, H3Headers, H3Response, HTTPMethod, InputLayer, InputStack, Layer, LazyEventHandler, MIMES, Matcher, MultiPartData, NodeEventContext, NodeListener, NodeMiddleware, NodePromisifiedHandler, ProxyOptions, RequestHeaders, Router, RouterMethod, RouterUse, Session, SessionConfig, SessionData, Stack, appendCorsHeaders, appendCorsPreflightHeaders, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getRequestHost, getRequestProtocol, getRequestURL, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, handleCors, isCorsOriginAllowed, isError, isEvent, isEventHandler, isMethod, isPreflightRequest, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, splitCookiesString, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
405
+ export { AddRouteShortcuts, App, AppOptions, AppUse, CacheConditions, CreateRouterOptions, DynamicEventHandler, Encoding, EventHandler, EventHandlerResponse, H3CorsOptions, H3Error, H3Event, H3EventContext, H3Headers, H3Response, HTTPMethod, InputLayer, InputStack, Layer, LazyEventHandler, MIMES, Matcher, MultiPartData, NodeEventContext, NodeListener, NodeMiddleware, NodePromisifiedHandler, ProxyOptions, RequestHeaders, Router, RouterMethod, RouterUse, Session, SessionConfig, SessionData, Stack, appendCorsHeaders, appendCorsPreflightHeaders, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getRequestHost, getRequestProtocol, getRequestURL, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, handleCors, isCorsOriginAllowed, isError, isEvent, isEventHandler, isMethod, isPreflightRequest, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sanitizeStatusCode, sanitizeStatusMessage, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, splitCookiesString, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
package/dist/index.mjs CHANGED
@@ -106,10 +106,10 @@ class H3Error extends Error {
106
106
  toJSON() {
107
107
  const obj = {
108
108
  message: this.message,
109
- statusCode: this.statusCode
109
+ statusCode: sanitizeStatusCode(this.statusCode, 500)
110
110
  };
111
111
  if (this.statusMessage) {
112
- obj.statusMessage = this.statusMessage;
112
+ obj.statusMessage = sanitizeStatusMessage(this.statusMessage);
113
113
  }
114
114
  if (this.data !== void 0) {
115
115
  obj.data = this.data;
@@ -148,15 +148,24 @@ function createError(input) {
148
148
  err.data = input.data;
149
149
  }
150
150
  if (input.statusCode) {
151
- err.statusCode = input.statusCode;
151
+ err.statusCode = sanitizeStatusCode(input.statusCode, err.statusCode);
152
152
  } else if (input.status) {
153
- err.statusCode = input.status;
153
+ err.statusCode = sanitizeStatusCode(input.status, err.statusCode);
154
154
  }
155
155
  if (input.statusMessage) {
156
156
  err.statusMessage = input.statusMessage;
157
157
  } else if (input.statusText) {
158
158
  err.statusMessage = input.statusText;
159
159
  }
160
+ if (err.statusMessage) {
161
+ const originalMessage = err.statusMessage;
162
+ const sanitizedMessage = sanitizeStatusMessage(err.statusMessage);
163
+ if (sanitizedMessage !== originalMessage) {
164
+ console.warn(
165
+ "[h3] Please prefer using `message` for longer error messages instead of `statusMessage`. In the future `statusMessage` will be sanitized by default."
166
+ );
167
+ }
168
+ }
160
169
  if (input.fatal !== void 0) {
161
170
  err.fatal = input.fatal;
162
171
  }
@@ -183,12 +192,7 @@ function sendError(event, error, debug) {
183
192
  return;
184
193
  }
185
194
  const _code = Number.parseInt(h3Error.statusCode);
186
- if (_code) {
187
- event.node.res.statusCode = _code;
188
- }
189
- if (h3Error.statusMessage) {
190
- event.node.res.statusMessage = h3Error.statusMessage;
191
- }
195
+ setResponseStatus(event, _code, h3Error.statusMessage);
192
196
  event.node.res.setHeader("content-type", MIMES.json);
193
197
  event.node.res.end(JSON.stringify(responseBody, void 0, 2));
194
198
  }
@@ -449,6 +453,23 @@ function splitCookiesString(cookiesString) {
449
453
  return cookiesStrings;
450
454
  }
451
455
 
456
+ const DISALLOWED_STATUS_CHARS = /[^\u0009\u0020-\u007E]/g;
457
+ function sanitizeStatusMessage(statusMessage = "") {
458
+ return statusMessage.replace(DISALLOWED_STATUS_CHARS, "");
459
+ }
460
+ function sanitizeStatusCode(statusCode, defaultStatusCode = 200) {
461
+ if (!statusCode) {
462
+ return defaultStatusCode;
463
+ }
464
+ if (typeof statusCode === "string") {
465
+ statusCode = Number.parseInt(statusCode, 10);
466
+ }
467
+ if (statusCode < 100 || statusCode > 999) {
468
+ return defaultStatusCode;
469
+ }
470
+ return statusCode;
471
+ }
472
+
452
473
  const PayloadMethods = /* @__PURE__ */ new Set(["PATCH", "POST", "PUT", "DELETE"]);
453
474
  const ignoredHeaders = /* @__PURE__ */ new Set([
454
475
  "transfer-encoding",
@@ -486,8 +507,11 @@ async function sendProxy(event, target, opts = {}) {
486
507
  headers: opts.headers,
487
508
  ...opts.fetchOptions
488
509
  });
489
- event.node.res.statusCode = response.status;
490
- event.node.res.statusMessage = response.statusText;
510
+ event.node.res.statusCode = sanitizeStatusCode(
511
+ response.status,
512
+ event.node.res.statusCode
513
+ );
514
+ event.node.res.statusMessage = sanitizeStatusMessage(response.statusText);
491
515
  for (const [key, value] of response.headers.entries()) {
492
516
  if (key === "content-encoding") {
493
517
  continue;
@@ -592,16 +616,21 @@ function send(event, data, type) {
592
616
  });
593
617
  }
594
618
  function sendNoContent(event, code = 204) {
595
- event.node.res.statusCode = code;
619
+ event.node.res.statusCode = sanitizeStatusCode(code, 204);
596
620
  if (event.node.res.statusCode === 204) {
597
621
  event.node.res.removeHeader("content-length");
598
622
  }
599
623
  event.node.res.end();
600
624
  }
601
625
  function setResponseStatus(event, code, text) {
602
- event.node.res.statusCode = code;
626
+ if (code) {
627
+ event.node.res.statusCode = sanitizeStatusCode(
628
+ code,
629
+ event.node.res.statusCode
630
+ );
631
+ }
603
632
  if (text) {
604
- event.node.res.statusMessage = text;
633
+ event.node.res.statusMessage = sanitizeStatusMessage(text);
605
634
  }
606
635
  }
607
636
  function getResponseStatus(event) {
@@ -616,7 +645,10 @@ function defaultContentType(event, type) {
616
645
  }
617
646
  }
618
647
  function sendRedirect(event, location, code = 302) {
619
- event.node.res.statusCode = code;
648
+ event.node.res.statusCode = sanitizeStatusCode(
649
+ code,
650
+ event.node.res.statusCode
651
+ );
620
652
  event.node.res.setHeader("location", location);
621
653
  const encodedLoc = location.replace(/"/g, "%22");
622
654
  const html = `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${encodedLoc}"></head></html>`;
@@ -1067,10 +1099,13 @@ class H3Event {
1067
1099
  this.res.setHeader(key, value);
1068
1100
  }
1069
1101
  if (response.status) {
1070
- this.res.statusCode = response.status;
1102
+ this.res.statusCode = sanitizeStatusCode(
1103
+ response.status,
1104
+ this.res.statusCode
1105
+ );
1071
1106
  }
1072
1107
  if (response.statusText) {
1073
- this.res.statusMessage = response.statusText;
1108
+ this.res.statusMessage = sanitizeStatusMessage(response.statusText);
1074
1109
  }
1075
1110
  if (response.redirected) {
1076
1111
  this.res.setHeader("location", response.url);
@@ -1399,4 +1434,4 @@ function createRouter(opts = {}) {
1399
1434
  return router;
1400
1435
  }
1401
1436
 
1402
- export { H3Error, H3Event, H3Headers, H3Response, MIMES, appendCorsHeaders, appendCorsPreflightHeaders, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getRequestHost, getRequestProtocol, getRequestURL, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, handleCors, isCorsOriginAllowed, isError, isEvent, isEventHandler, isMethod, isPreflightRequest, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, splitCookiesString, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
1437
+ export { H3Error, H3Event, H3Headers, H3Response, MIMES, appendCorsHeaders, appendCorsPreflightHeaders, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getRequestHost, getRequestProtocol, getRequestURL, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, handleCors, isCorsOriginAllowed, isError, isEvent, isEventHandler, isMethod, isPreflightRequest, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sanitizeStatusCode, sanitizeStatusMessage, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, splitCookiesString, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "h3",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
4
4
  "description": "Tiny JavaScript Server",
5
5
  "repository": "unjs/h3",
6
6
  "license": "MIT",
@@ -23,7 +23,7 @@
23
23
  "cookie-es": "^0.5.0",
24
24
  "defu": "^6.1.2",
25
25
  "destr": "^1.2.2",
26
- "iron-webcrypto": "^0.5.0",
26
+ "iron-webcrypto": "^0.6.0",
27
27
  "radix3": "^1.0.0",
28
28
  "ufo": "^1.1.1",
29
29
  "uncrypto": "^0.1.2"
@@ -31,18 +31,18 @@
31
31
  "devDependencies": {
32
32
  "0x": "^5.5.0",
33
33
  "@types/express": "^4.17.17",
34
- "@types/node": "^18.15.0",
34
+ "@types/node": "^18.15.3",
35
35
  "@types/supertest": "^2.0.12",
36
36
  "@vitest/coverage-c8": "^0.29.2",
37
37
  "autocannon": "^7.10.0",
38
38
  "changelogen": "^0.5.1",
39
39
  "connect": "^3.7.0",
40
- "eslint": "^8.35.0",
40
+ "eslint": "^8.36.0",
41
41
  "eslint-config-unjs": "^0.1.0",
42
42
  "express": "^4.18.2",
43
43
  "get-port": "^6.1.2",
44
- "jiti": "^1.17.2",
45
- "listhen": "^1.0.3",
44
+ "jiti": "^1.18.2",
45
+ "listhen": "^1.0.4",
46
46
  "node-fetch-native": "^1.0.2",
47
47
  "prettier": "^2.8.4",
48
48
  "supertest": "^6.3.3",
@@ -50,7 +50,7 @@
50
50
  "unbuild": "^1.1.2",
51
51
  "vitest": "^0.29.2"
52
52
  },
53
- "packageManager": "pnpm@7.29.0",
53
+ "packageManager": "pnpm@7.29.3",
54
54
  "scripts": {
55
55
  "build": "unbuild",
56
56
  "dev": "vitest",