partial download
Massimo Melina committed
Dec 16, 2021 at 19:23 UTC
9d4ab8ce8b358e7149edc07849e7ca102143b9a8
7 files changed
+51
-16
dev-notes.md
+1
@@ -8,6 +8,7 @@
8
- https://www.tutorialspoint.com/koajs/koajs_quick_guide.htm
9
10
# To do
11
+- more tests
12
- permissions
13
- search
14
- vfs: serve an html for a folder?
src/apis.ts
+2
-2
@@ -13,8 +13,8 @@ export function apiMw(apis: ApiHandlers) : Koa.Middleware {
13
return async (ctx, next) => {
14
const params = ctx.request.body
15
console.debug('API', ctx.method, ctx.path, params)
16
- // @ts-ignore
17
- ctx.assert(ctx.path in apis, 404, 'invalid api')
16
+ if (!(ctx.path in apis))
17
+ return ctx.throw(404, 'invalid api')
18
const cb = (apis as any)[ctx.path]
19
const res = await cb(params, ctx)
20
if (res)
src/index.ts
+3
-3
@@ -4,7 +4,7 @@ import bodyParser from 'koa-bodyparser'
4
import { apiMw, frontEndApis } from './apis'
5
import { serveFrontend } from './frontend'
6
import { API_URI, argv, DEV, FRONTEND_URI } from './const'
7
-import { createReadStream } from 'fs'
7
+import { serveFile } from './serveFile'
8
import { vfs } from './vfs'
9
import { isDirectory } from './misc'
10
import proxy from 'koa-better-http-proxy'
@@ -37,8 +37,8 @@ srv.use(async (ctx, next) => {
37
return path.endsWith('/') ? await serveFrontend(ctx,next)
38
: ctx.redirect(ctx.path+'/')
39
if (source)
40
- return ctx.body = source.includes('//') ? mount(path,proxy(source,{}))(ctx,next)
41
- : createReadStream(source)
40
+ return source.includes('//') ? mount(path,proxy(source,{}))(ctx,next)
41
+ : serveFile(source)(ctx,next)
42
await next()
43
})
44
src/pbkdf2.ts
+5
-6
@@ -21,7 +21,7 @@ async function pbkdf2(password, iterations=1e6) {
21
const pwUtf8 = new TextEncoder().encode(password); // encode pw as UTF-8
22
const pwKey = await crypto.subtle.importKey('raw', pwUtf8, 'PBKDF2', false, ['deriveBits']); // create pw key
23
24
- const saltUint8 = crypto.getRandomValues(new Uint8Array(16)); // get random salt
24
+ const saltUint8 = crypto.getRandomValues(new Uint8Array(16)); // get random salt
25
26
const params = { name: 'PBKDF2', hash: 'SHA-256', salt: saltUint8, iterations: iterations }; // pbkdf2 params
27
const keyBuffer = await crypto.subtle.deriveBits(params, pwKey, 256); // derive key
@@ -30,14 +30,13 @@ async function pbkdf2(password, iterations=1e6) {
30
31
const saltArray = Array.from(new Uint8Array(saltUint8)); // salt as byte array
32
33
- const iterHex = ('000000'+iterations.toString(16)).slice(-6); // iter’n count as hex
34
- const iterArray = iterHex.match(/.{2}/g).map(byte => parseInt(byte, 16)); // iter’ns as byte array
33
+ const iterHex = ('000000'+iterations.toString(16)).slice(-6); // iter’n count as hex
34
+ const iterArray = iterHex.match(/.{2}/g).map(byte => parseInt(byte, 16)); // iter’ns as byte array
35
36
const compositeArray = [].concat(saltArray, iterArray, keyArray); // combined array
37
const compositeStr = compositeArray.map(byte => String.fromCharCode(byte)).join(''); // combined as string
38
- const compositeBase64 = btoa('v01'+compositeStr); // encode as base64
39
-
40
- return compositeBase64; // return composite key
38
+ // encode as base64
39
+ return btoa('v01' + compositeStr); // return composite key
40
}
41
42
src/perm.ts
+3
-2
@@ -3,8 +3,9 @@ import fs from 'fs/promises'
3
import _ from 'lodash'
4
import yaml from 'yaml'
5
import { hashPassword } from './crypt'
6
+import { argv } from './const'
7
7
-const PATH = 'accounts.yaml'
8
+const PATH = argv.accounts || 'accounts.yaml'
9
10
interface UserDetails {
11
user: string, // we'll have user in it, so we don't need to pass it separately
@@ -33,7 +34,7 @@ async function load() {
34
file = await fs.readFile(PATH, 'utf8')
35
}
36
catch(e){
36
- console.warn('cannot read', PATH)
37
+ console.warn('cannot read', PATH, e)
38
return
39
}
40
const res = yaml.parse(file)
src/serveFile.ts
new
+31
@@ -0,0 +1,31 @@
1
+import Koa from 'koa'
2
+import { createReadStream } from 'fs'
3
+import fs from 'fs/promises'
4
+
5
+export function serveFile(source: string) : Koa.Middleware {
6
+ return async (ctx) => {
7
+ const { range } = ctx.request.header
8
+ ctx.set('Accept-Ranges', 'bytes')
9
+ if (!range)
10
+ return ctx.body = createReadStream(source)
11
+ const ranges = range.split('=')[1]
12
+ if (ranges.includes(','))
13
+ return ctx.throw(400, 'multi-range not supported')
14
+ let bytes = ranges?.split('-')
15
+ if (!bytes?.length)
16
+ return ctx.throw(400, 'bad range')
17
+ const stat = await fs.stat(source)
18
+ const max = stat.size - 1
19
+ let start = Number(bytes[0])
20
+ let end = Number(bytes[1]) || max
21
+ if (end > max || start > max) {
22
+ ctx.status = 416
23
+ ctx.set('Content-Range', `bytes ${stat.size}`)
24
+ ctx.body = 'Requested Range Not Satisfiable'
25
+ return
26
+ }
27
+ ctx.status = 206
28
+ ctx.set('Content-Range', `bytes ${start}-${end}/${stat.size}`)
29
+ ctx.body = createReadStream(source, { start, end })
30
+ }
31
+}
\ No newline at end of file
src/vfs.ts
+6
-3
@@ -2,7 +2,7 @@ import { argv } from './const'
2
import yaml from 'yaml'
3
import fs from 'fs/promises'
4
import { FSWatcher, watch } from 'fs'
5
-import { basename } from 'path'
5
+import { dirname, basename } from 'path'
6
import { isMatch } from 'micromatch'
7
import { complySlashes, prefix } from './misc'
8
import { getCurrentUser } from './perm'
@@ -30,10 +30,13 @@ const EMPTY = { type: VfsNodeType.root }
30
export class Vfs {
31
root: VfsNode = EMPTY
32
watcher?: FSWatcher
33
+ basePath: string
34
35
constructor(path?:string) {
36
+ this.basePath = ''
37
if (path)
36
- this.load(path).then()
38
+ this.load(path).then(()=>
39
+ this.basePath = dirname(path))
40
else
41
this.reset()
42
}
@@ -51,7 +54,7 @@ export class Vfs {
54
console.debug('loaded')
55
}
56
catch(e) {
54
- console.error(`Load failed for ${path}`)
57
+ console.error(`Load failed for ${path}`,e)
58
}
59
this.watcher?.close()
60
this.watcher = undefined