fix: vhosting plugin not working
Massimo Melina committed
Jan 3, 2023 at 19:51 UTC
f5d916d7769f798a02c90bd9e663da59dfe0c079
5 files changed
+37
-27
plugins/vhosting/plugin.js
+1
-1
@@ -21,7 +21,7 @@ exports.init = api => ({
21
middleware(ctx) {
22
let toModify = ctx
23
if (ctx.path.startsWith(api.const.SPECIAL_URI)) { // special uris should be excluded...
24
- toModify = ctx.request.query
24
+ toModify = ctx.params
25
if (toModify.path === undefined) // ...unless they carry a path in the query. In that case we'll work that.
26
return
27
}
src/apiMiddleware.ts
+2
-21
@@ -1,10 +1,9 @@
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 { IncomingMessage } from 'http'
3
import Koa from 'koa'
4
import createSSE from './sse'
5
import { Readable } from 'stream'
7
-import { asyncGeneratorToReadable, objSameKeys, onOff, tryJson, wantArray } from './misc'
6
+import { asyncGeneratorToReadable, onOff } from './misc'
7
import events from './events'
8
import { UNAUTHORIZED } from './const'
9
import _, { DebouncedFunc } from 'lodash'
@@ -20,8 +19,7 @@ export type ApiHandlers = Record<string, ApiHandler>
19
20
export function apiMiddleware(apis: ApiHandlers) : Koa.Middleware {
21
return async (ctx) => {
23
- const params = ctx.method === 'POST' ? await getJsonFromReq(ctx.req)
24
- : objSameKeys(ctx.request.query, x => Array.isArray(x) ? x : tryJson(x))
22
+ const { params } = ctx
23
console.debug('API', ctx.method, ctx.path, { ...params })
24
if (!apis.hasOwnProperty(ctx.path)) {
25
ctx.body = 'invalid api'
@@ -56,23 +54,6 @@ function isAsyncGenerator(x: any): x is AsyncGenerator {
54
return typeof (x as AsyncGenerator)?.next === 'function'
55
}
56
59
-async function getJsonFromReq(req: IncomingMessage): Promise<any> {
60
- return new Promise((resolve, reject) => {
61
- let data = ''
62
- req.on('data', chunk =>
63
- data += chunk)
64
- req.on('error', reject)
65
- req.on('end', () => {
66
- try {
67
- resolve(data && JSON.parse(data))
68
- }
69
- catch(e) {
70
- reject(e)
71
- }
72
- })
73
- })
74
-}
75
-
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 {
src/index.ts
+2
-1
@@ -9,7 +9,7 @@ import { frontEndApis } from './frontEndApis'
9
import { log } from './log'
10
import { pluginsMiddleware } from './plugins'
11
import { throttler } from './throttler'
12
-import { headRequests, gzipper, sessions, serveGuiAndSharedFiles, someSecurity, prepareState } from './middlewares'
12
+import { headRequests, gzipper, sessions, serveGuiAndSharedFiles, someSecurity, prepareState, paramsDecoder } from './middlewares'
13
import './listen'
14
import './commands'
15
import { adminApis } from './adminApis'
@@ -29,6 +29,7 @@ app.use(someSecurity)
29
.use(log())
30
.use(throttler)
31
.use(gzipper)
32
+ .use(paramsDecoder)
33
.use(pluginsMiddleware())
34
.use(mount(API_URI, apiMiddleware({ ...frontEndApis, ...adminApis })))
35
.use(serveGuiAndSharedFiles)
src/middlewares.ts
+26
-1
@@ -7,7 +7,7 @@ import { ADMIN_URI, BUILD_TIMESTAMP, DEV, FORBIDDEN, SESSION_DURATION } from './
7
import Application from 'koa'
8
import { FRONTEND_URI } from './const'
9
import { cantReadStatusCode, hasPermission, nodeIsDirectory, urlToNode } from './vfs'
10
-import { dirTraversal } from './misc'
10
+import { dirTraversal, objSameKeys, tryJson } from './misc'
11
import { zipStreamFromFolder } from './zip'
12
import { serveFileNode } from './serveFile'
13
import { serveGuiFiles } from './serveGuiFiles'
@@ -19,6 +19,7 @@ import { socket2connection, updateConnection, normalizeIp } from './connections'
19
import basicAuth from 'basic-auth'
20
import { SRPClientSession, SRPParameters, SRPRoutines } from 'tssrp6a'
21
import { srpStep1 } from './api.auth'
22
+import { IncomingMessage } from 'http'
23
24
export const gzipper = compress({
25
threshold: 2048,
@@ -149,3 +150,27 @@ async function srpCheck(username: string, password: string) {
150
const clientRes2 = await clientRes1.step2(BigInt(salt), BigInt(pubKey))
151
return await step1.step2(clientRes2.A, clientRes2.M1).then(() => true, () => false)
152
}
153
+
154
+// unify get/post parameters, with JSON decoding to not be limited to strings
155
+export const paramsDecoder: Koa.Middleware = async (ctx, next) => {
156
+ ctx.params = ctx.method === 'POST' ? tryJson(await getReqData(ctx.req))
157
+ : objSameKeys(ctx.query, x => Array.isArray(x) ? x : tryJson(x))
158
+ await next()
159
+}
160
+
161
+async function getReqData(req: IncomingMessage): Promise<any> {
162
+ return new Promise((resolve, reject) => {
163
+ let data = ''
164
+ req.on('data', chunk =>
165
+ data += chunk)
166
+ req.on('error', reject)
167
+ req.on('end', () => {
168
+ try {
169
+ resolve(data)
170
+ }
171
+ catch(e) {
172
+ reject(e)
173
+ }
174
+ })
175
+ })
176
+}
src/vfs.ts
+6
-3
@@ -64,11 +64,14 @@ function inheritFromParent(parent: VfsNode, child: VfsNode) {
64
}
65
66
export async function urlToNode(url: string, ctx?: Koa.Context, parent: VfsNode=vfs) : Promise<VfsNode | undefined> {
67
- let i = url.indexOf('/', 1)
68
- const name = decodeURIComponent(url.slice(url[0]==='/' ? 1 : 0, i < 0 ? undefined : i))
67
+ let initialSlashes = 0
68
+ while (url[initialSlashes] === '/')
69
+ initialSlashes++
70
+ let nextSlash = url.indexOf('/', initialSlashes)
71
+ const name = decodeURIComponent(url.slice(initialSlashes, nextSlash < 0 ? undefined : nextSlash))
72
if (!name)
73
return parent
71
- const rest = i < 0 ? '' : url.slice(i+1, url.endsWith('/') ? -1 : undefined)
74
+ const rest = nextSlash < 0 ? '' : url.slice(nextSlash+1, url.endsWith('/') ? -1 : undefined)
75
if (dirTraversal(name) || /[\/]/.test(name)) {
76
if (ctx)
77
ctx.status = 418