fix: get=list was not complying with configured roots
Massimo Melina committed
May 14, 2026 at 19:33 UTC
135b66c0f8fcdd2b07381bfc330c4c56b1591fa8
4 files changed
+30
-5
src/roots.ts
+2
-2
@@ -30,7 +30,7 @@ export const rootsMiddleware: Koa.Middleware = (ctx, next) =>
30
if (referer && try_(() => new URL(referer).pathname.startsWith(ctx.state.revProxyPath + ADMIN_URI))) return // exclude apis for admin-panel
31
}
32
if (_.isEmpty(roots.get())) return
33
- const root = ctx.state.root = roots.compiled()?.(ctx.host)
33
+ const root = ctx.state.root = roots.compiled()(ctx.host)
34
if (!ctx.state.skipFilters && forceAddress.get()
35
&& root === undefined && !isLocalHost(ctx) && ctx.host !== baseUrl.compiled())
36
return disconnect(ctx, forceAddress.key()) // returning truthy will not call next
@@ -52,4 +52,4 @@ declare module "koa" {
52
originalPath: string // before roots is applied
53
root?: string
54
}
55
-}
\ No newline at end of file
55
+}
src/serveGuiAndSharedFiles.ts
+13
-3
@@ -23,6 +23,7 @@ import {
23
asyncGeneratorToReadable, filterMapGenerator, isValidFileName, loadFileCached, pathEncode, safeDecodeURIComponent,
24
try_, pathDecodeSegments,
25
} from './misc'
26
+import { roots } from './roots'
27
import XXH from 'xxhashjs'
28
import fs from 'fs'
29
import { rm } from 'fs/promises'
@@ -160,10 +161,19 @@ async function sendFolderList(node: VfsNode, ctx: Koa.Context) {
161
ctx.type = 'text'
162
if (prepend === undefined || prepend === '*') { // * = force auto-detection even if we have baseUrl set
163
const { URL } = ctx
163
- const base = prepend === undefined && baseUrl.get()
164
- || URL.protocol + '//' + URL.host + ctx.state.revProxyPath
164
+ const requestBase = URL.protocol + '//' + URL.host + ctx.state.revProxyPath
165
+ const configuredBaseUrl = prepend === undefined && baseUrl.get()
166
+ const configuredRoot = configuredBaseUrl && roots.compiled()(baseUrl.compiled() || '')
167
+ const pathInConfiguredRoot = configuredRoot && (configuredRoot === '/' ? ctx.path
168
+ : ctx.path.startsWith(configuredRoot) ? ctx.path.slice(configuredRoot.length - 1)
169
+ : ctx.path === configuredRoot.slice(0, -1) ? '/'
170
+ : false)
171
+ // base_url may expose a host-rooted home; use it only for VFS paths inside that host root
172
+ const [base, path] = !configuredBaseUrl ? [requestBase, ctx.path]
173
+ : pathInConfiguredRoot === false ? [requestBase, ctx.path]
174
+ : [configuredBaseUrl, pathInConfiguredRoot || ctx.path]
175
// redo the encoding our way, keeping unicode chars unchanged. decode each segment separately because decodeURI preserves reserved escapes like %3A, which pathEncode would double-encode
166
- prepend = base + pathDecodeSegments(ctx.path, pathEncode)
176
+ prepend = base + pathDecodeSegments(path, pathEncode)
177
}
178
const walker = walkNode(node, { ctx, depth: depth === '*' ? Infinity : Number(depth), parallelizeRecursion: false }) // parallelization produces out-of-order results, and we don't want it like that here
179
ctx.body = asyncGeneratorToReadable(filterMapGenerator(walker, async el => {
tests/config.yaml
+1
@@ -1,4 +1,5 @@
1
port: 8081
2
+base_url: http://127.0.0.1:8081
3
open_browser_at_start: false
4
allowed_referer: x.com
5
localhost_admin: false
tests/test.ts
+14
@@ -154,6 +154,20 @@ describe('basics', () => {
154
if (String(data).includes('/tests/C%253A/'))
155
throw Error('double encoded path in list: ' + data)
156
}))
157
+ test('folder list strips base_url root', req('/f1/f2/?get=list&folders=*', data => {
158
+ data = String(data)
159
+ if (!data.includes(`${BASE_URL_127}/f2/alfa.txt`))
160
+ throw Error('missing base_url-rooted path in list: ' + data)
161
+ if (data.includes(`${BASE_URL_127}/f1/f2/alfa.txt`))
162
+ throw Error('base_url root still present in list: ' + data)
163
+ }))
164
+ test('folder list ignores base_url outside its root', req('/tests/?get=list&folders=*', data => {
165
+ data = String(data)
166
+ if (!data.includes(`${BASE_URL}/tests/page/`))
167
+ throw Error('missing request-host path in list: ' + data)
168
+ if (data.includes(BASE_URL_127))
169
+ throw Error('base_url used outside its root: ' + data)
170
+ }))
171
172
test('missing perm', reqList('/for-admins/', 401))
173
test('missing perm.file', req('/for-admins/alfa.txt', 401))