/* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from * DS102: Remove unnecessary code created because of implicit returns * DS205: Consider reworking code to avoid use of IIFEs * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ let RequestParser; const settings = require("settings-sharelatex"); module.exports = (RequestParser = { VALID_COMPILERS: ["pdflatex", "latex", "xelatex", "lualatex"], MAX_TIMEOUT: 600, parse(body, callback) { let resource; if (callback == null) { callback = function(error, data) {}; } const response = {}; if ((body.compile == null)) { return callback("top level object should have a compile attribute"); } const { compile } = body; if (!compile.options) { compile.options = {}; } try { response.compiler = this._parseAttribute("compiler", compile.options.compiler, { validValues: this.VALID_COMPILERS, default: "pdflatex", type: "string" } ); response.timeout = this._parseAttribute("timeout", compile.options.timeout, { default: RequestParser.MAX_TIMEOUT, type: "number" } ); response.imageName = this._parseAttribute("imageName", compile.options.imageName, {type: "string"}); response.draft = this._parseAttribute("draft", compile.options.draft, { default: false, type: "boolean" } ); response.check = this._parseAttribute("check", compile.options.check, {type: "string"}); response.flags = this._parseAttribute("flags", compile.options.flags, { default: [], type: "object" } ); // The syncType specifies whether the request contains all // resources (full) or only those resources to be updated // in-place (incremental). response.syncType = this._parseAttribute("syncType", compile.options.syncType, { validValues: ["full", "incremental"], type: "string" } ); // The syncState is an identifier passed in with the request // which has the property that it changes when any resource is // added, deleted, moved or renamed. // // on syncType full the syncState identifier is passed in and // stored // // on syncType incremental the syncState identifier must match // the stored value response.syncState = this._parseAttribute("syncState", compile.options.syncState, {type: "string"}); if (response.timeout > RequestParser.MAX_TIMEOUT) { response.timeout = RequestParser.MAX_TIMEOUT; } response.timeout = response.timeout * 1000; // milliseconds response.resources = ((() => { const result = []; for (resource of Array.from((compile.resources || []))) { result.push(this._parseResource(resource)); } return result; })()); const rootResourcePath = this._parseAttribute("rootResourcePath", compile.rootResourcePath, { default: "main.tex", type: "string" } ); const originalRootResourcePath = rootResourcePath; const sanitizedRootResourcePath = RequestParser._sanitizePath(rootResourcePath); response.rootResourcePath = RequestParser._checkPath(sanitizedRootResourcePath); for (resource of Array.from(response.resources)) { if (resource.path === originalRootResourcePath) { resource.path = sanitizedRootResourcePath; } } } catch (error1) { const error = error1; return callback(error); } return callback(null, response); }, _parseResource(resource) { let modified; if ((resource.path == null) || (typeof resource.path !== "string")) { throw "all resources should have a path attribute"; } if (resource.modified != null) { modified = new Date(resource.modified); if (isNaN(modified.getTime())) { throw `resource modified date could not be understood: ${resource.modified}`; } } if ((resource.url == null) && (resource.content == null)) { throw "all resources should have either a url or content attribute"; } if ((resource.content != null) && (typeof resource.content !== "string")) { throw "content attribute should be a string"; } if ((resource.url != null) && (typeof resource.url !== "string")) { throw "url attribute should be a string"; } return { path: resource.path, modified, url: resource.url, content: resource.content }; }, _parseAttribute(name, attribute, options) { if (attribute != null) { if (options.validValues != null) { if (options.validValues.indexOf(attribute) === -1) { throw `${name} attribute should be one of: ${options.validValues.join(", ")}`; } } if (options.type != null) { if (typeof attribute !== options.type) { throw `${name} attribute should be a ${options.type}`; } } } else { if (options.default != null) { return options.default; } } return attribute; }, _sanitizePath(path) { // See http://php.net/manual/en/function.escapeshellcmd.php return path.replace(/[\#\&\;\`\|\*\?\~\<\>\^\(\)\[\]\{\}\$\\\x0A\xFF\x00]/g, ""); }, _checkPath(path) { // check that the request does not use a relative path for (let dir of Array.from(path.split('/'))) { if (dir === '..') { throw "relative path in root resource"; } } return path; } });