@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
259 lines (258 loc) • 10.1 kB
JavaScript
"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const jszip_2 = __importDefault(require("jszip"));
const IStorage_1 = require("./IStorage");
const ZipFolder_1 = __importDefault(require("./ZipFolder"));
const StorageBase_1 = __importDefault(require("./StorageBase"));
const CreatorToolsHost_1 = __importStar(require("../app/CreatorToolsHost"));
const StorageUtilities_1 = __importDefault(require("./StorageUtilities"));
const SecurityUtilities_1 = __importDefault(require("../core/SecurityUtilities"));
class ZipStorage extends StorageBase_1.default {
_jsz;
name;
rootFolder;
modified = null;
lastLoadedOrSaved = null;
allowAllFiles = false;
get updatedSinceLoad() {
if (this.modified === null || (this.lastLoadedOrSaved === null && this.modified === null)) {
return false;
}
else if (this.lastLoadedOrSaved === null) {
return true;
}
return this.modified > this.lastLoadedOrSaved;
}
constructor() {
super();
ZipStorage.zipFixup();
this._jsz = new jszip_2.default();
this.rootFolder = new ZipFolder_1.default(this, this._jsz, null, "", "");
}
static zipFixup() {
if (CreatorToolsHost_1.default.hostType === CreatorToolsHost_1.HostType.electronNodeJs || CreatorToolsHost_1.default.hostType === CreatorToolsHost_1.HostType.toolsNodejs) {
// Fix CommonJS/ESM interop for JSZip without using eval
// The issue is that in some Node.js contexts, jszip_1.default is undefined
// but jszip_1 itself is the constructor we need
try {
// Access the module through the global require cache if available
// This is safer than eval and achieves the same result
const jszip_1 = require("jszip");
if (jszip_1 && !jszip_1.default && typeof jszip_1 === "function") {
// If jszip_1 is the constructor but default is missing, add it
jszip_1.default = jszip_1;
}
}
catch {
// If require fails (e.g., in bundled contexts), the import should work
// No action needed
}
}
}
static fromJsonString(jsonData) {
const zs = new ZipStorage();
const file = zs.rootFolder.ensureFile("d.json");
file.setContent(jsonData);
file.saveContent();
return zs;
}
static fromJsObject(data) {
const zs = new ZipStorage();
const file = zs.rootFolder.ensureFile("d.json");
let jsonData = undefined;
jsonData = JSON.stringify(data);
file.setContent(jsonData);
file.saveContent();
return zs;
}
static async fromZipBytesToJsonObject(data) {
const zs = new ZipStorage();
await zs.loadFromUint8Array(data);
return await ZipStorage.toJsObject(zs);
}
static async toJsObject(storage) {
const file = storage.rootFolder.ensureFile("d.json");
if (!file.isContentLoaded) {
await file.loadContent();
}
return StorageUtilities_1.default.getJsonObject(file);
}
updateLastLoadedOrSaved() {
this.lastLoadedOrSaved = new Date();
}
async loadFromBase64(data, name) {
try {
await this._jsz.loadAsync(data, {
base64: true,
checkCRC32: true,
});
}
catch (e) {
this.errorMessage = e.toString();
this.errorStatus = IStorage_1.StorageErrorStatus.unprocessable;
}
// Log.fail("Loading zip file from data " + data.length);
this.name = name;
this.updateLastLoadedOrSaved();
await this.rootFolder.load(true);
}
static async loadFromFile(file) {
if (file.fileContainerStorage && file.fileContainerStorage instanceof ZipStorage) {
return file.fileContainerStorage;
}
if (!file.isContentLoaded) {
await file.loadContent();
}
const data = file.content;
if (data && data instanceof Uint8Array) {
const zs = new ZipStorage();
await zs.loadFromUint8Array(data, file.name);
file.fileContainerStorage = zs;
zs.containerFile = file;
return zs;
}
return undefined;
}
async loadFromUint8Array(data, name) {
// Security: Validate upload size
if (!SecurityUtilities_1.default.validateFileSize(data.byteLength)) {
this.errorMessage = `ZIP file too large: ${data.byteLength} bytes (max: ${SecurityUtilities_1.default.MAX_UPLOAD_SIZE})`;
this.errorStatus = IStorage_1.StorageErrorStatus.unprocessable;
throw new Error(this.errorMessage);
}
try {
await this._jsz.loadAsync(data, {
base64: false,
});
}
catch (e) {
this.errorMessage = e.toString();
this.errorStatus = IStorage_1.StorageErrorStatus.unprocessable;
throw e;
}
const filePaths = Object.keys(this._jsz.files);
// Security: Validate ZIP contents
const fileCount = filePaths.length;
if (fileCount > SecurityUtilities_1.default.MAX_ZIP_FILES) {
this.errorMessage = `ZIP contains too many files: ${fileCount} (max: ${SecurityUtilities_1.default.MAX_ZIP_FILES})`;
this.errorStatus = IStorage_1.StorageErrorStatus.unprocessable;
throw new Error(this.errorMessage);
}
// Security: Validate paths in ZIP
for (const filePath of filePaths) {
if (!SecurityUtilities_1.default.validatePath(filePath)) {
this.errorMessage = `ZIP contains invalid path: ${filePath}`;
this.errorStatus = IStorage_1.StorageErrorStatus.unprocessable;
throw new Error(this.errorMessage);
}
}
// Security: Validate total decompressed size against bomb threshold
let totalDecompressedSize = 0;
for (const filePath of filePaths) {
const file = this._jsz.files[filePath];
if (file && !file.dir) {
const fileData = file._data;
if (fileData && typeof fileData.uncompressedSize === "number") {
totalDecompressedSize += fileData.uncompressedSize;
}
}
}
if (totalDecompressedSize > SecurityUtilities_1.default.MAX_DECOMPRESSED_SIZE) {
this.errorMessage = `This file is too large to import: decompressed size ${totalDecompressedSize} bytes exceeds limit of ${SecurityUtilities_1.default.MAX_DECOMPRESSED_SIZE} bytes`;
this.errorStatus = IStorage_1.StorageErrorStatus.unprocessable;
throw new Error(this.errorMessage);
}
this.name = name;
this.updateLastLoadedOrSaved();
await this.rootFolder.load(true);
}
joinPath(pathA, pathB) {
let fullPath = pathA;
if (!fullPath.endsWith(StorageBase_1.default.slashFolderDelimiter)) {
fullPath += StorageBase_1.default.slashFolderDelimiter;
}
fullPath += pathB;
return fullPath;
}
async generateUint8ArrayAsync() {
const result = await this._jsz.generateAsync({
type: "uint8array",
compression: "DEFLATE",
compressionOptions: {
level: 9,
},
});
return result;
}
async generateCompressedBase64Async() {
const result = await this._jsz.generateAsync({
type: "base64",
compression: "DEFLATE",
compressionOptions: {
level: 9,
},
});
return result;
}
async generateCompressedUint8ArrayAsync() {
const result = await this._jsz.generateAsync({
type: "uint8array",
compression: "DEFLATE",
compressionOptions: {
level: 9,
},
});
return result;
}
async generateBlobAsync() {
let type = "blob";
if (CreatorToolsHost_1.default.isLocalNode) {
type = "nodebuffer";
}
const result = await this._jsz.generateAsync({ type: type });
return result;
}
async getAvailable() {
this.available = true;
return this.available;
}
}
exports.default = ZipStorage;