hfs 0.26.8 → 0.27.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/README.md +15 -2
  2. package/admin/assets/index-509bb1d6.js +415 -0
  3. package/admin/assets/index-60a380a7.css +1 -0
  4. package/admin/assets/sha512-738f0943.js +8 -0
  5. package/admin/index.html +3 -1
  6. package/admin/{public/logo.svg → logo.svg} +0 -0
  7. package/frontend/assets/index-6e178dfd.css +1 -0
  8. package/frontend/assets/index-aea7654e.js +85 -0
  9. package/frontend/assets/sha512-bf915587.js +8 -0
  10. package/frontend/{public/fontello.css → fontello.css} +0 -0
  11. package/frontend/{public/fontello.woff2 → fontello.woff2} +0 -0
  12. package/frontend/index.html +4 -2
  13. package/package.json +2 -6
  14. package/plugins/vhosting/plugin.js +23 -20
  15. package/src/QuickZipStream.js +285 -0
  16. package/src/ThrottledStream.js +93 -0
  17. package/src/adminApis.js +169 -0
  18. package/src/api.accounts.js +59 -0
  19. package/src/api.auth.js +128 -0
  20. package/src/api.file_list.js +110 -0
  21. package/src/api.helpers.js +32 -0
  22. package/src/api.monitor.js +104 -0
  23. package/src/api.plugins.js +128 -0
  24. package/src/api.vfs.js +167 -0
  25. package/src/apiMiddleware.js +123 -0
  26. package/src/block.js +34 -0
  27. package/src/commands.js +125 -0
  28. package/src/config.js +168 -0
  29. package/src/connections.js +57 -0
  30. package/src/const.js +94 -0
  31. package/src/crypt.js +21 -0
  32. package/src/debounceAsync.js +49 -0
  33. package/src/events.js +9 -0
  34. package/src/frontEndApis.js +38 -0
  35. package/src/github.js +104 -0
  36. package/src/index.js +57 -0
  37. package/src/listen.js +235 -0
  38. package/src/log.js +137 -0
  39. package/src/middlewares.js +195 -0
  40. package/src/misc.js +160 -0
  41. package/src/pbkdf2.js +74 -0
  42. package/src/perm.js +183 -0
  43. package/src/plugins.js +343 -0
  44. package/src/serveFile.js +105 -0
  45. package/src/serveGuiFiles.js +113 -0
  46. package/src/sse.js +30 -0
  47. package/src/throttler.js +91 -0
  48. package/src/update.js +70 -0
  49. package/src/util-files.js +163 -0
  50. package/src/util-generators.js +31 -0
  51. package/src/util-http.js +32 -0
  52. package/src/vfs.js +232 -0
  53. package/src/watchLoad.js +73 -0
  54. package/src/zip.js +73 -0
  55. package/admin/.DS_Store +0 -0
  56. package/admin/.eslintrc +0 -8
  57. package/admin/.gitignore +0 -23
  58. package/admin/package.json +0 -67
  59. package/admin/src/AccountForm.ts +0 -92
  60. package/admin/src/AccountsPage.ts +0 -143
  61. package/admin/src/App.ts +0 -83
  62. package/admin/src/ArrayField.ts +0 -84
  63. package/admin/src/ConfigPage.ts +0 -279
  64. package/admin/src/FileField.ts +0 -52
  65. package/admin/src/FileForm.ts +0 -148
  66. package/admin/src/FilePicker.ts +0 -166
  67. package/admin/src/HomePage.ts +0 -96
  68. package/admin/src/InstalledPlugins.ts +0 -158
  69. package/admin/src/LoginRequired.ts +0 -75
  70. package/admin/src/LogoutPage.ts +0 -27
  71. package/admin/src/LogsPage.ts +0 -75
  72. package/admin/src/MainMenu.ts +0 -74
  73. package/admin/src/MenuButton.ts +0 -38
  74. package/admin/src/MonitorPage.ts +0 -200
  75. package/admin/src/OnlinePlugins.ts +0 -101
  76. package/admin/src/PermField.ts +0 -80
  77. package/admin/src/PluginsPage.ts +0 -27
  78. package/admin/src/VfsMenuBar.ts +0 -58
  79. package/admin/src/VfsPage.ts +0 -124
  80. package/admin/src/VfsTree.ts +0 -95
  81. package/admin/src/addFiles.ts +0 -59
  82. package/admin/src/api.ts +0 -246
  83. package/admin/src/dialog.ts +0 -203
  84. package/admin/src/index.css +0 -21
  85. package/admin/src/index.ts +0 -10
  86. package/admin/src/md.ts +0 -31
  87. package/admin/src/misc.ts +0 -141
  88. package/admin/src/react-app-env.d.ts +0 -1
  89. package/admin/src/reportWebVitals.ts +0 -15
  90. package/admin/src/setupTests.ts +0 -5
  91. package/admin/src/state.ts +0 -40
  92. package/admin/src/theme.ts +0 -37
  93. package/admin/tsconfig.json +0 -26
  94. package/admin/vite.config.ts +0 -32
  95. package/frontend/.DS_Store +0 -0
  96. package/frontend/.eslintrc +0 -8
  97. package/frontend/.gitignore +0 -23
  98. package/frontend/package.json +0 -51
  99. package/frontend/src/App.ts +0 -25
  100. package/frontend/src/Breadcrumbs.ts +0 -43
  101. package/frontend/src/BrowseFiles.ts +0 -141
  102. package/frontend/src/Head.ts +0 -45
  103. package/frontend/src/UserPanel.ts +0 -52
  104. package/frontend/src/api.ts +0 -78
  105. package/frontend/src/components.ts +0 -54
  106. package/frontend/src/dialog.css +0 -76
  107. package/frontend/src/dialog.ts +0 -105
  108. package/frontend/src/icons.ts +0 -46
  109. package/frontend/src/index.scss +0 -307
  110. package/frontend/src/index.ts +0 -10
  111. package/frontend/src/login.ts +0 -50
  112. package/frontend/src/menu.ts +0 -188
  113. package/frontend/src/misc.ts +0 -54
  114. package/frontend/src/options.ts +0 -52
  115. package/frontend/src/react-app-env.d.ts +0 -1
  116. package/frontend/src/reportWebVitals.ts +0 -15
  117. package/frontend/src/setupTests.ts +0 -5
  118. package/frontend/src/state.ts +0 -82
  119. package/frontend/src/useAuthorized.ts +0 -17
  120. package/frontend/src/useFetchList.ts +0 -144
  121. package/frontend/src/useTheme.ts +0 -23
  122. package/frontend/tsconfig.json +0 -26
  123. package/frontend/vite.config.ts +0 -21
  124. package/src/QuickZipStream.ts +0 -279
  125. package/src/ThrottledStream.ts +0 -98
  126. package/src/adminApis.ts +0 -161
  127. package/src/api.accounts.ts +0 -78
  128. package/src/api.auth.ts +0 -131
  129. package/src/api.file_list.ts +0 -102
  130. package/src/api.helpers.ts +0 -30
  131. package/src/api.monitor.ts +0 -106
  132. package/src/api.plugins.ts +0 -139
  133. package/src/api.vfs.ts +0 -182
  134. package/src/apiMiddleware.ts +0 -124
  135. package/src/block.ts +0 -35
  136. package/src/commands.ts +0 -122
  137. package/src/config.ts +0 -166
  138. package/src/connections.ts +0 -60
  139. package/src/const.ts +0 -57
  140. package/src/crypt.ts +0 -16
  141. package/src/debounceAsync.ts +0 -51
  142. package/src/events.ts +0 -6
  143. package/src/frontEndApis.ts +0 -17
  144. package/src/github.ts +0 -102
  145. package/src/index.ts +0 -53
  146. package/src/listen.ts +0 -220
  147. package/src/log.ts +0 -128
  148. package/src/middlewares.ts +0 -176
  149. package/src/misc.ts +0 -149
  150. package/src/pbkdf2.ts +0 -83
  151. package/src/perm.ts +0 -194
  152. package/src/plugins.ts +0 -342
  153. package/src/serveFile.ts +0 -104
  154. package/src/serveGuiFiles.ts +0 -95
  155. package/src/sse.ts +0 -29
  156. package/src/throttler.ts +0 -106
  157. package/src/update.ts +0 -67
  158. package/src/util-files.ts +0 -137
  159. package/src/util-generators.ts +0 -29
  160. package/src/util-http.ts +0 -29
  161. package/src/vfs.ts +0 -258
  162. package/src/watchLoad.ts +0 -75
  163. package/src/zip.ts +0 -69
@@ -1,139 +0,0 @@
1
- import {
2
- AvailablePlugin,
3
- enablePlugins,
4
- getAvailablePlugins,
5
- getPluginConfigFields,
6
- mapPlugins,
7
- Plugin, pluginsConfig,
8
- PATH as PLUGINS_PATH, isPluginRunning, enablePlugin, getPluginInfo, setPluginConfig
9
- } from './plugins'
10
- import _ from 'lodash'
11
- import assert from 'assert'
12
- import { objSameKeys, onOff, wait } from './misc'
13
- import { ApiHandlers, SendListReadable } from './apiMiddleware'
14
- import events from './events'
15
- import { rm } from 'fs/promises'
16
- import { downloadPlugin, getFolder2repo, getRepoInfo, readOnlinePlugin, searchPlugins } from './github'
17
-
18
- const apis: ApiHandlers = {
19
-
20
- get_plugins({}, ctx) {
21
- const list = new SendListReadable({ addAtStart: [ ...mapPlugins(serialize), ...getAvailablePlugins() ] })
22
- return list.events(ctx, {
23
- pluginInstalled: p => list.add(serialize(p)),
24
- 'pluginStarted pluginStopped pluginUpdated': p => {
25
- const { id, ...rest } = serialize(p)
26
- list.update({ id }, rest)
27
- },
28
- pluginUninstalled: id => list.remove({ id }),
29
- })
30
-
31
- function serialize(p: Readonly<Plugin> | AvailablePlugin) {
32
- const o = 'getData' in p ? Object.assign(_.pick(p, ['id','started']), p.getData())
33
- : { ...p } // _.defaults mutates object, and we don't want that
34
- return _.defaults(o, { started: null, badApi: null }) // nulls should be used to be sure to overwrite previous values,
35
- }
36
- },
37
-
38
- async get_plugin_updates() {
39
- const list = new SendListReadable()
40
- setTimeout(async () => {
41
- for (const [folder, repo] of Object.entries(getFolder2repo()))
42
- try {
43
- if (!repo) continue
44
- const online = await readOnlinePlugin(await getRepoInfo(repo))
45
- if (!online.apiRequired || online.badApi) continue
46
- const disk = getPluginInfo(folder)
47
- if (online.version! > disk.version)
48
- list.add(online)
49
- }
50
- catch (err:any) {
51
- list.error(err.code || err.message)
52
- }
53
- list.close()
54
- })
55
- return list
56
- },
57
-
58
- async set_plugin({ id, enabled, config }) {
59
- assert(id, 'id')
60
- if (enabled !== undefined)
61
- enablePlugin(id, enabled)
62
- if (config)
63
- setPluginConfig(id, config)
64
- return {}
65
- },
66
-
67
- async get_plugin({ id }) {
68
- return {
69
- enabled: enablePlugins.get().includes(id),
70
- config: {
71
- ...objSameKeys(getPluginConfigFields(id) ||{}, v => v?.defaultValue),
72
- ...pluginsConfig.get()[id]
73
- }
74
- }
75
- },
76
-
77
- search_online_plugins({ text }, ctx) {
78
- return new SendListReadable({
79
- async doAtStart(list) {
80
- try {
81
- const folder2repo = getFolder2repo()
82
- for await (const pl of searchPlugins(text)) {
83
- const repo = pl.id
84
- const folder = _.findKey(folder2repo, x => x === repo)
85
- const installed = folder && getPluginInfo(folder)
86
- Object.assign(pl, {
87
- installed: _.includes(folder2repo, repo),
88
- update: installed && installed.version < pl.version!,
89
- })
90
- list.add(pl)
91
- // watch for events about this plugin, until this request is closed
92
- ctx.req.on('close', onOff(events, {
93
- pluginInstalled: p => {
94
- if (p.repo === repo)
95
- list.update({ id: repo }, { installed: true })
96
- },
97
- pluginUninstalled: folder => {
98
- if (repo === getFolder2repo()[folder])
99
- list.update({ id: repo }, { installed: false })
100
- },
101
- pluginUpdated: p => {
102
- if (p.repo === repo)
103
- list.update({ id: repo }, { update: p.version < pl.version! })
104
- },
105
- ['pluginDownload_' + repo](status) {
106
- list.update({ id: repo }, { downloading: status ?? null })
107
- }
108
- }))
109
- }
110
- } catch (err: any) {
111
- list.error(err.code || err.message)
112
- }
113
- list.ready()
114
- }
115
- })
116
- },
117
-
118
- async download_plugin(pl) {
119
- const res = await downloadPlugin(pl.id, pl.branch)
120
- return typeof res === 'string' ? getPluginInfo(res) : res
121
- },
122
-
123
- async update_plugin(pl) {
124
- await downloadPlugin(pl.id, pl.branch, true)
125
- return {}
126
- },
127
-
128
- async uninstall_plugin({ id }) {
129
- while (isPluginRunning(id)) {
130
- enablePlugin(id, false)
131
- await wait(500)
132
- }
133
- await rm(PLUGINS_PATH + '/' + id, { recursive: true, force: true })
134
- return {}
135
- }
136
-
137
- }
138
-
139
- export default apis
package/src/api.vfs.ts DELETED
@@ -1,182 +0,0 @@
1
- // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
-
3
- import { getNodeName, nodeIsDirectory, saveVfs, urlToNode, vfs, VfsNode } from './vfs'
4
- import _ from 'lodash'
5
- import { stat } from 'fs/promises'
6
- import { ApiError, ApiHandlers } from './apiMiddleware'
7
- import { dirname, join, resolve } from 'path'
8
- import { dirStream, isWindowsDrive, objSameKeys } from './misc'
9
- import { exec } from 'child_process'
10
- import { promisify } from 'util'
11
- import { FORBIDDEN, IS_WINDOWS } from './const'
12
- import { isMatch } from 'micromatch'
13
-
14
- type VfsAdmin = {
15
- type?: string,
16
- size?: number,
17
- ctime?: Date,
18
- mtime?: Date,
19
- website?: true,
20
- children?: VfsAdmin[]
21
- } & Omit<VfsNode, 'type' | 'children'>
22
-
23
- // to manipulate the tree we need the original node
24
- async function urlToNodeOriginal(uri: string) {
25
- const n = await urlToNode(uri)
26
- return n?.isTemp ? n.original : n
27
- }
28
-
29
- const apis: ApiHandlers = {
30
-
31
- async get_vfs() {
32
- return { root: vfs && await recur(vfs) }
33
-
34
- async function recur(node: VfsNode): Promise<VfsAdmin> {
35
- const dir = await nodeIsDirectory(node)
36
- const stats: Pick<VfsAdmin, 'size' | 'ctime' | 'mtime'> = {}
37
- try {
38
- if (!dir)
39
- Object.assign(stats, _.pick(await stat(node.source!), ['size', 'ctime', 'mtime']))
40
- }
41
- catch {
42
- stats.size = -1
43
- }
44
- if (stats && Number(stats.mtime) === Number(stats.ctime))
45
- delete stats.mtime
46
- const isRoot = node === vfs
47
- return {
48
- ...stats,
49
- ...node,
50
- website: dir && node.source && await stat(join(node.source, 'index.html')).then(() => true, () => undefined)
51
- || undefined,
52
- name: isRoot ? undefined : getNodeName(node),
53
- type: dir ? 'folder' : undefined,
54
- children: node.children && await Promise.all(node.children.map(recur)),
55
- }
56
- }
57
- },
58
-
59
- async set_vfs({ uri, props }) {
60
- const n = await urlToNodeOriginal(uri)
61
- if (!n)
62
- return new ApiError(404, 'path not found')
63
- props = pickProps(props, ['name','source','can_see','can_read','masks','default'])
64
- props = objSameKeys(props, v => v === null ? undefined : v) // null is a way to serialize undefined, that will restore default values
65
- if (props.masks && typeof props.masks !== 'object')
66
- delete props.masks
67
- Object.assign(n, props)
68
- if (getNodeName(_.omit(n, ['name'])) === n.name) // name only if necessary
69
- n.name = undefined
70
- await saveVfs()
71
- return n
72
- },
73
-
74
- async add_vfs({ under, source, name }) {
75
- const n = under ? await urlToNodeOriginal(under) : vfs
76
- if (!n)
77
- return new ApiError(404, 'invalid under')
78
- if (n.isTemp || !await nodeIsDirectory(n))
79
- return new ApiError(FORBIDDEN, 'invalid under')
80
- if (isWindowsDrive(source))
81
- source += '\\' // slash must be included, otherwise it will refer to the cwd of that drive
82
- const a = n.children || (n.children = [])
83
- if (source && a.find(x => x.source === source))
84
- return new ApiError(409, 'already present')
85
- a.unshift({ source, name })
86
- await saveVfs()
87
- return {}
88
- },
89
-
90
- async del_vfs({ uris }) {
91
- if (!uris || !Array.isArray(uris))
92
- return new ApiError(400, 'invalid uris')
93
- return {
94
- errors: await Promise.all(uris.map(async uri => {
95
- if (typeof uri !== 'string')
96
- return 400
97
- const node = await urlToNodeOriginal(uri)
98
- if (!node)
99
- return 404
100
- const parent = dirname(uri)
101
- const parentNode = await urlToNodeOriginal(parent)
102
- if (!parentNode)
103
- return FORBIDDEN
104
- const { children } = parentNode
105
- if (!children) // shouldn't happen
106
- return 500
107
- const idx = children.indexOf(node)
108
- children.splice(idx, 1)
109
- saveVfs()
110
- return 0 // error code 0 is OK
111
- }))
112
- }
113
- },
114
-
115
- get_cwd() {
116
- return { path: process.cwd() }
117
- },
118
-
119
- async resolve_path({ path, closestFolder }) {
120
- path = resolve(path)
121
- if (closestFolder)
122
- while (path && !await stat(path).then(x => x.isDirectory(), () => 0))
123
- path = dirname(path)
124
- return { path }
125
- },
126
-
127
- async *ls({ path, files=true, fileMask }, ctx) {
128
- if (!path && IS_WINDOWS) {
129
- try {
130
- for (const n of await getDrives())
131
- yield { add: { n, k: 'd' } }
132
- }
133
- catch(error) {
134
- console.debug(error)
135
- }
136
- return
137
- }
138
- try {
139
- path = isWindowsDrive(path) ? path + '\\' : resolve(path || '/')
140
- for await (const name of dirStream(path)) {
141
- if (ctx.req.aborted)
142
- return
143
- try {
144
- const stats = await stat(join(path, name))
145
- if (stats.isFile())
146
- if (!files || fileMask && !isMatch(name, fileMask))
147
- continue
148
- yield {
149
- add: {
150
- n: name,
151
- s: stats.size,
152
- c: stats.ctime,
153
- m: stats.mtime,
154
- k: stats.isDirectory() ? 'd' : undefined,
155
- }
156
- }
157
- }
158
- catch {} // just ignore entries we can't stat
159
- }
160
- } catch (e: any) {
161
- yield { error: e.code || e.message || String(e) }
162
- }
163
- }
164
-
165
- }
166
-
167
- export default apis
168
-
169
- // pick only selected props, and consider null and empty string as undefined
170
- function pickProps(o: any, keys: string[]) {
171
- const ret: any = {}
172
- if (o && typeof o === 'object')
173
- for (const k of keys)
174
- if (k in o)
175
- ret[k] = o[k] === null || o[k] === '' ? undefined : o[k]
176
- return ret
177
- }
178
-
179
- async function getDrives() {
180
- const { stdout } = await promisify(exec)('wmic logicaldisk get name')
181
- return stdout.split('\n').slice(1).map(x => x.trim()).filter(Boolean)
182
- }
@@ -1,124 +0,0 @@
1
- // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
-
3
- import Koa from 'koa'
4
- import createSSE from './sse'
5
- import { Readable } from 'stream'
6
- import { asyncGeneratorToReadable, onOff } from './misc'
7
- import events from './events'
8
- import { UNAUTHORIZED } from './const'
9
- import _, { DebouncedFunc } from 'lodash'
10
-
11
- export class ApiError extends Error {
12
- constructor(public status:number, message?:string | Error) {
13
- super(typeof message === 'string' ? message : message?.message)
14
- }
15
- }
16
- type ApiHandlerResult = Record<string,any> | ApiError | Readable | AsyncGenerator<any>
17
- export type ApiHandler = (params:any, ctx:Koa.Context) => ApiHandlerResult | Promise<ApiHandlerResult>
18
- export type ApiHandlers = Record<string, ApiHandler>
19
-
20
- export function apiMiddleware(apis: ApiHandlers) : Koa.Middleware {
21
- return async (ctx) => {
22
- const { params } = ctx
23
- console.debug('API', ctx.method, ctx.path, { ...params })
24
- if (!apis.hasOwnProperty(ctx.path)) {
25
- ctx.body = 'invalid api'
26
- return ctx.status = 404
27
- }
28
- const csrf = ctx.cookies.get('csrf')
29
- // we don't rely on SameSite cookie option because it's https-only
30
- let res = csrf && csrf !== params.csrf ? new ApiError(UNAUTHORIZED, 'csrf')
31
- : await apis[ctx.path](params || {}, ctx)
32
- if (isAsyncGenerator(res))
33
- res = asyncGeneratorToReadable(res)
34
- if (res instanceof Readable) { // Readable, we'll go SSE-mode
35
- res.pipe(createSSE(ctx))
36
- const stillRes = res // satisfy ts
37
- ctx.req.on('close', () => // by closing the generated stream, creator of the stream will know the request is over without having to access anything else
38
- stillRes.destroy())
39
- return
40
- }
41
- if (res instanceof ApiError) {
42
- ctx.body = res.message
43
- return ctx.status = res.status
44
- }
45
- if (res instanceof Error) { // generic exception
46
- ctx.body = String(res)
47
- return ctx.status = 400
48
- }
49
- ctx.body = res
50
- }
51
- }
52
-
53
- function isAsyncGenerator(x: any): x is AsyncGenerator {
54
- return typeof (x as AsyncGenerator)?.next === 'function'
55
- }
56
-
57
- // offer an api for a generic dynamic list. Suitable to be the result of an api.
58
- type SendListFunc<T> = (list:SendListReadable<T>) => void
59
- export class SendListReadable<T> extends Readable {
60
- protected lastError: string | number | undefined
61
- protected buffer: any[] = []
62
- protected processBuffer: DebouncedFunc<any>
63
- constructor({ addAtStart, doAtStart, bufferTime }:{ bufferTime?: number, addAtStart?: T[], doAtStart?: SendListFunc<T> }={}) {
64
- super({ objectMode: true, read(){} })
65
- if (!bufferTime)
66
- bufferTime = 100
67
- this.processBuffer = _.debounce(() => {
68
- this.push(this.buffer)
69
- this.buffer = []
70
- }, bufferTime, { maxWait: bufferTime })
71
- this.on('end', () =>
72
- this.destroy())
73
- if (doAtStart)
74
- setTimeout(() => doAtStart(this)) // work later, when list object has been received by Koa
75
- if (addAtStart) {
76
- for (const x of addAtStart)
77
- this.add(x)
78
- this.ready()
79
- }
80
- }
81
- protected _push(rec: any) {
82
- this.buffer.push(rec)
83
- if (this.buffer.length > 10_000) // hard limit
84
- this.processBuffer.flush()
85
- else
86
- this.processBuffer()
87
- }
88
- add(rec: T | T[]) {
89
- this._push({ add: rec })
90
- }
91
- remove(key: Partial<T>) {
92
- this._push({ remove: [key] })
93
- }
94
- update(search: Partial<T>, change: Partial<T>) {
95
- this._push({ update:[{ search, change }] })
96
- }
97
- ready() { // useful to indicate the end of an initial phase, but we leave open for updates
98
- this._push('ready')
99
- }
100
- custom(data: any) {
101
- this._push(data)
102
- }
103
- error(msg: NonNullable<typeof this.lastError>, close=false) {
104
- this._push({ error: msg })
105
- this.lastError = msg
106
- if (close)
107
- this.close()
108
- }
109
- getLastError() {
110
- return this.lastError
111
- }
112
- close() {
113
- this.processBuffer.flush()
114
- this.push(null)
115
- }
116
- events(ctx: Koa.Context, eventMap: Parameters<typeof onOff>[1]) {
117
- const off = onOff(events, eventMap)
118
- ctx.res.once('close', off)
119
- return this
120
- }
121
- isClosed() {
122
- return this.destroyed
123
- }
124
- }
package/src/block.ts DELETED
@@ -1,35 +0,0 @@
1
- import { defineConfig } from './config'
2
- import { getConnections, normalizeIp } from './connections'
3
- import { onlyTruthy, with_ } from './misc'
4
- import cidr from 'cidr-tools'
5
- import _ from 'lodash'
6
- import { Socket } from 'net'
7
-
8
- defineConfig<string[]>('block', []).sub(rules => {
9
- compileBlock(rules)
10
- for (const { socket, ip } of getConnections())
11
- applyBlock(socket, ip)
12
- })
13
-
14
- type BlockFun = (x: string) => boolean
15
- let blockFunctions: BlockFun[] = [] // "compiled" versions of the rules in config.block
16
-
17
- function compileBlock(rules: any) {
18
- blockFunctions = !Array.isArray(rules) ? []
19
- : onlyTruthy(rules.map(rule => !rule ? null
20
- : with_(rule.ip, ip => typeof ip !== 'string' ? null
21
- : ip.includes('/') ? x => cidr.contains(ip, x)
22
- : ip.includes('*') ? with_(ipMask2regExp(ip), re => x => re.test(x) )
23
- : x => x === ip
24
- )
25
- ))
26
-
27
- function ipMask2regExp(ipMask: string) {
28
- return new RegExp(_.escapeRegExp(ipMask).replace(/\\\*/g, '.*'))
29
- }
30
- }
31
-
32
- export function applyBlock(socket: Socket, ip=normalizeIp(socket.remoteAddress||'')) {
33
- if (ip && blockFunctions.find(rule => rule(ip)))
34
- return socket.destroy()
35
- }
package/src/commands.ts DELETED
@@ -1,122 +0,0 @@
1
- import { addAccount, getAccount, updateAccount } from './perm'
2
- import { getConfig, getConfigDefinition, setConfig } from './config'
3
- import _ from 'lodash'
4
- import { getUpdate, update } from './update'
5
- import { openAdmin } from './listen'
6
- import yaml from 'yaml'
7
- import { BUILD_TIMESTAMP, VERSION } from './const'
8
- import { createInterface } from 'readline'
9
-
10
- try {
11
- /*
12
- is this try-block useful in case the stdin is unavailable?
13
- Not sure, but someone reported a problem using nohup https://github.com/rejetto/hfs/issues/74
14
- and I've found this example try-catching https://github.com/DefinitelyTyped/DefinitelyTyped/blob/dda83a906914489e09ca28afea12948529015d4a/types/node/readline.d.ts#L489
15
- */
16
- createInterface({ input: process.stdin }).on('line', parseCommandLine)
17
- console.log(`HINT: type "help" for help`)
18
- }
19
- catch {
20
- console.log("console commands not available")
21
- }
22
-
23
- function parseCommandLine(line: string) {
24
- if (!line) return
25
- const [name, ...params] = line.trim().split(/ +/)
26
- const cmd = (commands as any)[name]
27
- if (!cmd)
28
- return console.error("cannot understand entered command, try 'help'")
29
- if (cmd.cb.length > params.length)
30
- return console.error("insufficient parameters, expected: " + cmd.params)
31
- cmd.cb(...params).then(() =>console.log("+++ command executed"),
32
- (err: any) => {
33
- if (typeof err === 'string')
34
- console.error("command failed:", err)
35
- else
36
- throw err
37
- })
38
- }
39
-
40
- const commands = {
41
- help: {
42
- params: '',
43
- async cb() {
44
- console.log("supported commands:",
45
- ..._.map(commands, ({ params }, name) =>
46
- '\n - ' + name + ' ' + params))
47
- }
48
- },
49
- 'show-admin': {
50
- params: '',
51
- cb(){
52
- openAdmin()
53
- }
54
- },
55
- 'create-admin': {
56
- params: '<password> [<username>=admin]',
57
- async cb(password: string, username='admin') {
58
- if (getAccount(username))
59
- throw `user ${username} already exists`
60
- const acc = addAccount(username, { admin: true })
61
- await updateAccount(acc!, acc => {
62
- acc.password = password
63
- })
64
- }
65
- },
66
- 'change-password': {
67
- params: '<user> <password>',
68
- async cb(user: string, password: string) {
69
- const acc = getAccount(user)
70
- if (!acc)
71
- throw "user doesn't exist"
72
- await updateAccount(acc!, acc => {
73
- acc.password = password
74
- })
75
- }
76
- },
77
- config: {
78
- params: '<key> <value>',
79
- async cb(key: string, value: string) {
80
- const conf = getConfigDefinition(key)
81
- if (!conf)
82
- throw "specified key doesn't exist"
83
- let v: any = value
84
- try { v = JSON.parse(v) }
85
- catch {}
86
- setConfig({ [key]: v })
87
- }
88
- },
89
- 'show-config': {
90
- params: '<key>',
91
- async cb(key: string) {
92
- const conf = getConfigDefinition(key)
93
- if (!conf)
94
- throw "specified key doesn't exist"
95
- console.log(yaml.stringify(getConfig(key), { lineWidth:1000 }).trim())
96
- }
97
- },
98
- quit: {
99
- params: '',
100
- async cb() {
101
- process.exit(0)
102
- }
103
- },
104
- update: {
105
- params: '',
106
- cb: update
107
- },
108
- 'check-update': {
109
- params: '',
110
- async cb() {
111
- const update = await getUpdate()
112
- console.log("new version available", update.name)
113
- }
114
- },
115
- version: {
116
- params: '',
117
- async cb() {
118
- console.log(VERSION)
119
- console.log(BUILD_TIMESTAMP)
120
- }
121
- },
122
- }