UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

359 lines (357 loc) 16.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const http = require("http"); const ServerManager_1 = require("./ServerManager"); const IAuthenticationToken_1 = require("./IAuthenticationToken"); const Log_1 = require("../core/Log"); const ZipStorage_1 = require("../storage/ZipStorage"); const Utilities_1 = require("../core/Utilities"); const Project_1 = require("../app/Project"); const IProjectInfoData_1 = require("../info/IProjectInfoData"); const ProjectInfoSet_1 = require("../info/ProjectInfoSet"); const ProjectInfoUtilities_1 = require("../info/ProjectInfoUtilities"); class HttpServer { constructor(localEnv, serverManager) { this.host = "localhost"; this.port = 80; this.headers = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "OPTIONS, POST, PUT, GET, PATCH", "Access-Control-Max-Age": 2592000, "Access-Control-Allow-Headers": "*", }; this._serverManager = serverManager; this._localEnvironment = localEnv; this.processRequest = this.processRequest.bind(this); } init() { const requestListener = this.processRequest; if (this._localEnvironment && this._localEnvironment.serverHostPort) { this.port = this._localEnvironment.serverHostPort; } if (this._localEnvironment && this._localEnvironment.serverDomainName) { this.host = this._localEnvironment.serverDomainName; } this._httpServer = http.createServer(requestListener); this._httpServer.listen(this.port, this.host, () => { Log_1.default.message(`Minecraft http server is running on http://${this.host}:${this.port}`); }); } stop() { if (this._httpServer) { this._httpServer.close(() => { Log_1.default.message(`Minecraft http server closed.`); }); } if (this._httpsServer) { this._httpsServer.close(() => { Log_1.default.message(`Minecraft https server closed.`); }); } } getRootPath() { let fullPath = __dirname; const lastSlash = Math.max(fullPath.lastIndexOf("\\", fullPath.length - 2), fullPath.lastIndexOf("/", fullPath.length - 2)); if (lastSlash >= 0) { fullPath = fullPath.substring(0, lastSlash + 1); } return fullPath; } parseCookies(req) { const result = {}; const cookieHeader = req.headers?.cookie; if (!cookieHeader) return result; const cookieVals = cookieHeader.split(`;`); for (let i = 0; i < cookieVals.length; i++) { const cookie = cookieVals[i]; let [name, ...rest] = cookie.split(`=`); name = name?.trim(); if (!name) { return result; } const value = rest.join(`=`).trim(); if (!value) { return result; } result[name] = decodeURIComponent(value); } return result; } processRequest(req, res) { if (req.method === "OPTIONS") { res.writeHead(204, this.headers); res.end(); return; } if (!req.url) { res.writeHead(404, this.headers); Log_1.default.message("Requested url was not specified."); return; } let authorizedPermissionLevel = IAuthenticationToken_1.ServerPermissionLevel.none; let headerPasscode = undefined; if (req.headers["mctpc"]) { headerPasscode = req.headers["mctpc"]; } if (headerPasscode) { if (headerPasscode === this._localEnvironment.displayReadOnlyPasscode) { authorizedPermissionLevel = IAuthenticationToken_1.ServerPermissionLevel.displayReadOnly; } else if (headerPasscode === this._localEnvironment.fullReadOnlyPasscode) { authorizedPermissionLevel = IAuthenticationToken_1.ServerPermissionLevel.fullReadOnly; } else if (headerPasscode === this._localEnvironment.updateStatePasscode) { authorizedPermissionLevel = IAuthenticationToken_1.ServerPermissionLevel.updateState; } else if (headerPasscode === this._localEnvironment.adminPasscode) { authorizedPermissionLevel = IAuthenticationToken_1.ServerPermissionLevel.admin; } else { this.sendErrorRequest(401, "Invalid passcode passed in via mctpc header.", req, res); return; } } if (this._serverManager.features === ServerManager_1.ServerManagerFeatures.all) { } let encryptedToken; const auth = req.headers.authorization; if (auth && auth.length > 40) { // assume that the auth token > 40 const authStr = auth; const firstSection = authStr.substring(0, 7).toLowerCase(); if (firstSection === "bearer " && auth.indexOf("=") >= 0) { const tokenPart = authStr.substring(7); const tokenParts = tokenPart.split("="); if (tokenParts.length === 2) { if (tokenParts[0] === "mctauth") { encryptedToken = tokenParts[1]; } } } } if (!encryptedToken) { const cookies = this.parseCookies(req); const authCookie = cookies["mctauth"]; if (authCookie) { encryptedToken = authCookie; } } if (authorizedPermissionLevel === IAuthenticationToken_1.ServerPermissionLevel.none) { this.sendErrorRequest(401, "No permissions granted; 401 returned.", req, res); return; } const urlSegments = req.url.toLowerCase().split("/"); if (urlSegments.length >= 2) { if (urlSegments[1] === "api") { if (urlSegments[2] === "validate" && req.method === "POST" && req.headers["content-type"] === "application/zip") { if (!this.hasPermissionLevel(authorizedPermissionLevel, IAuthenticationToken_1.ServerPermissionLevel.updateState, req, res)) { return; } const body = []; req.on("data", (chunk) => { body.push(Buffer.from(chunk, "binary")); }); req.on("end", async () => { if (body.length >= 1) { const bodyContent = Buffer.concat(body); if (!this.carto) { this.sendErrorRequest(500, "Unexpected configuration.", req, res); return; } const zipStorage = new ZipStorage_1.default(); const contentUint = new Uint8Array(bodyContent); Log_1.default.message(this.getShortReqDescription(req) + "Received package of " + contentUint.length + " bytes"); try { await zipStorage.loadFromUint8Array(contentUint); } catch (e) { this.sendErrorRequest(400, "Error processing passed-in validation package.", req, res); return; } if (!res.headersSent) { res.writeHead(200, this.headers); } const packProject = new Project_1.default(this.carto, "Test", null); packProject.setProjectFolder(zipStorage.rootFolder); await packProject.inferProjectItemsFromFiles(); let suiteInst = IProjectInfoData_1.ProjectInfoSuite.default; let excludeTests = []; if (req.headers["mctsuite"] && typeof req.headers["mctsuite"] == "string") { suiteInst = ProjectInfoSet_1.default.getSuiteFromString(req.headers["mctsuite"]); } if (req.headers["mctexcludeTests"] && typeof req.headers["mctexcludeTests"] == "string") { excludeTests = req.headers["mctexcludeTests"].split(","); } const pis = new ProjectInfoSet_1.default(packProject, suiteInst, excludeTests); await pis.generateForProject(); let subsetReports = []; if (req.headers["mctsuite"] === "all") { subsetReports = await ProjectInfoUtilities_1.default.getDerivedStates(packProject, pis); } const result = JSON.stringify(pis.getDataObject(undefined, undefined, undefined, false, subsetReports)); res.write(result); res.end(); if (this._serverManager.runOnce) { this._serverManager.shutdown("Shutting down due to completion of one validation operation in runOnce mode."); } return; } else { this.sendErrorRequest(400, "Unexpected post type: " + body.length, req, res); return; } }); return; } let portOrSlot = -1; try { portOrSlot = parseInt(urlSegments[2]); } catch (e) { } if (portOrSlot < 0 || portOrSlot > 65536 || portOrSlot === 80 || portOrSlot === 443) { this.sendErrorRequest(400, "Unexpected port or slot specified", req, res); return; } if (urlSegments[3] === "status" && req.method === "GET") { if (!this.hasPermissionLevel(authorizedPermissionLevel, IAuthenticationToken_1.ServerPermissionLevel.displayReadOnly, req, res)) { return; } const status = this.getStatus(portOrSlot); Log_1.default.message(this.getShortReqDescription(req) + "Status: " + JSON.stringify(status)); res.writeHead(200, this.headers); res.write(JSON.stringify(status)); res.end(); return; } else if (urlSegments[3] === "updateStatus" && req.method === "POST") { if (!this.hasPermissionLevel(authorizedPermissionLevel, IAuthenticationToken_1.ServerPermissionLevel.updateState, req, res)) { return; } const body = []; req.on("data", (chunk) => { body.push(chunk); }); req.on("end", () => { if (body.length === 1) { const val = body[0].toString(); let updates; try { updates = JSON.parse(val); } catch (e) { } if (updates && updates.length) { for (let i = 0; i < updates.length; i++) { const upd = updates[i]; } } res.writeHead(200, this.headers); res.end(); return; } else { res.writeHead(500, this.headers); res.end(); return; } }); } else if (urlSegments[3] === "command" && req.method === "POST") { if (!this.hasPermissionLevel(authorizedPermissionLevel, IAuthenticationToken_1.ServerPermissionLevel.updateState, req, res)) { return; } const body = []; req.on("data", (chunk) => { body.push(chunk); }); req.on("end", () => { if (body.length === 1) { const val = body[0].toString(); res.writeHead(200, this.headers); res.end(); return; } else { res.writeHead(500, this.headers); res.end(); return; } }); } else if (urlSegments[3] === "upload" && (req.method === "POST" || req.method === "PATCH") && req.headers["content-type"] === "application/zip") { if (!this.hasPermissionLevel(authorizedPermissionLevel, IAuthenticationToken_1.ServerPermissionLevel.admin, req, res)) { return; } const body = []; req.on("data", (chunk) => { body.push(chunk); }); req.on("end", async () => { if (body.length === 1) { const zipStorage = new ZipStorage_1.default(); const contentUint = new Uint8Array(body[0]); Log_1.default.message(this.getShortReqDescription(req) + "Received update package of " + contentUint.length + " bytes"); try { await zipStorage.loadFromUint8Array(contentUint); } catch (e) { res.writeHead(400, this.headers); res.end(); return; } let isReloadable = false; if (req.headers["mctools-reloadable"]) { isReloadable = true; } res.writeHead(200, this.headers); res.end(); if (this._serverManager.runOnce) { this._serverManager.shutdown("Shutting down due to completion of one deploy operation in runOnce mode."); } return; } }); } } } res.writeHead(500, this.headers); res.end(); } getShortReqDescription(req) { return "req" + Utilities_1.default.getDateStr(new Date()) + " " + req.method + " " + req.url + ": "; } getStatus(portOrSlot) { return { id: -1, time: new Date().getTime(), }; } sendErrorRequest(statusCode, message, req, res) { Log_1.default.message(this.getShortReqDescription(req) + "Error request: " + message); if (!res.headersSent) { res.writeHead(statusCode, this.headers); } res.write(message); res.end(); } hasPermissionLevel(currentPermLevel, requiredPermissionLevel, req, res) { if (currentPermLevel < requiredPermissionLevel) { Log_1.default.message(this.getShortReqDescription(req) + "Current permissions (" + +currentPermLevel + ") granted, but (" + requiredPermissionLevel + ") needed; 401 returned."); res.writeHead(401, this.headers); res.end("API call failed due to insufficient permissions (" + requiredPermissionLevel + ")"); return false; } return true; } } exports.default = HttpServer; //# sourceMappingURL=../maps/local/HttpServer.js.map