fix: HTTP query string params array parsing (#38222)

pull/38184/head^2
Diego Sampaio 3 months ago committed by GitHub
parent 8ecbbaf981
commit 2b7dcc6fd5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 13
      packages/http-router/src/Router.ts
  2. 60
      packages/http-router/src/parseQueryParams.test.ts
  3. 5
      packages/http-router/src/parseQueryParams.ts

@ -5,10 +5,10 @@ import express from 'express';
import type { Context, HonoRequest, MiddlewareHandler } from 'hono';
import { Hono } from 'hono';
import type { StatusCode } from 'hono/utils/http-status';
import qs from 'qs'; // Using qs specifically to keep express compatibility
import type { ResponseSchema, TypedOptions } from './definition';
import { honoAdapterForExpress } from './middlewares/honoAdapterForExpress';
import { parseQueryParams } from './parseQueryParams';
const logger = new Logger('HttpRouter');
@ -186,7 +186,7 @@ export class Router<
}
protected parseQueryParams(request: HonoRequest) {
return qs.parse(request.raw.url.split('?')?.[1] || '');
return parseQueryParams(request.raw.url.split('?')?.[1] || '');
}
protected method<TSubPathPattern extends string, TOptions extends TypedOptions>(
@ -201,7 +201,14 @@ export class Router<
this.innerRouter[method.toLowerCase() as Lowercase<Method>](`/${subpath}`.replace('//', '/'), ...middlewares, async (c) => {
const { req, res } = c;
const queryParams = this.parseQueryParams(req);
let queryParams: Record<string, any>;
try {
queryParams = this.parseQueryParams(req);
} catch (e) {
logger.warn({ msg: 'Error parsing query params for request', path: req.path, err: e });
return c.json({ success: false, error: 'Invalid query parameters' }, 400);
}
if (options.query) {
const validatorFn = options.query;

@ -0,0 +1,60 @@
import { parseQueryParams } from './parseQueryParams';
describe('parseQueryParams', () => {
it('should parse simple query string', () => {
const result = parseQueryParams('foo=bar');
expect(result).toEqual({ foo: 'bar' });
});
it('should parse multiple query parameters', () => {
const result = parseQueryParams('foo=bar&baz=qux');
expect(result).toEqual({ foo: 'bar', baz: 'qux' });
});
it('should parse array parameters', () => {
const result = parseQueryParams('ids[]=1&ids[]=2&ids[]=3');
expect(result).toEqual({ ids: ['1', '2', '3'] });
});
it('should parse nested objects', () => {
const result = parseQueryParams('user[name]=john&user[age]=30');
expect(result).toEqual({ user: { name: 'john', age: '30' } });
});
it('should handle empty query string', () => {
const result = parseQueryParams('');
expect(result).toEqual({});
});
it('should decode URL encoded values', () => {
const result = parseQueryParams('name=John%20Doe');
expect(result).toEqual({ name: 'John Doe' });
});
it('should handle boolean-like values as strings', () => {
const result = parseQueryParams('active=true&disabled=false');
expect(result).toEqual({ active: 'true', disabled: 'false' });
});
it('should throw error when array limit is exceeded', () => {
const largeArray = Array(501)
.fill(0)
.map((_, i) => `ids[]=${i}`)
.join('&');
expect(() => parseQueryParams(largeArray)).toThrow();
});
it('should parse arrays within the limit', () => {
const array = Array(500)
.fill(0)
.map((_, i) => `ids[]=${i}`)
.join('&');
const result = parseQueryParams(array);
expect(result.ids).toHaveLength(500);
});
it('should parse as array even without brackets', () => {
const result = parseQueryParams('ids=1&ids=2');
expect(result.ids).toHaveLength(2);
});
});

@ -0,0 +1,5 @@
import qs from 'qs'; // Using qs specifically to keep express compatibility
export function parseQueryParams(url: string) {
return qs.parse(url, { arrayLimit: 500, throwOnLimitExceeded: true });
}
Loading…
Cancel
Save