11const path = require ( "node:path" ) ;
22const querystring = require ( "node:querystring" ) ;
3- // eslint-disable-next-line n/no-deprecated-api
4- const { parse } = require ( "node:url" ) ;
53
64const getPaths = require ( "./getPaths" ) ;
75const memorize = require ( "./memorize" ) ;
@@ -17,20 +15,18 @@ function decode(input) {
1715 return querystring . unescape ( input ) ;
1816}
1917
20- const memoizedParse = memorize ( parse , undefined , ( value ) => {
21- if ( value . pathname ) {
22- value . pathname = decode ( value . pathname ) ;
23- }
18+ const memoizedParse = memorize ( ( url ) => {
19+ const urlObject = new URL ( url , "http://localhost" ) ;
2420
25- return value ;
26- } ) ;
21+ // We can't change pathname in URL object directly because don't decode correctly
22+ return { ...urlObject , pathname : decode ( urlObject . pathname ) } ;
23+ } , undefined ) ;
2724
2825const UP_PATH_REGEXP = / (?: ^ | [ \\ / ] ) \. \. (?: [ \\ / ] | $ ) / ;
2926
3027/**
3128 * @typedef {object } Extra
32- * @property {import("fs").Stats= } stats stats
33- * @property {number= } errorCode error code
29+ * @property {import("fs").Stats } stats stats
3430 * @property {boolean= } immutable true when immutable, otherwise false
3531 */
3632
@@ -42,43 +38,55 @@ const UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
4238 * @returns {string }
4339 */
4440
45- // TODO refactor me in the next major release, this function should return `{ filename, stats, error }`
41+ class FilenameError extends Error {
42+ /**
43+ * @param {string } message message
44+ * @param {number= } code error code
45+ */
46+ constructor ( message , code ) {
47+ super ( message ) ;
48+ this . name = "FilenameError" ;
49+ this . code = code ;
50+ }
51+ }
52+
4653// TODO fix redirect logic when `/` at the end, like https://github.com/pillarjs/send/blob/master/index.js#L586
4754/**
4855 * @template {IncomingMessage} Request
4956 * @template {ServerResponse} Response
5057 * @param {import("../index.js").FilledContext<Request, Response> } context context
5158 * @param {string } url url
52- * @param {Extra= } extra extra
53- * @returns {string | undefined } filename
59+ * @returns {{ filename: string, extra: Extra } | undefined } result of get filename from url
5460 */
55- function getFilenameFromUrl ( context , url , extra = { } ) {
56- const { options } = context ;
57- const paths = getPaths ( context ) ;
61+ function getFilenameFromUrl ( context , url ) {
62+ /** @type { URL } */
63+ let urlObject ;
5864
5965 /** @type {string | undefined } */
6066 let foundFilename ;
61- /** @type {import("node:url").Url } */
62- let urlObject ;
6367
6468 try {
6569 // The `url` property of the `request` is contains only `pathname`, `search` and `hash`
66- urlObject = memoizedParse ( url , false , true ) ;
70+ urlObject = memoizedParse ( url ) ;
6771 } catch {
6872 return ;
6973 }
7074
75+ const { options } = context ;
76+ const paths = getPaths ( context ) ;
77+
78+ /** @type {Extra } */
79+ const extra = { } ;
80+
7181 for ( const { publicPath, outputPath, assetsInfo } of paths ) {
7282 /** @type {string | undefined } */
7383 let filename ;
74- /** @type {import("node:url").Url } */
84+ /** @type {URL } */
7585 let publicPathObject ;
7686
7787 try {
7888 publicPathObject = memoizedParse (
7989 publicPath !== "auto" && publicPath ? publicPath : "/" ,
80- false ,
81- true ,
8290 ) ;
8391 } catch {
8492 continue ;
@@ -94,16 +102,12 @@ function getFilenameFromUrl(context, url, extra = {}) {
94102 ) {
95103 // Null byte(s)
96104 if ( pathname . includes ( "\0" ) ) {
97- extra . errorCode = 400 ;
98-
99- return ;
105+ throw new FilenameError ( "Bad Request" , 400 ) ;
100106 }
101107
102108 // ".." is malicious
103109 if ( UP_PATH_REGEXP . test ( path . normalize ( `./${ pathname } ` ) ) ) {
104- extra . errorCode = 403 ;
105-
106- return ;
110+ throw new FilenameError ( "Forbidden" , 403 ) ;
107111 }
108112
109113 // Strip the `pathname` property from the `publicPath` option from the start of requested url
@@ -161,7 +165,12 @@ function getFilenameFromUrl(context, url, extra = {}) {
161165 }
162166 }
163167
164- return foundFilename ;
168+ if ( ! foundFilename ) {
169+ return ;
170+ }
171+
172+ return { filename : foundFilename , extra } ;
165173}
166174
167175module . exports = getFilenameFromUrl ;
176+ module . exports . FilenameError = FilenameError ;
0 commit comments