better code: "compiled" configs

Massimo Melina committed Apr 1, 2023 at 00:10 UTC a2d0b773ed9f7ce262d0e1add8e2a047e8a6fac4
11 files changed +36 -32
src/adminApis.ts
+4 -4
@@ -18,7 +18,7 @@ import pluginsApis from './api.plugins'
18 import monitorApis from './api.monitor'
19 import langApis from './api.lang'
20 import { getConnections } from './connections'
21 -import { debounceAsync, isLocalHost, matchesNet, onOff, waitFor } from './misc'
21 +import { debounceAsync, isLocalHost, makeNetMatcher, onOff, waitFor } from './misc'
22 import events from './events'
23 import { accountCanLoginAdmin, accountsConfig, getFromAccount } from './perm'
24 import Koa from 'koa'
@@ -160,8 +160,8 @@ for (const [k, was] of Object.entries(adminApis))
160 }
161
162 export const localhostAdmin = defineConfig('localhost_admin', true)
163 -export const adminNet = defineConfig('admin_net', '')
164 -export const favicon = defineConfig<string>('favicon')
163 +export const adminNet = defineConfig('admin_net', '', v => makeNetMatcher(v, true) )
164 +export const favicon = defineConfig('favicon', '')
165 export const title = defineConfig('title', "File server")
166
167 export function ctxAdminAccess(ctx: Koa.Context) {
@@ -181,5 +181,5 @@ export function anyAccountCanLoginAdmin() {
181 }
182
183 export function allowAdmin(ctx: Koa.Context) {
184 - return matchesNet(ctx, adminNet.get(), true)
184 + return adminNet.compiled()(ctx.ip)
185 }
\ No newline at end of file
src/api.vfs.ts
+3 -2
@@ -5,7 +5,7 @@ import _ from 'lodash'
5 import { stat } from 'fs/promises'
6 import { ApiError, ApiHandlers } from './apiMiddleware'
7 import { dirname, extname, join, resolve } from 'path'
8 -import { dirStream, isWindowsDrive, matches, newObj } from './misc'
8 +import { dirStream, isWindowsDrive, makeMatcher, newObj } from './misc'
9 import {
10 IS_WINDOWS,
11 HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR, HTTP_CONFLICT, HTTP_NOT_ACCEPTABLE,
@@ -170,13 +170,14 @@ const apis: ApiHandlers = {
170 return
171 }
172 try {
173 + const matching = makeMatcher(fileMask)
174 path = isWindowsDrive(path) ? path + '\\' : resolve(path || '/')
175 for await (const [name, isDir] of dirStream(path)) {
176 if (ctx.req.aborted)
177 return
178 try {
179 if (!isDir)
179 - if (!files || fileMask && !matches(name, fileMask))
180 + if (!files || fileMask && !matching(name))
181 continue
182 const stats = await stat(join(path, name))
183 yield {
src/block.ts
+6 -6
@@ -5,18 +5,18 @@ import { getConnections, normalizeIp } from './connections'
5 import { makeNetMatcher, onlyTruthy } from './misc'
6 import { Socket } from 'net'
7
8 -type BlockFun = (x: string) => boolean
9 -let blockFunctions: BlockFun[] = [] // "compiled" versions of the rules in config.block
8 +interface BlockingRule { ip: string }
9
11 -defineConfig<string[]>('block', []).sub(rules => {
12 - blockFunctions = !Array.isArray(rules) ? []
13 - : onlyTruthy(rules.map((rule: any) => rule?.ip && makeNetMatcher(rule.ip)))
10 +const block = defineConfig('block', [] as BlockingRule[], rules => {
11 + const ret = !Array.isArray(rules) ? []
12 + : onlyTruthy(rules.map(rule => makeNetMatcher(rule.ip, true)))
13 // reapply new block to existing connections
14 for (const { socket, ip } of getConnections())
15 applyBlock(socket, ip)
16 + return ret
17 })
18
19 export function applyBlock(socket: Socket, ip=normalizeIp(socket.remoteAddress||'')) {
20 - if (ip && blockFunctions.find(rule => rule(ip)))
20 + if (ip && block.compiled().find(rule => rule(ip)))
21 return socket.destroy()
22 }
src/config.ts
+7 -2
@@ -53,9 +53,13 @@ const { save } = watchLoad(path, values => setConfig(values||{}, false), {
53 interface ConfigProps<T> {
54 defaultValue?: T,
55 }
56 -export function defineConfig<T>(k: string, defaultValue?: T) {
56 +export function defineConfig<T, CT=T>(k: string, defaultValue: T, compiler: ((v: T) => CT)=_.identity) {
57 configProps[k] = { defaultValue }
58 type Updater = (currentValue:T) => T
59 + let compiled: CT = compiler(defaultValue)
60 + if (compiler)
61 + subscribeConfig(k, (v:T) =>
62 + compiled = compiler(v) )
63 return {
64 key() {
65 return k
@@ -71,7 +75,8 @@ export function defineConfig<T>(k: string, defaultValue?: T) {
75 this.set((v as Updater)(this.get()))
76 else
77 setConfig1(k, v)
74 - }
78 + },
79 + compiled: () => compiled
80 }
81 }
82
src/customHtml.ts
+1 -1
@@ -22,7 +22,7 @@ if (!existsSync(FILE))
22 events.once('config ready', () => {
23 const legacy = prefix('[beforeHeader]\n', customHeader.get())
24 writeFileSync(FILE, legacy)
25 - customHeader.set(undefined) // get rid of it
25 + customHeader.set('') // get rid of it
26 })
27 watchLoad(FILE, data => {
28 const re = /^\[(\w+)] *$/gm
src/frontEndApis.ts
+1 -1
@@ -20,7 +20,7 @@ import { mkdir, readFile, rm } from 'fs/promises'
20 import { join } from 'path'
21 import { wantArray } from './misc'
22
23 -export const customHeader = defineConfig<string | undefined>('custom_header')
23 +export const customHeader = defineConfig('custom_header', '')
24
25 export const frontEndApis: ApiHandlers = {
26 file_list,
src/listen.ts
+2 -2
@@ -83,8 +83,8 @@ const considerHttps = debounceAsync(async () => {
83 })
84
85
86 -const cert = defineConfig<string>('cert')
87 -const privateKey = defineConfig<string>('private_key')
86 +const cert = defineConfig('cert', '')
87 +const privateKey = defineConfig('private_key', '')
88 const httpsNeeds = [cert, privateKey]
89 const httpsOptions = { cert: '', private_key: '' }
90 type HttpsKeys = keyof typeof httpsOptions
src/misc.ts
+8 -10
@@ -173,21 +173,19 @@ export function isLocalHost(c: Connection | Koa.Context) {
173 return ip && (ip === '::1' || ip.endsWith('127.0.0.1'))
174 }
175
176 -export function matchesNet(ip: Koa.Context | string, mask: string, emptyMaskReturns=false) {
177 - if (typeof ip !== 'string')
178 - ip = ip.ip
179 - return mask ? makeNetMatcher(mask)(ip) : emptyMaskReturns
176 +export function makeNetMatcher(mask: string, emptyMaskReturns=false) {
177 + return !mask ? () => emptyMaskReturns
178 + : mask.includes('/') ? (ip: string) => cidr.contains(mask, ip)
179 + : makeMatcher(mask)
180 }
181
182 -export function makeNetMatcher(mask: string) {
183 - return mask.includes('/') ? (ip: string) => cidr.contains(mask, ip)
184 - : matcher(mask)
182 +export function makeMatcher(mask: string, emptyMaskReturns=false) {
183 + return mask ? matcher('(' + mask + ')') // adding () will allow us to use the pipe at root level
184 + : () => emptyMaskReturns
185 }
186
187 export function matches(s: string, mask: string, emptyMaskReturns=false) {
188 - if (!mask)
189 - return emptyMaskReturns
190 - return isMatch(s, '(' + mask + ')') // adding () will allow us to use the pipe at root level
188 + return makeMatcher('(' + mask + ')', emptyMaskReturns)(s) // adding () will allow us to use the pipe at root level
189 }
190
191 export function same(a: any, b: any) {
src/perm.ts
+1 -1
@@ -50,7 +50,7 @@ export function saveSrpInfo(account:Account, salt:string | bigint, verifier: str
50 account.srp = String(salt) + '|' + String(verifier)
51 }
52
53 -export const allowClearTextLogin = defineConfig('allow_clear_text_login')
53 +export const allowClearTextLogin = defineConfig('allow_clear_text_login', false)
54
55 const srp6aNimbusRoutines = new SRPRoutines(new SRPParameters())
56
src/serveFile.ts
+2 -2
@@ -26,7 +26,7 @@ export function serveFileNode(ctx: Koa.Context, node: VfsNode) {
26 const name = getNodeName(node)
27 const mimeString = typeof mime === 'string' ? mime
28 : _.find(mime, (val,mask) => matches(name, mask))
29 - const allowed = allowedReferer.get()
29 + const allowed = allowedReferer.get()
30 if (allowed) {
31 const ref = /\/\/([^:/]+)/.exec(ctx.get('referer'))?.[1] // extract host from url
32 if (ref && ref !== host() // automatic accept if referer is basically the hosting domain
@@ -51,7 +51,7 @@ export async function serveFile(ctx: Koa.Context, source:string, mime?:string, c
51 const fn = path.basename(source)
52 if ('dl' in ctx.params) // please, download
53 ctx.attachment(fn)
54 - mime = mime ?? _.find(mimeCfg.get(), (v,k) => matches(fn, k, ))
54 + mime = mime ?? _.find(mimeCfg.get(), (v,k) => matches(fn, k))
55 if (mime === MIME_AUTO)
56 mime = mimetypes.lookup(source) || ''
57 if (mime)
src/upload.ts
+1 -1
@@ -15,7 +15,7 @@ import { socket2connection, updateConnection } from './connections'
15 import { roundSpeed } from './throttler'
16 import _ from 'lodash'
17
18 -export const deleteUnfinishedUploadsAfter = defineConfig('delete_unfinished_uploads_after')
18 +export const deleteUnfinishedUploadsAfter = defineConfig<undefined|number>('delete_unfinished_uploads_after', undefined)
19 export const minAvailableMb = defineConfig('min_available_mb', 100)
20 const dontOverwriteUploading = defineConfig('dont_overwrite_uploading', false)
21