@@ -9,6 +9,10 @@ import { FastRouter } from '@neabyte/fast-router'
99 * @description Scans routes, runs middleware chain, dispatches to route or static.
1010 */
1111export class Handler {
12+ /** Default max route param length */
13+ private static readonly defaultMaxRouteParamLength = 1024
14+ /** Default max request URL length */
15+ private static readonly defaultMaxUrlLength = 8192
1216 /** Default error response builder using Error. */
1317 private static readonly defaultErrorResponseBuilder : Types . ErrorResponseBuilder = {
1418 build : ( ctx , statusCode , error , errorMiddleware ) =>
@@ -26,6 +30,10 @@ export class Handler {
2630 private errorResponseBuilder : Types . ErrorResponseBuilder
2731 /** Fast router for route matching. */
2832 private routerInstance = new FastRouter < Types . RouteMetadata > ( )
33+ /** Max length per route param; 414 when exceeded */
34+ private maxRouteParamLength : number | undefined
35+ /** Max request URL length; 414 when exceeded */
36+ private maxUrlLength : number | undefined
2937 /** Request timeout in ms; 503 when exceeded when set. */
3038 private requestTimeoutMs : number | undefined
3139 /** Static file handler; default or custom. */
@@ -43,13 +51,15 @@ export class Handler {
4351 constructor ( options ?: Types . HandlerOptions ) {
4452 this . errorResponseBuilder = options ?. errorResponseBuilder ?? Handler . defaultErrorResponseBuilder
4553 this . staticHandler = options ?. staticHandler ?? Handler . defaultStaticHandler
54+ this . maxUrlLength = options ?. maxUrlLength ?? Handler . defaultMaxUrlLength
55+ this . maxRouteParamLength = options ?. maxRouteParamLength ?? Handler . defaultMaxRouteParamLength
4656 this . requestTimeoutMs = options ?. requestTimeoutMs
47- this . workerPool = options ?. worker !== undefined
48- ? Core . Worker . createPool ( options . worker )
49- : undefined
50- this . viewEngine = options ?. viewsDir !== undefined
51- ? new Rendering . Engine ( { viewsDir : options . viewsDir } )
52- : undefined
57+ this . workerPool =
58+ options ?. worker !== undefined ? Core . Worker . createPool ( options . worker ) : undefined
59+ this . viewEngine =
60+ options ?. viewsDir !== undefined
61+ ? new Rendering . Engine ( { viewsDir : options . viewsDir } )
62+ : undefined
5363 }
5464
5565 /**
@@ -92,7 +102,12 @@ export class Handler {
92102 * @returns Async function from Request to Response
93103 */
94104 createHandler ( ) : ( req : Request ) => Promise < Response > {
105+ const maxUrlLength = this . maxUrlLength
106+ const maxRouteParamLength = this . maxRouteParamLength
95107 const run = async ( req : Request ) : Promise < Response > => {
108+ if ( maxUrlLength !== undefined && maxUrlLength > 0 && req . url . length > maxUrlLength ) {
109+ return Handler . buildUriTooLongResponse ( req )
110+ }
96111 const url = new URL ( req . url )
97112 const ctx = new Core . Context ( req , url , { } , this . handleResponse . bind ( this ) )
98113 if ( this . workerPool ) {
@@ -115,6 +130,13 @@ export class Handler {
115130 return await ctx . handleError ( 404 , new Error ( 'Route not found' ) )
116131 }
117132 if ( 'params' in routeResult && routeResult . params ) {
133+ if ( maxRouteParamLength !== undefined && maxRouteParamLength > 0 ) {
134+ for ( const paramValue of Object . values ( routeResult . params ) ) {
135+ if ( paramValue . length > maxRouteParamLength ) {
136+ return await ctx . handleError ( 414 , new Error ( 'URI Too Long' ) )
137+ }
138+ }
139+ }
118140 ctx . setParams ( routeResult . params )
119141 }
120142 const { handler } = metadata as Types . RouteMetadata
@@ -143,7 +165,7 @@ export class Handler {
143165 const timeoutMs = this . requestTimeoutMs
144166 return async ( req : Request ) => {
145167 if ( timeoutMs !== undefined && timeoutMs > 0 ) {
146- const timeoutResponse = new Promise < Response > ( ( resolve ) => {
168+ const timeoutResponse = new Promise < Response > ( resolve => {
147169 setTimeout (
148170 ( ) => resolve ( new Response ( null , { status : 503 , statusText : 'Service Unavailable' } ) ) ,
149171 timeoutMs
@@ -230,6 +252,28 @@ export class Handler {
230252 Routing . Scanner . validateModule ( module , routePath , Core . Constant . httpMethods )
231253 }
232254
255+ /**
256+ * Build 414 response from request.
257+ * @description Returns JSON or HTML by Accept header.
258+ * @param req - Incoming request
259+ * @returns 414 Response
260+ */
261+ private static buildUriTooLongResponse ( req : Request ) : Response {
262+ const statusCode = 414
263+ const error = 'URI Too Long'
264+ const isJson = req . headers . get ( 'accept' ) ?. includes ( 'application/json' )
265+ if ( isJson ) {
266+ return globalThis . Response . json (
267+ { error, path : '' , statusCode } ,
268+ { status : statusCode , headers : { 'Content-Type' : 'application/json' } }
269+ )
270+ }
271+ return new Response ( Core . Error . defaultErrorHtml ( statusCode , error ) , {
272+ status : statusCode ,
273+ headers : { 'Content-Type' : 'text/html' }
274+ } )
275+ }
276+
233277 /**
234278 * Run middleware chain for pathname.
235279 * @description Filters by path then runs next chain; returns first response.
@@ -241,7 +285,7 @@ export class Handler {
241285 ctx : Core . Context ,
242286 pathname : string
243287 ) : Promise < Response | undefined > {
244- const applicableMiddlewares = this . entryMiddleware . filter ( ( middlewareEntry ) => {
288+ const applicableMiddlewares = this . entryMiddleware . filter ( middlewareEntry => {
245289 if ( middlewareEntry . path === '' || middlewareEntry . path === '*' ) {
246290 return true
247291 }
0 commit comments