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