fix: permissions not correctly applied with masks

Massimo Melina committed Mar 16, 2023 at 17:54 UTC 6079b1372d2a3bf06606ed5030c6d03e2ac3ef58
3 files changed +40 -28
src/api.vfs.ts
+4 -4
@@ -168,21 +168,21 @@ const apis: ApiHandlers = {
168 }
169 try {
170 path = isWindowsDrive(path) ? path + '\\' : resolve(path || '/')
171 - for await (const name of dirStream(path)) {
171 + for await (const [name, isDir] of dirStream(path)) {
172 if (ctx.req.aborted)
173 return
174 try {
175 - const stats = await stat(join(path, name))
176 - if (stats.isFile())
175 + if (!isDir)
176 if (!files || fileMask && !isMatch(name, fileMask))
177 continue
178 + const stats = await stat(join(path, name))
179 yield {
180 add: {
181 n: name,
182 s: stats.size,
183 c: stats.ctime,
184 m: stats.mtime,
185 - k: stats.isDirectory() ? 'd' : undefined,
185 + k: isDir ? 'd' : undefined,
186 }
187 }
188 }
src/util-files.ts
+3 -2
@@ -94,10 +94,11 @@ export async function* dirStream(path: string, deep?: number) {
94 const skip = await getItemsToSkip(path)
95 for await (const entry of dirStream) {
96 let { path, dirent } = entry as any
97 - if (!dirent.isDirectory() && !dirent.isFile()) continue
97 + const isDir = dirent.isDirectory()
98 + if (!isDir && !dirent.isFile()) continue
99 path = String(path)
100 if (!skip?.includes(path))
100 - yield path
101 + yield [path, isDir]
102 }
103
104 async function getItemsToSkip(path: string) {
src/vfs.ts
+33 -22
@@ -1,7 +1,7 @@
1 // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 import fs from 'fs/promises'
4 -import { basename, join, resolve } from 'path'
4 +import { basename, dirname, join, resolve } from 'path'
5 import { isMatch } from 'micromatch'
6 import { dirStream, dirTraversal, enforceFinal, getOrSet, isDirectory, typedKeys } from './misc'
7 import Koa from 'koa'
@@ -175,26 +175,44 @@ export async function* walkNode(parent:VfsNode, ctx?: Koa.Context, depth:number=
175 const took = prefixPath ? undefined : new Set()
176 if (children)
177 for (const child of children) {
178 - const name = prefixPath + getNodeName(child)
178 + const nodeName = getNodeName(child)
179 + const name = prefixPath + nodeName
180 took?.add(name)
180 - yield* workItem({
181 - ...child,
182 - name,
183 - }, depth > 0 && await nodeIsDirectory(child).catch(() => false))
181 + const item = { ...child, name }
182 + if (!canSee(item)) continue
183 + yield item
184 + if (!depth || !await nodeIsDirectory(child).catch(() => false)) continue
185 + inheritMasks(item, parent, nodeName)
186 + if (!ctx || hasPermission(item, 'can_list', ctx)) // check perm before recursion
187 + yield* walkNode(item, ctx, depth - 1, name + '/')
188 }
189 if (!source)
190 return
191 try {
188 - for await (const path of dirStream(source, depth)) {
192 + let lastDir = prefixPath.slice(0, -1) || '.'
193 + const map = new Map()
194 + map.set(lastDir, parent)
195 + // it's important to keep using dirStream in deep-mode, as it is manyfold faster (it parallelizes)
196 + for await (const [path, isDir] of dirStream(source, depth)) {
197 if (ctx?.req.aborted)
198 return
199 const name = prefixPath + (parent.rename?.[path] || path)
200 if (took?.has(name)) continue
193 - yield* workItem({
201 + if (depth) {
202 + const dir = dirname(name)
203 + if (dir !== lastDir)
204 + parent = map.get(lastDir = dir)
205 + }
206 +
207 + const item = {
208 name,
209 source: join(source, path),
210 rename: renameUnderPath(parent.rename, path),
197 - })
211 + }
212 + if (!canSee(item)) continue
213 + if (isDir)
214 + map.set(name, item)
215 + yield item
216 }
217 }
218 catch(e) {
@@ -202,20 +220,13 @@ export async function* walkNode(parent:VfsNode, ctx?: Koa.Context, depth:number=
220 }
221
222 // item will be changed, so be sure to pass a temp node
205 - async function* workItem(item: VfsNode, recur=false) {
206 - const name = getNodeName(item)
207 - // we basename for depth>0 where we already have the rest of the path in the parent's url, and would be duplicated
208 - const virtualBasename = basename(name)
209 - item.isTemp = true
210 - applyMasks(item, parent, virtualBasename)
223 + function canSee(item: VfsNode) {
224 + // we basename for depth>0 where we already have the rest of the path in the parent's url, and would be duplicated
225 + applyMasks(item, parent, basename(getNodeName(item)))
226 inheritFromParent(parent, item)
212 - if (ctx && !hasPermission(item, 'can_see', ctx))
213 - return
214 - yield item
215 - if (!recur) return
216 - inheritMasks(item, parent, virtualBasename)
217 - if (!ctx || hasPermission(item, 'can_list', ctx)) // check perm before recursion
218 - yield* walkNode(item, ctx, depth - 1, name + '/')
227 + if (ctx && !hasPermission(item, 'can_see', ctx)) return
228 + item.isTemp = true
229 + return item
230 }
231
232 function masksCouldGivePermission(masks: Masks | undefined) {