admin/fs: don't show hidden files

Massimo Melina committed Apr 13, 2022 at 23:23 UTC ebbc8a9c6051bc2afab32f2ead8744a1c6559bbf
3 files changed +41 -42
server/src/api.vfs.ts
+2 -11
@@ -5,8 +5,7 @@ import _ from 'lodash'
5 import { stat } from 'fs/promises'
6 import { ApiError, ApiHandlers } from './apiMiddleware'
7 import { dirname, join } from 'path'
8 -import glob from 'fast-glob'
9 -import { enforceFinal, isWindowsDrive, objSameKeys } from './misc'
8 +import { dirStream, enforceFinal, isWindowsDrive, objSameKeys } from './misc'
9 import { exec } from 'child_process'
10 import { promisify } from 'util'
11 import { FORBIDDEN, IS_WINDOWS } from './const'
@@ -128,17 +127,9 @@ const apis: ApiHandlers = {
127 try {
128 if (isWindowsDrive(path))
129 path = enforceFinal('/', path)
131 - const dirStream = glob.stream('*', {
132 - cwd: path,
133 - dot: true,
134 - onlyFiles: false,
135 - suppressErrors: true,
136 - })
137 - for await (let name of dirStream) {
130 + for await (const name of dirStream(path)) {
131 if (ctx.req.aborted)
132 return
140 - if (name instanceof Buffer)
141 - name = name.toString('utf8')
133 try {
134 const full = join(path, name)
135 const stats = await stat(full)
server/src/misc.ts
+37
@@ -7,6 +7,9 @@ import { watch } from 'fs'
7 import _ from 'lodash'
8 import { Readable } from 'stream'
9 import Koa from 'koa'
10 +import glob from 'fast-glob'
11 +import { IS_WINDOWS } from './const'
12 +import { execFile } from 'child_process'
13
14 export type Callback<IN=void, OUT=void> = (x:IN) => OUT
15 export type Dict<T = any> = Record<string, T>
@@ -229,3 +232,37 @@ export function isLocalHost(s: string | Koa.Context) {
232 s = s.socket.remoteAddress || '' // don't use .ip as it is subject to proxied ips
233 return s === '127.0.0.1' || s === '::1' || s === '::ffff:127.0.0.1'
234 }
235 +
236 +export async function* dirStream(path: string) {
237 + const dirStream = glob.stream('*', {
238 + cwd: path,
239 + dot: true,
240 + onlyFiles: false,
241 + suppressErrors: true,
242 + })
243 + const skip = await getItemsToSkip(path)
244 + for await (let path of dirStream) {
245 + if (path instanceof Buffer)
246 + path = path.toString('utf8')
247 + if (skip?.includes(path))
248 + continue
249 + yield path
250 + }
251 +
252 + async function getItemsToSkip(path: string) {
253 + if (!IS_WINDOWS) return
254 + const out = await run('dir', ['/ah', '/b', path.replace(/\//g, '\\')])
255 + return out.split('\r\n').slice(0,-1)
256 + }
257 +}
258 +
259 +export function run(cmd: string, args: string[] = []): Promise<string> {
260 + return new Promise((resolve, reject) =>
261 + execFile('cmd', ['/c', cmd, ...args], (err, stdout) => {
262 + if (err)
263 + reject(err)
264 + else
265 + resolve(stdout)
266 + }))
267 +}
268 +
server/src/vfs.ts
+2 -31
@@ -3,7 +3,7 @@
3 import fs from 'fs/promises'
4 import { basename } from 'path'
5 import { isMatch } from 'micromatch'
6 -import { dirTraversal, enforceFinal, getOrSet, isDirectory, typedKeys } from './misc'
6 +import { dirStream, dirTraversal, enforceFinal, getOrSet, isDirectory, typedKeys } from './misc'
7 import Koa from 'koa'
8 import glob from 'fast-glob'
9 import _ from 'lodash'
@@ -12,7 +12,6 @@ import { FORBIDDEN, IS_WINDOWS } from './const'
12 import events from './events'
13 import { getCurrentUsernameExpanded } from './perm'
14 import { with_ } from './misc'
15 -import { execFile } from 'child_process'
15
16 const WHO_ANYONE = true
17 const WHO_NO_ONE = false
@@ -156,20 +155,9 @@ export async function* walkNode(parent:VfsNode, ctx: Koa.Context, depth:number=0
155 return
156 try {
157 const base = enforceFinal('/', source)
159 - const dirStream = glob.stream('*', {
160 - dot: true,
161 - onlyFiles: false,
162 - cwd: base,
163 - suppressErrors: true,
164 - })
165 - const skip = await getItemsToSkip(base)
166 - for await (let path of dirStream) {
158 + for await (const path of dirStream(base)) {
159 if (ctx.req.aborted)
160 return
169 - if (path instanceof Buffer)
170 - path = path.toString('utf8')
171 - if (skip?.includes(path))
172 - continue
161 let { rename } = parent
162 const renamed = rename?.[path]
163 yield* workItem({
@@ -205,23 +193,6 @@ export async function* walkNode(parent:VfsNode, ctx: Koa.Context, depth:number=0
193 catch{} // stat failed in nodeIsDirectory, ignore
194 }
195 }
208 -
209 -async function getItemsToSkip(path: string) {
210 - if (!IS_WINDOWS) return
211 - const out = await run('dir', ['/ah', '/b', path.replace(/\//g, '\\')])
212 - return out.split('\r\n').slice(0,-1)
213 -}
214 -
215 -function run(cmd: string, args: string[] = []): Promise<string> {
216 - return new Promise((resolve, reject) =>
217 - execFile('cmd', ['/c', cmd, ...args], (err, stdout) => {
218 - if (err)
219 - reject(err)
220 - else
221 - resolve(stdout)
222 - }))
223 -}
224 -
196 function applyMasks(item: VfsNode, parent: VfsNode, name: string) {
197 const { masks } = parent
198 if (!masks) return