code: /* 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('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',
            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
    }
  }
  