Merge pull request #1352 from sharelatex/spd-zip-files-with-backslashes

Handle import of zip files that include filenames with backslashes

GitOrigin-RevId: 9f84cf6e0a648ee04bac89fe385931d603709a41
This commit is contained in:
James Allen 2019-01-07 11:18:10 +00:00 committed by sharelatex
parent 1bddd03335
commit e624f697d2
7 changed files with 133 additions and 63 deletions

View file

@ -38,16 +38,18 @@ module.exports = ArchiveManager =
callback(null, isTooLarge) callback(null, isTooLarge)
_checkFilePath: (entry, destination, callback = (err, destFile) ->) -> _checkFilePath: (entry, destination, callback = (err, destFile) ->) ->
# transform backslashes to forwardslashes to accommodate badly-behaved zip archives
transformedFilename = entry.fileName.replace(/\\/g, '/')
# check if the entry is a directory # check if the entry is a directory
endsWithSlash = /\/$/ endsWithSlash = /\/$/
if endsWithSlash.test(entry.fileName) if endsWithSlash.test(transformedFilename)
return callback() # don't give a destfile for directory return callback() # don't give a destfile for directory
# check that the file does not use a relative path # check that the file does not use a relative path
for dir in entry.fileName.split('/') for dir in transformedFilename.split('/')
if dir == '..' if dir == '..'
return callback(new Error("relative path")) return callback(new Error("relative path"))
# check that the destination file path is normalized # check that the destination file path is normalized
dest = "#{destination}/#{entry.fileName}" dest = "#{destination}/#{transformedFilename}"
if dest != Path.normalize(dest) if dest != Path.normalize(dest)
return callback(new Error("unnormalized path")) return callback(new Error("unnormalized path"))
else else

View file

@ -1936,7 +1936,7 @@
"readable-stream": { "readable-stream": {
"version": "2.3.6", "version": "2.3.6",
"from": "readable-stream@>=2.2.2 <3.0.0", "from": "readable-stream@>=2.2.2 <3.0.0",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz" "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz"
}, },
"string_decoder": { "string_decoder": {
"version": "1.1.1", "version": "1.1.1",
@ -2107,9 +2107,9 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz"
}, },
"cookies": { "cookies": {
"version": "0.7.1", "version": "0.7.3",
"from": "cookies@>=0.2.2", "from": "cookies@>=0.2.2",
"resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.1.tgz", "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.3.tgz",
"dev": true "dev": true
}, },
"copy-descriptor": { "copy-descriptor": {
@ -2629,9 +2629,9 @@
"dev": true "dev": true
}, },
"domelementtype": { "domelementtype": {
"version": "1.3.0", "version": "1.3.1",
"from": "domelementtype@>=1.3.0 <2.0.0", "from": "domelementtype@>=1.3.0 <2.0.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
"dev": true "dev": true
}, },
"domhandler": { "domhandler": {
@ -2835,9 +2835,9 @@
"dev": true "dev": true
}, },
"entities": { "entities": {
"version": "1.1.1", "version": "1.1.2",
"from": "entities@>=1.1.1 <2.0.0", "from": "entities@>=1.1.1 <2.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
"dev": true "dev": true
}, },
"errno": { "errno": {
@ -3603,9 +3603,9 @@
} }
}, },
"fd-slicer": { "fd-slicer": {
"version": "1.0.1", "version": "1.1.0",
"from": "fd-slicer@>=1.0.1 <1.1.0", "from": "fd-slicer@>=1.1.0 <1.2.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz" "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz"
}, },
"figures": { "figures": {
"version": "2.0.0", "version": "2.0.0",
@ -4798,7 +4798,7 @@
"readable-stream": { "readable-stream": {
"version": "2.3.6", "version": "2.3.6",
"from": "readable-stream@^2.0.1", "from": "readable-stream@^2.0.1",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"dev": true "dev": true
}, },
"string_decoder": { "string_decoder": {
@ -4826,33 +4826,21 @@
"dev": true "dev": true
}, },
"htmlparser2": { "htmlparser2": {
"version": "3.9.2", "version": "3.10.0",
"from": "htmlparser2@>=3.9.0 <4.0.0", "from": "htmlparser2@>=3.10.0 <4.0.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"isarray": {
"version": "1.0.0",
"from": "isarray@~1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"dev": true
},
"process-nextick-args": {
"version": "2.0.0",
"from": "process-nextick-args@>=2.0.0 <2.1.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
"dev": true
},
"readable-stream": { "readable-stream": {
"version": "2.3.6", "version": "3.1.1",
"from": "readable-stream@>=2.0.2 <3.0.0", "from": "readable-stream@>=3.0.6 <4.0.0",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz",
"dev": true "dev": true
}, },
"string_decoder": { "string_decoder": {
"version": "1.1.1", "version": "1.2.0",
"from": "string_decoder@>=1.1.1 <1.2.0", "from": "string_decoder@>=1.1.1 <2.0.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz",
"dev": true "dev": true
} }
} }
@ -5844,9 +5832,9 @@
} }
}, },
"keygrip": { "keygrip": {
"version": "1.0.2", "version": "1.0.3",
"from": "keygrip@>=1.0.2 <1.1.0", "from": "keygrip@>=1.0.3 <1.1.0",
"resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.2.tgz", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.3.tgz",
"dev": true "dev": true
}, },
"killable": { "killable": {
@ -7160,7 +7148,7 @@
"readable-stream": { "readable-stream": {
"version": "2.3.6", "version": "2.3.6",
"from": "readable-stream@^2.0.1", "from": "readable-stream@^2.0.1",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"dev": true "dev": true
}, },
"string_decoder": { "string_decoder": {
@ -7811,7 +7799,7 @@
"readable-stream": { "readable-stream": {
"version": "2.3.6", "version": "2.3.6",
"from": "readable-stream@^2.3.3", "from": "readable-stream@^2.3.3",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"dev": true "dev": true
}, },
"string_decoder": { "string_decoder": {
@ -8168,7 +8156,7 @@
"readable-stream": { "readable-stream": {
"version": "2.3.6", "version": "2.3.6",
"from": "readable-stream@^2.0.2", "from": "readable-stream@^2.0.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"dev": true "dev": true
}, },
"readdirp": { "readdirp": {
@ -10065,9 +10053,9 @@
} }
}, },
"sanitize-html": { "sanitize-html": {
"version": "1.18.2", "version": "1.20.0",
"from": "sanitize-html@>=1.14.1 <2.0.0", "from": "sanitize-html@>=1.14.1 <2.0.0",
"resolved": "http://registry.npmjs.org/sanitize-html/-/sanitize-html-1.18.2.tgz", "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.20.0.tgz",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"ansi-styles": { "ansi-styles": {
@ -10089,9 +10077,9 @@
"dev": true "dev": true
}, },
"postcss": { "postcss": {
"version": "6.0.22", "version": "7.0.7",
"from": "postcss@>=6.0.14 <7.0.0", "from": "postcss@>=7.0.5 <8.0.0",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.22.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz",
"dev": true "dev": true
}, },
"source-map": { "source-map": {
@ -10101,9 +10089,9 @@
"dev": true "dev": true
}, },
"supports-color": { "supports-color": {
"version": "5.4.0", "version": "5.5.0",
"from": "supports-color@>=5.3.0 <6.0.0", "from": "supports-color@>=5.3.0 <6.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"dev": true "dev": true
} }
} }
@ -10814,7 +10802,7 @@
"readable-stream": { "readable-stream": {
"version": "2.3.6", "version": "2.3.6",
"from": "readable-stream@^2.0.2", "from": "readable-stream@^2.0.2",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"dev": true "dev": true
}, },
"string_decoder": { "string_decoder": {
@ -10890,7 +10878,7 @@
"readable-stream": { "readable-stream": {
"version": "2.3.6", "version": "2.3.6",
"from": "readable-stream@^2.3.0", "from": "readable-stream@^2.3.0",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"dev": true "dev": true
}, },
"string_decoder": { "string_decoder": {
@ -11278,13 +11266,19 @@
"translations-sharelatex": { "translations-sharelatex": {
"version": "0.1.4", "version": "0.1.4",
"from": "git+https://github.com/sharelatex/translations-sharelatex.git#master", "from": "git+https://github.com/sharelatex/translations-sharelatex.git#master",
"resolved": "git+https://github.com/sharelatex/translations-sharelatex.git#c3dbe7869c594cb91cd366020f8b69e50a9405c9", "resolved": "git+https://github.com/sharelatex/translations-sharelatex.git#beea1036cdf3adf41cd41e73fcfd6a5a70f83763",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"async": { "async": {
"version": "2.6.0", "version": "2.6.1",
"from": "async@>=2.1.4 <3.0.0", "from": "async@>=2.1.4 <3.0.0",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
"dev": true
},
"lodash": {
"version": "4.17.11",
"from": "lodash@>=4.17.10 <5.0.0",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"dev": true "dev": true
} }
} }
@ -12209,13 +12203,13 @@
"readable-stream": { "readable-stream": {
"version": "2.3.6", "version": "2.3.6",
"from": "readable-stream@^2.0.2", "from": "readable-stream@^2.0.2",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"dev": true "dev": true
}, },
"readdirp": { "readdirp": {
"version": "2.1.0", "version": "2.1.0",
"from": "readdirp@>=2.0.0 <3.0.0", "from": "readdirp@>=2.0.0 <3.0.0",
"resolved": "http://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz",
"dev": true "dev": true
}, },
"string_decoder": { "string_decoder": {
@ -12413,7 +12407,7 @@
"chokidar": { "chokidar": {
"version": "2.0.3", "version": "2.0.3",
"from": "chokidar@>=2.0.0 <3.0.0", "from": "chokidar@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz", "resolved": "http://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz",
"dev": true "dev": true
}, },
"cliui": { "cliui": {
@ -13228,9 +13222,9 @@
} }
}, },
"yauzl": { "yauzl": {
"version": "2.9.1", "version": "2.10.0",
"from": "yauzl@>=2.8.0 <3.0.0", "from": "yauzl@2.10.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.9.1.tgz" "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz"
}, },
"yeast": { "yeast": {
"version": "0.1.2", "version": "0.1.2",

View file

@ -104,7 +104,7 @@
"v8-profiler": "^5.2.3", "v8-profiler": "^5.2.3",
"valid-url": "^1.0.9", "valid-url": "^1.0.9",
"xml2js": "0.2.0", "xml2js": "0.2.0",
"yauzl": "^2.8.0" "yauzl": "^2.10.0"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^6.6.1", "autoprefixer": "^6.6.1",

View file

@ -204,6 +204,54 @@ describe "ProjectStructureChanges", ->
expect(project.name).to.match @test_project_match expect(project.name).to.match @test_project_match
done() done()
describe "uploading a project with a shared top-level folder", ->
before (done) ->
MockDocUpdaterApi.clearProjectStructureUpdates()
zip_file = fs.createReadStream(Path.resolve(__dirname + '/../files/test_project_with_shared_top_level_folder.zip'))
@owner.request.post {
uri: "project/new/upload",
formData:
qqfile: zip_file
}, (error, res, body) =>
throw error if error?
if res.statusCode < 200 || res.statusCode >= 300
throw new Error("failed to upload project #{res.statusCode}")
@uploaded_project_id = JSON.parse(body).project_id
done()
it "should not create the top-level folder", (done) ->
ProjectGetter.getProject @uploaded_project_id, (error, project) ->
expect(error).not.to.exist
expect(project.rootFolder[0].folders.length).to.equal 0
expect(project.rootFolder[0].docs.length).to.equal 2
done()
describe "uploading a project with backslashes in the path names", ->
before (done) ->
MockDocUpdaterApi.clearProjectStructureUpdates()
zip_file = fs.createReadStream(Path.resolve(__dirname + '/../files/test_project_with_backslash_in_filename.zip'))
@owner.request.post {
uri: "project/new/upload",
formData:
qqfile: zip_file
}, (error, res, body) =>
throw error if error?
if res.statusCode < 200 || res.statusCode >= 300
throw new Error("failed to upload project #{res.statusCode}")
@uploaded_project_id = JSON.parse(body).project_id
done()
it "should set the project name from the zip contents", (done) ->
ProjectGetter.getProject @uploaded_project_id, (error, project) ->
expect(error).not.to.exist
expect(project.rootFolder[0].folders[0].name).to.equal('styles')
expect(project.rootFolder[0].folders[0].docs[0].name).to.equal('ao.sty')
done()
describe "uploading a file", -> describe "uploading a file", ->
beforeEach (done) -> beforeEach (done) ->
MockDocUpdaterApi.clearProjectStructureUpdates() MockDocUpdaterApi.clearProjectStructureUpdates()

View file

@ -111,12 +111,39 @@ describe "ArchiveManager", ->
@zipfile.emit "entry", {fileName: "foo/./testfile.txt"} @zipfile.emit "entry", {fileName: "foo/./testfile.txt"}
@zipfile.emit "end" @zipfile.emit "end"
it "should not write try to read the file entry", -> it "should not try to read the file entry", ->
@zipfile.openReadStream.called.should.equal false @zipfile.openReadStream.called.should.equal false
it "should log out a warning", -> it "should log out a warning", ->
@logger.warn.called.should.equal true @logger.warn.called.should.equal true
describe "with backslashes in the path", ->
beforeEach (done) ->
@readStream = new events.EventEmitter
@readStream.pipe = sinon.stub()
@writeStream = new events.EventEmitter
@fs.createWriteStream = sinon.stub().returns @writeStream
@zipfile.openReadStream = sinon.stub().callsArgWith(1, null, @readStream)
@fse.ensureDir = sinon.stub().callsArg(1)
@ArchiveManager.extractZipArchive @source, @destination, (error) =>
@callback(error)
done()
@zipfile.emit "entry", {fileName: 'wombat\\foo.tex'}
@zipfile.emit "entry", {fileName: 'potato\\bar.tex'}
@zipfile.emit "end"
it "should read the file entry with its original path", ->
@zipfile.openReadStream.should.be.calledWith({fileName: 'wombat\\foo.tex'})
@zipfile.openReadStream.should.be.calledWith({fileName: 'potato\\bar.tex'})
it "should treat the backslashes as a directory separator when creating the directory", ->
@fse.ensureDir.should.be.calledWith("#{@destination}/wombat");
@fse.ensureDir.should.be.calledWith("#{@destination}/potato");
it "should treat the backslashes as a directory separator when creating the file", ->
@fs.createWriteStream.should.be.calledWith("#{@destination}/wombat/foo.tex");
@fs.createWriteStream.should.be.calledWith("#{@destination}/potato/bar.tex");
describe "with a directory entry", -> describe "with a directory entry", ->
beforeEach (done) -> beforeEach (done) ->
@zipfile.openReadStream = sinon.stub() @zipfile.openReadStream = sinon.stub()
@ -126,7 +153,7 @@ describe "ArchiveManager", ->
@zipfile.emit "entry", {fileName: "testdir/"} @zipfile.emit "entry", {fileName: "testdir/"}
@zipfile.emit "end" @zipfile.emit "end"
it "should not write try to read the entry", -> it "should not try to read the entry", ->
@zipfile.openReadStream.called.should.equal false @zipfile.openReadStream.called.should.equal false
it "should not log out a warning", -> it "should not log out a warning", ->
@ -295,4 +322,3 @@ describe "ArchiveManager", ->
@callback @callback
.calledWith(null, @directory + "/folder") .calledWith(null, @directory + "/folder")
.should.equal true .should.equal true