hfs
Version:
HTTP File Server
79 lines (78 loc) • 3.73 kB
JavaScript
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiError = void 0;
exports.apiMiddleware = apiMiddleware;
const sse_1 = __importDefault(require("./sse"));
const stream_1 = require("stream");
const misc_1 = require("./misc");
const const_1 = require("./const");
const config_1 = require("./config");
const plugins_1 = require("./plugins");
class ApiError extends Error {
constructor(status, message) {
super(typeof message === 'string' ? message : message && message instanceof Error ? message.message : JSON.stringify(message));
this.status = status;
}
}
exports.ApiError = ApiError;
const logApi = (0, config_1.defineConfig)(misc_1.CFG.log_api, true);
function apiMiddleware(apis) {
return async (ctx) => {
var _a;
if (!logApi.get())
ctx.state.dontLog = true;
const isPost = ctx.state.params;
const params = isPost ? ctx.state.params || {} : ctx.query;
const apiName = ctx.path;
console.debug('API', ctx.method, apiName, { ...params });
const noBrowser = (_a = ctx.get('user-agent')) === null || _a === void 0 ? void 0 : _a.startsWith('curl');
const safe = noBrowser || isPost && ctx.get('x-hfs-anti-csrf') // POST is safe because browser will enforce SameSite cookie
|| apiName.startsWith('get_'); // "get_" apis are safe because they make no change
if (!safe)
return send(const_1.HTTP_FOOL, "missing header x-hfs-anti-csrf=1");
const customApiRest = apiName.startsWith(const_1.PLUGIN_CUSTOM_REST_PREFIX) && apiName.slice(const_1.PLUGIN_CUSTOM_REST_PREFIX.length);
const apiFun = customApiRest && (0, plugins_1.firstPlugin)(pl => { var _a; return (_a = pl.getData().customRest) === null || _a === void 0 ? void 0 : _a[customApiRest]; })
|| apis.hasOwnProperty(apiName) && apis[apiName];
if (!apiFun)
return send(const_1.HTTP_BAD_REQUEST, 'invalid api');
// we don't rely on SameSite cookie option because it's https-only
let res;
try {
res = await apiFun(params, ctx);
if (res === null)
return;
}
catch (e) {
if (typeof e === 'string') // message meant to be transmitted
return send(const_1.HTTP_BAD_REQUEST, e);
if (typeof e === 'number')
e = new ApiError(e);
res = e;
}
if (isAsyncGenerator(res))
res = (0, misc_1.asyncGeneratorToReadable)(res);
if (res instanceof stream_1.Readable) { // Readable, we'll go SSE-mode
res.pipe((0, sse_1.default)(ctx));
const resAsReadable = res; // satisfy ts
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
resAsReadable.destroy());
return;
}
if (res instanceof ApiError)
return send(res.status, res.message);
if (res instanceof Error) // generic error/exception
return send(const_1.HTTP_BAD_REQUEST, res.stack || res.message || String(res));
ctx.body = res;
function send(status, body) {
ctx.body = body;
ctx.status = status;
}
};
}
function isAsyncGenerator(x) {
return typeof (x === null || x === void 0 ? void 0 : x.next) === 'function';
}
;