Skip to content

Commit 937f504

Browse files
fix(search): reject malformed torrent pagination params (#126)
Co-authored-by: rissrice2105-agent <rissrice2105-agent@users.noreply.github.com>
1 parent 213a9bc commit 937f504

2 files changed

Lines changed: 35 additions & 4 deletions

File tree

src/app/api/search/torrents/route.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,17 @@ describe('Torrent Search API', () => {
6767
expect(data.error).toBe('Invalid limit. Must be a number.');
6868
});
6969

70+
it('should return 400 for partial, fractional, or unsafe integer limits', async () => {
71+
for (const limit of ['10abc', '1.5', '9007199254740992']) {
72+
const request = createRequest({ q: 'test', limit });
73+
const response = await GET(request);
74+
75+
expect(response.status).toBe(400);
76+
const data = await response.json();
77+
expect(data.error).toBe('Invalid limit. Must be a number.');
78+
}
79+
});
80+
7081
it('should return 400 if limit exceeds maximum', async () => {
7182
const request = createRequest({ q: 'test', limit: '200' });
7283
const response = await GET(request);
@@ -94,6 +105,17 @@ describe('Torrent Search API', () => {
94105
expect(data.error).toBe('Invalid offset. Must be a number.');
95106
});
96107

108+
it('should return 400 for partial, fractional, or unsafe integer offsets', async () => {
109+
for (const offset of ['10abc', '1.5', '9007199254740992']) {
110+
const request = createRequest({ q: 'test', offset });
111+
const response = await GET(request);
112+
113+
expect(response.status).toBe(400);
114+
const data = await response.json();
115+
expect(data.error).toBe('Invalid offset. Must be a number.');
116+
}
117+
});
118+
97119
it('should return 400 for negative offset', async () => {
98120
const request = createRequest({ q: 'test', offset: '-1' });
99121
const response = await GET(request);

src/app/api/search/torrents/route.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ const MAX_LIMIT = 100;
3939
*/
4040
const DEFAULT_LIMIT = 50;
4141

42+
function parseStrictIntegerParam(value: string | null): number | null {
43+
if (value == null || !/^-?\d+$/.test(value)) {
44+
return null;
45+
}
46+
47+
const parsed = Number(value);
48+
return Number.isSafeInteger(parsed) ? parsed : null;
49+
}
50+
4251
/**
4352
* Extended search result with source
4453
*/
@@ -125,8 +134,8 @@ export async function GET(request: NextRequest): Promise<NextResponse<SearchResp
125134
const limitParam = searchParams.get('limit');
126135
let limit = DEFAULT_LIMIT;
127136
if (limitParam) {
128-
const parsedLimit = parseInt(limitParam, 10);
129-
if (isNaN(parsedLimit)) {
137+
const parsedLimit = parseStrictIntegerParam(limitParam);
138+
if (parsedLimit == null) {
130139
return NextResponse.json(
131140
{ error: 'Invalid limit. Must be a number.' },
132141
{ status: 400 }
@@ -151,8 +160,8 @@ export async function GET(request: NextRequest): Promise<NextResponse<SearchResp
151160
const offsetParam = searchParams.get('offset');
152161
let offset = 0;
153162
if (offsetParam) {
154-
const parsedOffset = parseInt(offsetParam, 10);
155-
if (isNaN(parsedOffset)) {
163+
const parsedOffset = parseStrictIntegerParam(offsetParam);
164+
if (parsedOffset == null) {
156165
return NextResponse.json(
157166
{ error: 'Invalid offset. Must be a number.' },
158167
{ status: 400 }

0 commit comments

Comments
 (0)