@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
359 lines (357 loc) • 16.7 kB
JavaScript
"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