mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Switch to using injectable to setup route for serving static files
Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
parent
086db9fce7
commit
711516cccf
@ -3,7 +3,9 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { Router } from "../router";
|
||||
import type { LensApiRequest } from "../router";
|
||||
import staticFileRouteInjectable from "../routes/static-file-route.injectable";
|
||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||
|
||||
jest.mock("electron", () => ({
|
||||
app: {
|
||||
@ -22,19 +24,31 @@ jest.mock("electron", () => ({
|
||||
}));
|
||||
|
||||
describe("Router", () => {
|
||||
let handleStaticFile: (request: LensApiRequest) => Promise<void>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
await di.runSetups();
|
||||
|
||||
handleStaticFile = di.inject(staticFileRouteInjectable).handler;
|
||||
});
|
||||
|
||||
it("blocks path traversal attacks", async () => {
|
||||
const response: any = {
|
||||
statusCode: 200,
|
||||
end: jest.fn(),
|
||||
};
|
||||
|
||||
await (Router as any).handleStaticFile({
|
||||
const request = {
|
||||
params: {
|
||||
path: "../index.ts",
|
||||
},
|
||||
response,
|
||||
raw: {},
|
||||
});
|
||||
} as LensApiRequest<any>;
|
||||
|
||||
await handleStaticFile(request);
|
||||
|
||||
expect(response.statusCode).toEqual(404);
|
||||
});
|
||||
@ -46,17 +60,20 @@ describe("Router", () => {
|
||||
setHeader: jest.fn(),
|
||||
end: jest.fn(),
|
||||
};
|
||||
|
||||
const req: any = {
|
||||
url: "",
|
||||
};
|
||||
|
||||
await (Router as any).handleStaticFile({
|
||||
const request = {
|
||||
params: {
|
||||
path: "router.test.ts",
|
||||
},
|
||||
response,
|
||||
raw: { req },
|
||||
});
|
||||
} as LensApiRequest<any>;
|
||||
|
||||
await handleStaticFile(request);
|
||||
|
||||
expect(response.statusCode).toEqual(200);
|
||||
});
|
||||
|
||||
@ -6,12 +6,8 @@
|
||||
import Call from "@hapi/call";
|
||||
import Subtext from "@hapi/subtext";
|
||||
import type http from "http";
|
||||
import type httpProxy from "http-proxy";
|
||||
import path from "path";
|
||||
import { readFile } from "fs-extra";
|
||||
import type { Cluster } from "../common/cluster/cluster";
|
||||
import { appName, publicPath } from "../common/vars";
|
||||
import logger from "./logger";
|
||||
|
||||
export interface RouterRequestOpts {
|
||||
req: http.IncomingMessage;
|
||||
@ -44,34 +40,9 @@ export interface LensApiRequest<P = any> {
|
||||
};
|
||||
}
|
||||
|
||||
function getMimeType(filename: string) {
|
||||
const mimeTypes: Record<string, string> = {
|
||||
html: "text/html",
|
||||
txt: "text/plain",
|
||||
css: "text/css",
|
||||
gif: "image/gif",
|
||||
jpg: "image/jpeg",
|
||||
png: "image/png",
|
||||
svg: "image/svg+xml",
|
||||
js: "application/javascript",
|
||||
woff2: "font/woff2",
|
||||
ttf: "font/ttf",
|
||||
};
|
||||
|
||||
return mimeTypes[path.extname(filename).slice(1)] || "text/plain";
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
httpProxy?: httpProxy;
|
||||
}
|
||||
|
||||
export class Router {
|
||||
protected router = new Call.Router();
|
||||
protected static rootPath = path.resolve(__static);
|
||||
|
||||
public constructor(private dependencies: Dependencies) {
|
||||
this.addRoutes();
|
||||
}
|
||||
static rootPath = path.resolve(__static);
|
||||
|
||||
public async route(cluster: Cluster, req: http.IncomingMessage, res: http.ServerResponse): Promise<boolean> {
|
||||
const url = new URL(req.url, "http://localhost");
|
||||
@ -111,66 +82,15 @@ export class Router {
|
||||
};
|
||||
}
|
||||
|
||||
protected static async handleStaticFile({ params, response }: LensApiRequest): Promise<void> {
|
||||
let filePath = params.path;
|
||||
|
||||
for (let retryCount = 0; retryCount < 5; retryCount += 1) {
|
||||
const asset = path.join(Router.rootPath, filePath);
|
||||
const normalizedFilePath = path.resolve(asset);
|
||||
|
||||
if (!normalizedFilePath.startsWith(Router.rootPath)) {
|
||||
response.statusCode = 404;
|
||||
|
||||
return response.end();
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await readFile(asset);
|
||||
|
||||
response.setHeader("Content-Type", getMimeType(asset));
|
||||
response.write(data);
|
||||
response.end();
|
||||
} catch (err) {
|
||||
if (retryCount > 5) {
|
||||
logger.error("handleStaticFile:", err.toString());
|
||||
response.statusCode = 404;
|
||||
|
||||
return response.end();
|
||||
}
|
||||
|
||||
filePath = `${publicPath}/${appName}.html`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addRoute = (route: Route) => {
|
||||
this.router.add({ method: route.method, path: route.path }, (request: LensApiRequest) => {
|
||||
route.handler(request);
|
||||
});
|
||||
};
|
||||
|
||||
protected addRoutes() {
|
||||
// Static assets
|
||||
if (this.dependencies.httpProxy) {
|
||||
this.router.add({ method: "get", path: "/{path*}" }, (apiReq: LensApiRequest) => {
|
||||
const { req, res } = apiReq.raw;
|
||||
|
||||
if (req.url === "/" || !req.url.startsWith("/build/")) {
|
||||
req.url = `${publicPath}/${appName}.html`;
|
||||
}
|
||||
|
||||
this.dependencies.httpProxy.web(req, res, {
|
||||
target: "http://127.0.0.1:8080",
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.router.add({ method: "get", path: "/{path*}" }, Router.handleStaticFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface Route {
|
||||
path: string;
|
||||
method: "get" | "post" | "put" | "patch" | "delete";
|
||||
handler: (request: LensApiRequest) => Promise<void>;
|
||||
handler: (request: LensApiRequest) => void | Promise<void>;
|
||||
}
|
||||
|
||||
@ -4,8 +4,6 @@
|
||||
*/
|
||||
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
|
||||
import { Router, Route } from "../router";
|
||||
import isDevelopmentInjectable from "../../common/vars/is-development.injectable";
|
||||
import httpProxy from "http-proxy";
|
||||
|
||||
export const routeInjectionToken = getInjectionToken<Route>({
|
||||
id: "route-injection-token",
|
||||
@ -15,12 +13,7 @@ const routerInjectable = getInjectable({
|
||||
id: "router",
|
||||
|
||||
instantiate: (di) => {
|
||||
const isDevelopment = di.inject(isDevelopmentInjectable);
|
||||
const proxy = isDevelopment ? httpProxy.createProxy() : undefined;
|
||||
|
||||
const router = new Router({
|
||||
httpProxy: proxy,
|
||||
});
|
||||
const router = new Router();
|
||||
|
||||
const routes = di.injectMany(routeInjectionToken);
|
||||
|
||||
|
||||
110
src/main/routes/static-file-route.injectable.ts
Normal file
110
src/main/routes/static-file-route.injectable.ts
Normal file
@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { LensApiRequest, Route } from "../router";
|
||||
import logger from "../logger";
|
||||
import { routeInjectionToken } from "../router/router.injectable";
|
||||
import {
|
||||
appName,
|
||||
publicPath,
|
||||
} from "../../common/vars";
|
||||
import path from "path";
|
||||
import readFileInjectable from "../../common/fs/read-file.injectable";
|
||||
import isDevelopmentInjectable from "../../common/vars/is-development.injectable";
|
||||
import httpProxy from "http-proxy";
|
||||
import { Router } from "../router";
|
||||
|
||||
function getMimeType(filename: string) {
|
||||
const mimeTypes: Record<string, string> = {
|
||||
html: "text/html",
|
||||
txt: "text/plain",
|
||||
css: "text/css",
|
||||
gif: "image/gif",
|
||||
jpg: "image/jpeg",
|
||||
png: "image/png",
|
||||
svg: "image/svg+xml",
|
||||
js: "application/javascript",
|
||||
woff2: "font/woff2",
|
||||
ttf: "font/ttf",
|
||||
};
|
||||
|
||||
return mimeTypes[path.extname(filename).slice(1)] || "text/plain";
|
||||
}
|
||||
|
||||
interface ProductionDependencies {
|
||||
readFile: (path: string) => Promise<Buffer>;
|
||||
}
|
||||
|
||||
const handleStaticFileInProduction =
|
||||
({ readFile }: ProductionDependencies) =>
|
||||
async ({ params, response }: LensApiRequest): Promise<void> => {
|
||||
let filePath = params.path;
|
||||
|
||||
for (let retryCount = 0; retryCount < 5; retryCount += 1) {
|
||||
const asset = path.join(Router.rootPath, filePath);
|
||||
const normalizedFilePath = path.resolve(asset);
|
||||
|
||||
if (!normalizedFilePath.startsWith(Router.rootPath)) {
|
||||
response.statusCode = 404;
|
||||
|
||||
return response.end();
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await readFile(asset);
|
||||
|
||||
response.setHeader("Content-Type", getMimeType(asset));
|
||||
response.write(data);
|
||||
response.end();
|
||||
} catch (err) {
|
||||
if (retryCount > 5) {
|
||||
logger.error("handleStaticFile:", err.toString());
|
||||
response.statusCode = 404;
|
||||
|
||||
return response.end();
|
||||
}
|
||||
|
||||
filePath = `${publicPath}/${appName}.html`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
interface DevelopmentDependencies {
|
||||
proxy: httpProxy;
|
||||
}
|
||||
|
||||
const handleStaticFileInDevelopment =
|
||||
({ proxy }: DevelopmentDependencies) =>
|
||||
(apiReq: LensApiRequest) => {
|
||||
const { req, res } = apiReq.raw;
|
||||
|
||||
if (req.url === "/" || !req.url.startsWith("/build/")) {
|
||||
req.url = `${publicPath}/${appName}.html`;
|
||||
}
|
||||
|
||||
proxy.web(req, res, {
|
||||
target: "http://127.0.0.1:8080",
|
||||
});
|
||||
};
|
||||
|
||||
const staticFileRouteInjectable = getInjectable({
|
||||
id: "static-file-route",
|
||||
|
||||
instantiate: (di): Route => {
|
||||
const isDevelopment = di.inject(isDevelopmentInjectable);
|
||||
|
||||
return {
|
||||
method: "get",
|
||||
path: `/{path*}`,
|
||||
handler: isDevelopment
|
||||
? handleStaticFileInDevelopment({ proxy: httpProxy.createProxy() })
|
||||
: handleStaticFileInProduction({ readFile: di.inject(readFileInjectable) }),
|
||||
};
|
||||
},
|
||||
|
||||
injectionToken: routeInjectionToken,
|
||||
});
|
||||
|
||||
export default staticFileRouteInjectable;
|
||||
Loading…
Reference in New Issue
Block a user