/* eslint-disable handle-callback-err, no-control-regex, no-throw-literal, no-unused-vars, no-useless-escape, standard/no-callback-literal, valid-typeof, */ // TODO: This file was created by bulk-decaffeinate. // Fix any style issues and re-enable lint. /* * 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('@overleaf/settings') 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.enablePdfCaching = this._parseAttribute( 'enablePdfCaching', compile.options.enablePdfCaching, { default: false, type: 'boolean' } ) response.timeout = this._parseAttribute( 'timeout', compile.options.timeout, { default: RequestParser.MAX_TIMEOUT, type: 'number' } ) response.imageName = this._parseAttribute( 'imageName', compile.options.imageName, { type: 'string', validValues: settings.clsi && settings.clsi.docker && settings.clsi.docker.allowedImages } ) 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' }) if (settings.allowedCompileGroups) { response.compileGroup = this._parseAttribute( 'compileGroup', compile.options.compileGroup, { validValues: settings.allowedCompileGroups, default: '', type: 'string' } ) } // 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 (const dir of Array.from(path.split('/'))) { if (dir === '..') { throw 'relative path in root resource' } } return path } }