This commit is contained in:
Henry Oswald 2015-04-09 17:16:23 +01:00
commit 158b640b59
6 changed files with 105 additions and 19 deletions

View file

@ -1,13 +1,13 @@
_ = require("underscore") _ = require("underscore")
metrics = require("metrics-sharelatex") metrics = require("metrics-sharelatex")
logger = require("logger-sharelatex") logger = require("logger-sharelatex")
exec = require('child_process').exec safe_exec = require("./SafeExec")
approvedFormats = ["png"] approvedFormats = ["png"]
fourtySeconds = 40 * 1000 fourtySeconds = 40 * 1000
childProcessOpts = childProcessOpts =
killSignal: "SIGKILL" killSignal: "SIGTERM"
timeout: fourtySeconds timeout: fourtySeconds
@ -23,8 +23,7 @@ module.exports =
return callback err return callback err
width = "600x" width = "600x"
args = "nice convert -define pdf:fit-page=#{width} -flatten -density 300 #{sourcePath} #{destPath}" args = "nice convert -define pdf:fit-page=#{width} -flatten -density 300 #{sourcePath} #{destPath}"
console.log args safe_exec args, childProcessOpts, (err, stdout, stderr)->
exec args, childProcessOpts, (err, stdout, stderr)->
timer.done() timer.done()
if err? if err?
logger.err err:err, stderr:stderr, sourcePath:sourcePath, requestedFormat:requestedFormat, destPath:destPath, "something went wrong converting file" logger.err err:err, stderr:stderr, sourcePath:sourcePath, requestedFormat:requestedFormat, destPath:destPath, "something went wrong converting file"
@ -38,7 +37,7 @@ module.exports =
sourcePath = "#{sourcePath}[0]" sourcePath = "#{sourcePath}[0]"
width = "260x" width = "260x"
args = "nice convert -flatten -background white -density 300 -define pdf:fit-page=#{width} #{sourcePath} -resize #{width} #{destPath}" args = "nice convert -flatten -background white -density 300 -define pdf:fit-page=#{width} #{sourcePath} -resize #{width} #{destPath}"
exec args, childProcessOpts, (err, stdout, stderr)-> safe_exec args, childProcessOpts, (err, stdout, stderr)->
if err? if err?
logger.err err:err, stderr:stderr, sourcePath:sourcePath, "something went wrong converting file to thumbnail" logger.err err:err, stderr:stderr, sourcePath:sourcePath, "something went wrong converting file to thumbnail"
else else
@ -51,7 +50,7 @@ module.exports =
sourcePath = "#{sourcePath}[0]" sourcePath = "#{sourcePath}[0]"
width = "548x" width = "548x"
args = "nice convert -flatten -background white -density 300 -define pdf:fit-page=#{width} #{sourcePath} -resize #{width} #{destPath}" args = "nice convert -flatten -background white -density 300 -define pdf:fit-page=#{width} #{sourcePath} -resize #{width} #{destPath}"
exec args, childProcessOpts, (err, stdout, stderr)-> safe_exec args, childProcessOpts, (err, stdout, stderr)->
if err? if err?
logger.err err:err, stderr:stderr, sourcePath:sourcePath, destPath:destPath, "something went wrong converting file to preview" logger.err err:err, stderr:stderr, sourcePath:sourcePath, destPath:destPath, "something went wrong converting file to preview"
else else

View file

@ -104,6 +104,9 @@ module.exports =
secret: settings.filestore.s3.secret secret: settings.filestore.s3.secret
bucket: bucketName bucket: bucketName
s3Client.list prefix:key, (err, data)-> s3Client.list prefix:key, (err, data)->
if err?
logger.err err:err, bucketName:bucketName, key:key, "something went wrong listing prefix in aws"
return callback(err)
keys = _.map data.Contents, (entry)-> keys = _.map data.Contents, (entry)->
return entry.Key return entry.Key
s3Client.deleteMultiple keys, callback s3Client.deleteMultiple keys, callback

View file

@ -0,0 +1,43 @@
_ = require("underscore")
logger = require("logger-sharelatex")
child_process = require('child_process')
# execute a command in the same way as 'exec' but with a timeout that
# kills all child processes
#
# we spawn the command with 'detached:true' to make a new process
# group, then we can kill everything in that process group.
module.exports = (command, options, callback = (err, stdout, stderr) ->) ->
# options are {timeout: number-of-milliseconds, killSignal: signal-name}
[cmd, args...] = command.split(' ')
child = child_process.spawn cmd, args, {detached:true}
stdout = ""
stderr = ""
cleanup = _.once (err) ->
clearTimeout killTimer if killTimer?
callback err, stdout, stderr
if options.timeout?
killTimer = setTimeout () ->
try
# use negative process id to kill process group
process.kill -child.pid, options.killSignal || "SIGTERM"
catch error
logger.log process: child.pid, kill_error: error, "error killing process"
, options.timeout
child.on 'close', (code, signal) ->
err = if code then new Error("exit status #{code}") else signal
cleanup err
child.on 'error', (err) ->
cleanup err
child.stdout.on 'data', (chunk) ->
stdout += chunk
child.stderr.on 'data', (chunk) ->
stderr += chunk

View file

@ -1,6 +1,6 @@
{ {
"name": "filestore-sharelatex", "name": "filestore-sharelatex",
"version": "0.1.3", "version": "0.1.4",
"description": "An API for CRUD operations on binary files stored in S3", "description": "An API for CRUD operations on binary files stored in S3",
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -10,10 +10,9 @@ describe "FileConverter", ->
beforeEach -> beforeEach ->
@child_process = @safe_exec = sinon.stub()
exec : sinon.stub()
@converter = SandboxedModule.require modulePath, requires: @converter = SandboxedModule.require modulePath, requires:
'child_process': @child_process "./SafeExec": @safe_exec
"logger-sharelatex": "logger-sharelatex":
log:-> log:->
err:-> err:->
@ -25,43 +24,43 @@ describe "FileConverter", ->
describe "convert", -> describe "convert", ->
it "should convert the source to the requested format", (done)-> it "should convert the source to the requested format", (done)->
@child_process.exec.callsArgWith(2) @safe_exec.callsArgWith(2)
@converter.convert @sourcePath, @format, (err)=> @converter.convert @sourcePath, @format, (err)=>
args = @child_process.exec.args[0][0] args = @safe_exec.args[0][0]
args.indexOf(@sourcePath).should.not.equal -1 args.indexOf(@sourcePath).should.not.equal -1
args.indexOf(@format).should.not.equal -1 args.indexOf(@format).should.not.equal -1
done() done()
it "should return the dest path", (done)-> it "should return the dest path", (done)->
@child_process.exec.callsArgWith(2) @safe_exec.callsArgWith(2)
@converter.convert @sourcePath, @format, (err, destPath)=> @converter.convert @sourcePath, @format, (err, destPath)=>
destPath.should.equal "#{@sourcePath}.#{@format}" destPath.should.equal "#{@sourcePath}.#{@format}"
done() done()
it "should return the error from convert", (done)-> it "should return the error from convert", (done)->
@child_process.exec.callsArgWith(2, @error) @safe_exec.callsArgWith(2, @error)
@converter.convert @sourcePath, @format, (err)=> @converter.convert @sourcePath, @format, (err)=>
err.should.equal @error err.should.equal @error
done() done()
it "should not accapt an non aproved format", (done)-> it "should not accapt an non aproved format", (done)->
@child_process.exec.callsArgWith(2) @safe_exec.callsArgWith(2)
@converter.convert @sourcePath, "ahhhhh", (err)=> @converter.convert @sourcePath, "ahhhhh", (err)=>
expect(err).to.exist expect(err).to.exist
done() done()
describe "thumbnail", -> describe "thumbnail", ->
it "should call converter resize with args", (done)-> it "should call converter resize with args", (done)->
@child_process.exec.callsArgWith(2) @safe_exec.callsArgWith(2)
@converter.thumbnail @sourcePath, (err)=> @converter.thumbnail @sourcePath, (err)=>
args = @child_process.exec.args[0][0] args = @safe_exec.args[0][0]
args.indexOf(@sourcePath).should.not.equal -1 args.indexOf(@sourcePath).should.not.equal -1
done() done()
describe "preview", -> describe "preview", ->
it "should call converter resize with args", (done)-> it "should call converter resize with args", (done)->
@child_process.exec.callsArgWith(2) @safe_exec.callsArgWith(2)
@converter.preview @sourcePath, (err)=> @converter.preview @sourcePath, (err)=>
args = @child_process.exec.args[0][0] args = @safe_exec.args[0][0]
args.indexOf(@sourcePath).should.not.equal -1 args.indexOf(@sourcePath).should.not.equal -1
done() done()

View file

@ -0,0 +1,42 @@
assert = require("chai").assert
sinon = require('sinon')
chai = require('chai')
should = chai.should()
expect = chai.expect
modulePath = "../../../app/js/SafeExec.js"
SandboxedModule = require('sandboxed-module')
describe "SafeExec", ->
beforeEach ->
@safe_exec = SandboxedModule.require modulePath, requires:
"logger-sharelatex":
log:->
err:->
@options = {timeout: 10*1000, killSignal: "SIGTERM" }
describe "safe_exec", ->
it "should execute a valid command", (done) ->
@safe_exec "/bin/echo hello", @options, (err, stdout, stderr) =>
stdout.should.equal "hello\n"
should.not.exist(err)
done()
it "should execute a command with non-zero exit status", (done) ->
@safe_exec "/usr/bin/env false", @options, (err, stdout, stderr) =>
stdout.should.equal ""
stderr.should.equal ""
err.message.should.equal "exit status 1"
done()
it "should handle an invalid command", (done) ->
@safe_exec "/bin/foobar", @options, (err, stdout, stderr) =>
err.code.should.equal "ENOENT"
done()
it "should handle a command that runs too long", (done) ->
@safe_exec "/bin/sleep 10", {timeout: 500, killSignal: "SIGTERM"}, (err, stdout, stderr) =>
err.should.equal "SIGTERM"
done()