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