diff --git a/services/clsi/app/coffee/OutputCacheManager.coffee b/services/clsi/app/coffee/OutputCacheManager.coffee index 465b0431d9..23e179c425 100644 --- a/services/clsi/app/coffee/OutputCacheManager.coffee +++ b/services/clsi/app/coffee/OutputCacheManager.coffee @@ -63,6 +63,11 @@ module.exports = OutputCacheManager = # copy all the output files into the new cache directory results = [] async.mapSeries outputFiles, (file, cb) -> + # don't send dot files as output, express doesn't serve them + if OutputCacheManager._fileIsHidden(file.path) + logger.warn compileDir: compileDir, path: file.path, "ignoring dotfile in output" + return cb() + # copy other files into cache directory if valid newFile = _.clone(file) [src, dst] = [Path.join(compileDir, file.path), Path.join(cacheDir, file.path)] OutputCacheManager._checkFileIsSafe src, (err, isSafe) -> @@ -144,6 +149,9 @@ module.exports = OutputCacheManager = removeDir dir, cb , callback + _fileIsHidden: (path) -> + return path?.match(/^\.|\/./)? + _checkFileIsSafe: (src, callback = (error, isSafe) ->) -> # check if we have a valid file to copy into the cache fs.stat src, (err, stats) -> diff --git a/services/clsi/app/coffee/OutputFileFinder.coffee b/services/clsi/app/coffee/OutputFileFinder.coffee index 80bd07f2cb..4b07f6e13a 100644 --- a/services/clsi/app/coffee/OutputFileFinder.coffee +++ b/services/clsi/app/coffee/OutputFileFinder.coffee @@ -29,8 +29,10 @@ module.exports = OutputFileFinder = callback = (error, fileList) -> _callback(error, fileList) _callback = () -> - - args = [directory, "-name", ".*", "-prune", "-o", "-type", "f", "-print"] + + # don't include clsi-specific files/directories in the output list + EXCLUDE_DIRS = ["-name", ".cache", "-o", "-name", ".archive","-o", "-name", ".project-*"] + args = [directory, "(", EXCLUDE_DIRS..., ")", "-prune", "-o", "-type", "f", "-print"] logger.log args: args, "running find command" proc = spawn("find", args) diff --git a/services/clsi/app/coffee/ResourceStateManager.coffee b/services/clsi/app/coffee/ResourceStateManager.coffee index b894701a6a..fbd4c67736 100644 --- a/services/clsi/app/coffee/ResourceStateManager.coffee +++ b/services/clsi/app/coffee/ResourceStateManager.coffee @@ -50,14 +50,19 @@ module.exports = ResourceStateManager = resources = ({path: path} for path in resourceList) callback(null, resources) - checkResourceFiles: (resources, allFiles, directory, callback = (error) ->) -> + checkResourceFiles: (resources, allFiles, basePath, callback = (error) ->) -> + # check the paths are all relative to current directory + for file in resources or [] + for dir in file?.path?.split('/') + if dir == '..' + return callback new Error("relative path in resource file list") # check if any of the input files are not present in list of files seenFile = {} for file in allFiles seenFile[file] = true missingFiles = (resource.path for resource in resources when not seenFile[resource.path]) - if missingFiles.length > 0 - logger.err missingFiles:missingFiles, dir:directory, allFiles:allFiles, resources:resources, "missing input files for project" + if missingFiles?.length > 0 + logger.err missingFiles:missingFiles, basePath:basePath, allFiles:allFiles, resources:resources, "missing input files for project" return callback new Errors.FilesOutOfSyncError("resource files missing in incremental update") else callback() diff --git a/services/clsi/app/coffee/ResourceWriter.coffee b/services/clsi/app/coffee/ResourceWriter.coffee index f9e90b036a..55970ee850 100644 --- a/services/clsi/app/coffee/ResourceWriter.coffee +++ b/services/clsi/app/coffee/ResourceWriter.coffee @@ -78,8 +78,6 @@ module.exports = ResourceWriter = should_delete = true if path.match(/^output\./) or path.match(/\.aux$/) or path.match(/^cache\//) # knitr cache should_delete = false - if path == '.project-sync-state' - should_delete = false if path == "output.pdf" or path == "output.dvi" or path == "output.log" or path == "output.xdv" should_delete = true if path == "output.tex" # created by TikzManager if present in output files diff --git a/services/clsi/test/unit/coffee/ResourceStateManagerTests.coffee b/services/clsi/test/unit/coffee/ResourceStateManagerTests.coffee new file mode 100644 index 0000000000..e5e1c13011 --- /dev/null +++ b/services/clsi/test/unit/coffee/ResourceStateManagerTests.coffee @@ -0,0 +1,109 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +should = require('chai').should() +modulePath = require('path').join __dirname, '../../../app/js/ResourceStateManager' +Path = require "path" +Errors = require "../../../app/js/Errors" + +describe "ResourceStateManager", -> + beforeEach -> + @ResourceStateManager = SandboxedModule.require modulePath, requires: + "fs": @fs = {} + "logger-sharelatex": {log: sinon.stub(), err: sinon.stub()} + "./SafeReader": @SafeReader = {} + @basePath = "/path/to/write/files/to" + @resources = [ + {path: "resource-1-mock"} + {path: "resource-2-mock"} + {path: "resource-3-mock"} + ] + @state = "1234567890" + @resourceFileName = "#{@basePath}/.project-sync-state" + @resourceFileContents = "#{@resources[0].path}\n#{@resources[1].path}\n#{@resources[2].path}\nstateHash:#{@state}" + @callback = sinon.stub() + + describe "saveProjectState", -> + beforeEach -> + @fs.writeFile = sinon.stub().callsArg(2) + + describe "when the state is specified", -> + beforeEach -> + @ResourceStateManager.saveProjectState(@state, @resources, @basePath, @callback) + + it "should write the resource list to disk", -> + @fs.writeFile + .calledWith(@resourceFileName, @resourceFileContents) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true + + describe "when the state is undefined", -> + beforeEach -> + @state = undefined + @fs.unlink = sinon.stub().callsArg(1) + @ResourceStateManager.saveProjectState(@state, @resources, @basePath, @callback) + + it "should unlink the resource file", -> + @fs.unlink + .calledWith(@resourceFileName) + .should.equal true + + it "should not write the resource list to disk", -> + @fs.writeFile.called.should.equal false + + it "should call the callback", -> + @callback.called.should.equal true + + describe "checkProjectStateMatches", -> + + describe "when the state matches", -> + beforeEach -> + @SafeReader.readFile = sinon.stub().callsArgWith(3, null, @resourceFileContents) + @ResourceStateManager.checkProjectStateMatches(@state, @basePath, @callback) + + it "should read the resource file", -> + @SafeReader.readFile + .calledWith(@resourceFileName) + .should.equal true + + it "should call the callback with the results", -> + @callback.calledWithMatch(null, @resources).should.equal true + + describe "when the state does not match", -> + beforeEach -> + @SafeReader.readFile = sinon.stub().callsArgWith(3, null, @resourceFileContents) + @ResourceStateManager.checkProjectStateMatches("not-the-original-state", @basePath, @callback) + + it "should call the callback with an error", -> + error = new Errors.FilesOutOfSyncError("invalid state for incremental update") + @callback.calledWith(error).should.equal true + + describe "checkResourceFiles", -> + describe "when all the files are present", -> + beforeEach -> + @allFiles = [ @resources[0].path, @resources[1].path, @resources[2].path] + @ResourceStateManager.checkResourceFiles(@resources, @allFiles, @basePath, @callback) + + it "should call the callback", -> + @callback.calledWithExactly().should.equal true + + describe "when there is a missing file", -> + beforeEach -> + @allFiles = [ @resources[0].path, @resources[1].path] + @fs.stat = sinon.stub().callsArgWith(1, new Error()) + @ResourceStateManager.checkResourceFiles(@resources, @allFiles, @basePath, @callback) + + it "should call the callback with an error", -> + error = new Errors.FilesOutOfSyncError("resource files missing in incremental update") + @callback.calledWith(error).should.equal true + + describe "when a resource contains a relative path", -> + beforeEach -> + @resources[0].path = "../foo/bar.tex" + @allFiles = [ @resources[0].path, @resources[1].path, @resources[2].path] + @ResourceStateManager.checkResourceFiles(@resources, @allFiles, @basePath, @callback) + + it "should call the callback with an error", -> + @callback.calledWith(new Error("relative path in resource file list")).should.equal true +