hono 4.12.3 → 4.12.5

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.
@@ -137,7 +137,7 @@ var EventProcessor = class {
137
137
  }
138
138
  return result;
139
139
  }
140
- setCookies(event, res, result) {
140
+ setCookies(_event, res, result) {
141
141
  if (res.headers.has("set-cookie")) {
142
142
  const cookies = res.headers.getSetCookie ? res.headers.getSetCookie() : Array.from(res.headers.entries()).filter(([k]) => k === "set-cookie").map(([, v]) => v);
143
143
  if (Array.isArray(cookies)) {
@@ -168,7 +168,7 @@ class EventProcessor {
168
168
  }
169
169
  return result;
170
170
  }
171
- setCookies(event, res, result) {
171
+ setCookies(_event, res, result) {
172
172
  if (res.headers.has("set-cookie")) {
173
173
  const cookies = res.headers.getSetCookie ? res.headers.getSetCookie() : Array.from(res.headers.entries()).filter(([k]) => k === "set-cookie").map(([, v]) => v);
174
174
  if (Array.isArray(cookies)) {
@@ -34,6 +34,11 @@ class SSEStreamingApi extends import_stream.StreamingApi {
34
34
  const dataLines = data.split(/\r\n|\r|\n/).map((line) => {
35
35
  return `data: ${line}`;
36
36
  }).join("\n");
37
+ for (const key of ["event", "id", "retry"]) {
38
+ if (message[key] && /[\r\n]/.test(message[key])) {
39
+ throw new Error(`${key} must not contain "\\r" or "\\n"`);
40
+ }
41
+ }
37
42
  const sseData = [
38
43
  message.event && `event: ${message.event}`,
39
44
  dataLines,
@@ -113,6 +113,7 @@ d.replaceWith(c.content)
113
113
  Suspense[import_constants.DOM_RENDERER] = import_components2.Suspense;
114
114
  const textEncoder = new TextEncoder();
115
115
  const renderToReadableStream = (content, onError = console.trace) => {
116
+ let cancelled = false;
116
117
  const reader = new ReadableStream({
117
118
  async start(controller) {
118
119
  try {
@@ -126,7 +127,9 @@ const renderToReadableStream = (content, onError = console.trace) => {
126
127
  true,
127
128
  context
128
129
  );
129
- controller.enqueue(textEncoder.encode(resolved));
130
+ if (!cancelled) {
131
+ controller.enqueue(textEncoder.encode(resolved));
132
+ }
130
133
  let resolvedCount = 0;
131
134
  const callbacks = [];
132
135
  const then = (promise) => {
@@ -144,7 +147,9 @@ const renderToReadableStream = (content, onError = console.trace) => {
144
147
  );
145
148
  res.callbacks?.map((c) => c({ phase: import_html2.HtmlEscapedCallbackPhase.Stream, context })).filter(Boolean).forEach(then);
146
149
  resolvedCount++;
147
- controller.enqueue(textEncoder.encode(res));
150
+ if (!cancelled) {
151
+ controller.enqueue(textEncoder.encode(res));
152
+ }
148
153
  })
149
154
  );
150
155
  };
@@ -155,7 +160,12 @@ const renderToReadableStream = (content, onError = console.trace) => {
155
160
  } catch (e) {
156
161
  onError(e);
157
162
  }
158
- controller.close();
163
+ if (!cancelled) {
164
+ controller.close();
165
+ }
166
+ },
167
+ cancel() {
168
+ cancelled = true;
159
169
  }
160
170
  });
161
171
  return reader;
@@ -23,6 +23,7 @@ __export(serve_static_exports, {
23
23
  module.exports = __toCommonJS(serve_static_exports);
24
24
  var import_compress = require("../../utils/compress");
25
25
  var import_mime = require("../../utils/mime");
26
+ var import_url = require("../../utils/url");
26
27
  var import_path = require("./path");
27
28
  const ENCODINGS = {
28
29
  br: ".br",
@@ -44,7 +45,7 @@ const serveStatic = (options) => {
44
45
  filename = options.path;
45
46
  } else {
46
47
  try {
47
- filename = decodeURIComponent(c.req.path);
48
+ filename = (0, import_url.tryDecodeURI)(c.req.path);
48
49
  if (/(?:^|[\/\\])\.\.(?:$|[\/\\])/.test(filename)) {
49
50
  throw new Error();
50
51
  }
@@ -112,6 +112,11 @@ const _serialize = (name, value, opt = {}) => {
112
112
  throw new Error("__Host- Cookie must not have Domain attributes");
113
113
  }
114
114
  }
115
+ for (const key of ["domain", "path"]) {
116
+ if (opt[key] && /[;\r\n]/.test(opt[key])) {
117
+ throw new Error(`${key} must not contain ";", "\\r", or "\\n"`);
118
+ }
119
+ }
115
120
  if (opt && typeof opt.maxAge === "number" && opt.maxAge >= 0) {
116
121
  if (opt.maxAge > 3456e4) {
117
122
  throw new Error(
@@ -177,10 +177,13 @@ const verifyWithJwks = async (token, options, init) => {
177
177
  });
178
178
  };
179
179
  const decode = (token) => {
180
+ const parts = token.split(".");
181
+ if (parts.length !== 3) {
182
+ throw new import_types.JwtTokenInvalid(token);
183
+ }
180
184
  try {
181
- const [h, p] = token.split(".");
182
- const header = decodeJwtPart(h);
183
- const payload = decodeJwtPart(p);
185
+ const header = decodeJwtPart(parts[0]);
186
+ const payload = decodeJwtPart(parts[1]);
184
187
  return {
185
188
  header,
186
189
  payload
@@ -190,9 +193,12 @@ const decode = (token) => {
190
193
  }
191
194
  };
192
195
  const decodeHeader = (token) => {
196
+ const parts = token.split(".");
197
+ if (parts.length !== 3) {
198
+ throw new import_types.JwtTokenInvalid(token);
199
+ }
193
200
  try {
194
- const [h] = token.split(".");
195
- return decodeJwtPart(h);
201
+ return decodeJwtPart(parts[0]);
196
202
  } catch {
197
203
  throw new import_types.JwtTokenInvalid(token);
198
204
  }
@@ -29,7 +29,8 @@ __export(url_exports, {
29
29
  mergePath: () => mergePath,
30
30
  splitPath: () => splitPath,
31
31
  splitRoutingPath: () => splitRoutingPath,
32
- tryDecode: () => tryDecode
32
+ tryDecode: () => tryDecode,
33
+ tryDecodeURI: () => tryDecodeURI
33
34
  });
34
35
  module.exports = __toCommonJS(url_exports);
35
36
  const splitPath = (path) => {
@@ -251,5 +252,6 @@ const decodeURIComponent_ = decodeURIComponent;
251
252
  mergePath,
252
253
  splitPath,
253
254
  splitRoutingPath,
254
- tryDecode
255
+ tryDecode,
256
+ tryDecodeURI
255
257
  });
@@ -11,6 +11,11 @@ var SSEStreamingApi = class extends StreamingApi {
11
11
  const dataLines = data.split(/\r\n|\r|\n/).map((line) => {
12
12
  return `data: ${line}`;
13
13
  }).join("\n");
14
+ for (const key of ["event", "id", "retry"]) {
15
+ if (message[key] && /[\r\n]/.test(message[key])) {
16
+ throw new Error(`${key} must not contain "\\r" or "\\n"`);
17
+ }
18
+ }
14
19
  const sseData = [
15
20
  message.event && `event: ${message.event}`,
16
21
  dataLines,
@@ -89,6 +89,7 @@ d.replaceWith(c.content)
89
89
  Suspense[DOM_RENDERER] = SuspenseDomRenderer;
90
90
  var textEncoder = new TextEncoder();
91
91
  var renderToReadableStream = (content, onError = console.trace) => {
92
+ let cancelled = false;
92
93
  const reader = new ReadableStream({
93
94
  async start(controller) {
94
95
  try {
@@ -102,7 +103,9 @@ var renderToReadableStream = (content, onError = console.trace) => {
102
103
  true,
103
104
  context
104
105
  );
105
- controller.enqueue(textEncoder.encode(resolved));
106
+ if (!cancelled) {
107
+ controller.enqueue(textEncoder.encode(resolved));
108
+ }
106
109
  let resolvedCount = 0;
107
110
  const callbacks = [];
108
111
  const then = (promise) => {
@@ -120,7 +123,9 @@ var renderToReadableStream = (content, onError = console.trace) => {
120
123
  );
121
124
  res.callbacks?.map((c) => c({ phase: HtmlEscapedCallbackPhase.Stream, context })).filter(Boolean).forEach(then);
122
125
  resolvedCount++;
123
- controller.enqueue(textEncoder.encode(res));
126
+ if (!cancelled) {
127
+ controller.enqueue(textEncoder.encode(res));
128
+ }
124
129
  })
125
130
  );
126
131
  };
@@ -131,7 +136,12 @@ var renderToReadableStream = (content, onError = console.trace) => {
131
136
  } catch (e) {
132
137
  onError(e);
133
138
  }
134
- controller.close();
139
+ if (!cancelled) {
140
+ controller.close();
141
+ }
142
+ },
143
+ cancel() {
144
+ cancelled = true;
135
145
  }
136
146
  });
137
147
  return reader;
@@ -1,6 +1,7 @@
1
1
  // src/middleware/serve-static/index.ts
2
2
  import { COMPRESSIBLE_CONTENT_TYPE_REGEX } from "../../utils/compress.js";
3
3
  import { getMimeType } from "../../utils/mime.js";
4
+ import { tryDecodeURI } from "../../utils/url.js";
4
5
  import { defaultJoin } from "./path.js";
5
6
  var ENCODINGS = {
6
7
  br: ".br",
@@ -22,7 +23,7 @@ var serveStatic = (options) => {
22
23
  filename = options.path;
23
24
  } else {
24
25
  try {
25
- filename = decodeURIComponent(c.req.path);
26
+ filename = tryDecodeURI(c.req.path);
26
27
  if (/(?:^|[\/\\])\.\.(?:$|[\/\\])/.test(filename)) {
27
28
  throw new Error();
28
29
  }
@@ -138,7 +138,7 @@ export declare abstract class EventProcessor<E extends LambdaEvent> {
138
138
  protected getDomainName(event: E): string | undefined;
139
139
  createRequest(event: E): Request;
140
140
  createResult(event: E, res: Response, options: Pick<HandleOptions, 'isContentTypeBinary'>): Promise<APIGatewayProxyResult>;
141
- setCookies(event: E, res: Response, result: APIGatewayProxyResult): void;
141
+ setCookies(_event: E, res: Response, result: APIGatewayProxyResult): void;
142
142
  }
143
143
  export declare class EventV2Processor extends EventProcessor<APIGatewayProxyEventV2> {
144
144
  protected getPath(event: APIGatewayProxyEventV2): string;
@@ -1,7 +1,7 @@
1
1
  import type { Hono } from '../hono';
2
2
  import type { HonoBase } from '../hono-base';
3
3
  import type { METHODS, METHOD_NAME_ALL_LOWERCASE } from '../router';
4
- import type { Endpoint, KnownResponseFormat, ResponseFormat, Schema } from '../types';
4
+ import type { Endpoint, ExtractSchema, KnownResponseFormat, ResponseFormat, Schema } from '../types';
5
5
  import type { StatusCode, SuccessStatusCode } from '../utils/http-status';
6
6
  import type { HasRequiredKeys } from '../utils/types';
7
7
  /**
@@ -205,5 +205,5 @@ type ModSchema<D, Def extends GlobalResponseDefinition> = {
205
205
  [M in keyof D[K]]: ModRoute<D[K][M], Def>;
206
206
  };
207
207
  };
208
- export type ApplyGlobalResponse<App, Def extends GlobalResponseDefinition> = App extends HonoBase<infer E, infer D extends Schema, infer B> ? Hono<E, ModSchema<D, Def> extends Schema ? ModSchema<D, Def> : never, B> : never;
208
+ export type ApplyGlobalResponse<App, Def extends GlobalResponseDefinition> = App extends HonoBase<infer E, infer _ extends Schema, infer B> ? ModSchema<ExtractSchema<App>, Def> extends infer S extends Schema ? Hono<E, S, B> : never : never;
209
209
  export {};
@@ -59,7 +59,7 @@ export declare class HonoRequest<P extends string = '/', I extends Input['out']
59
59
  * const { id, comment_id } = c.req.param()
60
60
  * ```
61
61
  */
62
- param<P2 extends ParamKeys<P> = ParamKeys<P>>(key: P2 extends `${infer _}?` ? never : P2): string;
62
+ param<P2 extends ParamKeys<P> = ParamKeys<P>>(key: string extends P ? never : P2 extends `${infer _}?` ? never : P2): string;
63
63
  param<P2 extends RemoveQuestion<ParamKeys<P>> = RemoveQuestion<ParamKeys<P>>>(key: P2): string | undefined;
64
64
  param(key: string): string | undefined;
65
65
  param<P2 extends string = P>(): Simplify<UnionToIntersection<ParamKeyToRecord<ParamKeys<P2>>>>;
@@ -8,6 +8,16 @@ export declare const splitRoutingPath: (routePath: string) => string[];
8
8
  export declare const getPattern: (label: string, next?: string) => Pattern | null;
9
9
  type Decoder = (str: string) => string;
10
10
  export declare const tryDecode: (str: string, decoder: Decoder) => string;
11
+ /**
12
+ * Try to apply decodeURI() to given string.
13
+ * If it fails, skip invalid percent encoding or invalid UTF-8 sequences, and apply decodeURI() to the rest as much as possible.
14
+ * @param str The string to decode.
15
+ * @returns The decoded string that sometimes contains undecodable percent encoding.
16
+ * @example
17
+ * tryDecodeURI('Hello%20World') // 'Hello World'
18
+ * tryDecodeURI('Hello%20World/%A4%A2') // 'Hello World/%A4%A2'
19
+ */
20
+ export declare const tryDecodeURI: (str: string) => string;
11
21
  export declare const getPath: (request: Request) => string;
12
22
  export declare const getQueryStrings: (url: string) => string;
13
23
  export declare const getPathNoStrict: (request: Request) => string;
@@ -87,6 +87,11 @@ var _serialize = (name, value, opt = {}) => {
87
87
  throw new Error("__Host- Cookie must not have Domain attributes");
88
88
  }
89
89
  }
90
+ for (const key of ["domain", "path"]) {
91
+ if (opt[key] && /[;\r\n]/.test(opt[key])) {
92
+ throw new Error(`${key} must not contain ";", "\\r", or "\\n"`);
93
+ }
94
+ }
90
95
  if (opt && typeof opt.maxAge === "number" && opt.maxAge >= 0) {
91
96
  if (opt.maxAge > 3456e4) {
92
97
  throw new Error(
@@ -165,10 +165,13 @@ var verifyWithJwks = async (token, options, init) => {
165
165
  });
166
166
  };
167
167
  var decode = (token) => {
168
+ const parts = token.split(".");
169
+ if (parts.length !== 3) {
170
+ throw new JwtTokenInvalid(token);
171
+ }
168
172
  try {
169
- const [h, p] = token.split(".");
170
- const header = decodeJwtPart(h);
171
- const payload = decodeJwtPart(p);
173
+ const header = decodeJwtPart(parts[0]);
174
+ const payload = decodeJwtPart(parts[1]);
172
175
  return {
173
176
  header,
174
177
  payload
@@ -178,9 +181,12 @@ var decode = (token) => {
178
181
  }
179
182
  };
180
183
  var decodeHeader = (token) => {
184
+ const parts = token.split(".");
185
+ if (parts.length !== 3) {
186
+ throw new JwtTokenInvalid(token);
187
+ }
181
188
  try {
182
- const [h] = token.split(".");
183
- return decodeJwtPart(h);
189
+ return decodeJwtPart(parts[0]);
184
190
  } catch {
185
191
  throw new JwtTokenInvalid(token);
186
192
  }
package/dist/utils/url.js CHANGED
@@ -217,5 +217,6 @@ export {
217
217
  mergePath,
218
218
  splitPath,
219
219
  splitRoutingPath,
220
- tryDecode
220
+ tryDecode,
221
+ tryDecodeURI
221
222
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "4.12.3",
3
+ "version": "4.12.5",
4
4
  "description": "Web framework built on Web Standards",
5
5
  "main": "dist/cjs/index.js",
6
6
  "type": "module",
@@ -656,7 +656,7 @@
656
656
  "nodejs"
657
657
  ],
658
658
  "devDependencies": {
659
- "@hono/eslint-config": "^2.0.5",
659
+ "@hono/eslint-config": "^2.1.0",
660
660
  "@hono/node-server": "^1.13.5",
661
661
  "@types/glob": "^9.0.0",
662
662
  "@types/jsdom": "^21.1.7",
@@ -667,7 +667,7 @@
667
667
  "bun-types": "^1.2.20",
668
668
  "editorconfig-checker": "6.1.1",
669
669
  "esbuild": "^0.27.1",
670
- "eslint": "9.39.1",
670
+ "eslint": "^9.39.3",
671
671
  "glob": "^11.0.0",
672
672
  "jsdom": "22.1.0",
673
673
  "msw": "^2.6.0",