@@ -110,6 +110,32 @@ const isServerNotRunningError = (error: Error): boolean => {
110110 ) ;
111111} ;
112112
113+ function isExplicitRelativePath ( value : string ) : boolean {
114+ return (
115+ value . startsWith ( "./" ) ||
116+ value . startsWith ( "../" ) ||
117+ value . startsWith ( ".\\" ) ||
118+ value . startsWith ( "..\\" )
119+ ) ;
120+ }
121+
122+ function resolveFilesystemBrowseInputPath ( input : {
123+ cwd : string | undefined ;
124+ path : Path . Path ;
125+ partialPath : string ;
126+ } ) : Effect . Effect < string | null , never , Path . Path > {
127+ return Effect . gen ( function * ( ) {
128+ if ( ! isExplicitRelativePath ( input . partialPath ) ) {
129+ return input . path . resolve ( yield * expandHomePath ( input . partialPath ) ) ;
130+ }
131+ if ( ! input . cwd ) {
132+ return null ;
133+ }
134+ const expandedCwd = yield * expandHomePath ( input . cwd ) ;
135+ return input . path . resolve ( expandedCwd , input . partialPath ) ;
136+ } ) ;
137+ }
138+
113139function rejectUpgrade ( socket : Duplex , statusCode : number , message : string ) : void {
114140 socket . end (
115141 `HTTP/1.1 ${ statusCode } ${ statusCode === 401 ? "Unauthorized" : "Bad Request" } \r\n` +
@@ -868,14 +894,30 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
868894
869895 case WS_METHODS . filesystemBrowse : {
870896 const body = stripRequestTag ( request . body ) ;
871- const expanded = path . resolve ( yield * expandHomePath ( body . partialPath ) ) ;
897+ const resolvedInputPath = yield * resolveFilesystemBrowseInputPath ( {
898+ cwd : body . cwd ,
899+ path,
900+ partialPath : body . partialPath ,
901+ } ) ;
902+ if ( resolvedInputPath === null ) {
903+ return yield * new RouteRequestError ( {
904+ message : "Relative filesystem browse paths require a current project." ,
905+ } ) ;
906+ }
907+
908+ const expanded = resolvedInputPath ;
872909 const endsWithSep = / [ \\ / ] $ / . test ( body . partialPath ) || body . partialPath === "~" ;
873910 const parentDir = endsWithSep ? expanded : path . dirname ( expanded ) ;
874911 const prefix = endsWithSep ? "" : path . basename ( expanded ) ;
875912
876- const names = yield * fileSystem
877- . readDirectory ( parentDir )
878- . pipe ( Effect . catch ( ( ) => Effect . succeed ( [ ] as string [ ] ) ) ) ;
913+ const names = yield * fileSystem . readDirectory ( parentDir ) . pipe (
914+ Effect . mapError (
915+ ( cause ) =>
916+ new RouteRequestError ( {
917+ message : `Unable to browse '${ parentDir } ': ${ Cause . pretty ( Cause . fail ( cause ) ) . trim ( ) } ` ,
918+ } ) ,
919+ ) ,
920+ ) ;
879921
880922 const showHidden = prefix . startsWith ( "." ) ;
881923 const lowerPrefix = prefix . toLowerCase ( ) ;
@@ -889,18 +931,19 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
889931 const entries = yield * Effect . forEach (
890932 filtered ,
891933 ( name ) =>
892- fileSystem . stat ( path . join ( parentDir , name ) ) . pipe (
893- Effect . map ( ( s ) =>
894- s . type === "Directory" ? { name, fullPath : path . join ( parentDir , name ) } : null ,
934+ fileSystem
935+ . stat ( path . join ( parentDir , name ) )
936+ . pipe (
937+ Effect . map ( ( s ) =>
938+ s . type === "Directory" ? { name, fullPath : path . join ( parentDir , name ) } : null ,
939+ ) ,
895940 ) ,
896- Effect . catch ( ( ) => Effect . succeed ( null ) ) ,
897- ) ,
898941 { concurrency : 16 } ,
899942 ) ;
900943
901944 return {
902945 parentPath : parentDir ,
903- entries : entries . filter ( Boolean ) . slice ( 0 , 50 ) ,
946+ entries : entries . filter ( Boolean ) ,
904947 } ;
905948 }
906949
0 commit comments