| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- logger = require "logger-sharelatex"
- metrics = require "metrics-sharelatex"
- fs = require "fs"
- Path = require "path"
- fse = require "fs-extra"
- yauzl = require "yauzl"
- Settings = require "settings-sharelatex"
- _ = require("underscore")
- ONE_MEG = 1024 * 1024
- module.exports = ArchiveManager =
- _isZipTooLarge: (source, callback = (err, isTooLarge)->)->
- callback = _.once callback
- totalSizeInBytes = null
- yauzl.open source, {lazyEntries: true}, (err, zipfile) ->
- return callback(err) if err?
- if Settings.maxEntitiesPerProject? and zipfile.entryCount > Settings.maxEntitiesPerProject
- return callback(null, true) # too many files in zip file
- zipfile.on "error", callback
- # read all the entries
- zipfile.readEntry()
- zipfile.on "entry", (entry) ->
- totalSizeInBytes += entry.uncompressedSize
- zipfile.readEntry() # get the next entry
- # no more entries to read
- zipfile.on "end", () ->
- if !totalSizeInBytes? or isNaN(totalSizeInBytes)
- logger.err source:source, totalSizeInBytes:totalSizeInBytes, "error getting bytes of zip"
- return callback(new Error("error getting bytes of zip"))
- isTooLarge = totalSizeInBytes > (ONE_MEG * 300)
- callback(null, isTooLarge)
- _checkFilePath: (entry, destination, callback = (err, destFile) ->) ->
- # check if the entry is a directory
- endsWithSlash = /\/$/
- if endsWithSlash.test(entry.fileName)
- return callback() # don't give a destfile for directory
- # check that the file does not use a relative path
- for dir in entry.fileName.split('/')
- if dir == '..'
- return callback(new Error("relative path"))
- # check that the destination file path is normalized
- dest = "#{destination}/#{entry.fileName}"
- if dest != Path.normalize(dest)
- return callback(new Error("unnormalized path"))
- else
- return callback(null, dest)
- _writeFileEntry: (zipfile, entry, destFile, callback = (err)->) ->
- callback = _.once callback
- zipfile.openReadStream entry, (err, readStream) ->
- return callback(err) if err?
- readStream.on "error", callback
- readStream.on "end", callback
- errorHandler = (err) -> # clean up before calling callback
- readStream.unpipe()
- readStream.destroy()
- callback(err)
- fse.ensureDir Path.dirname(destFile), (err) ->
- return errorHandler(err) if err?
- writeStream = fs.createWriteStream destFile
- writeStream.on 'error', errorHandler
- readStream.pipe(writeStream)
- _extractZipFiles: (source, destination, callback = (err) ->) ->
- callback = _.once callback
- yauzl.open source, {lazyEntries: true}, (err, zipfile) ->
- return callback(err) if err?
- zipfile.on "error", callback
- # read all the entries
- zipfile.readEntry()
- zipfile.on "entry", (entry) ->
- logger.log {source:source, fileName: entry.fileName}, "processing zip file entry"
- ArchiveManager._checkFilePath entry, destination, (err, destFile) ->
- if err?
- logger.warn err:err, source:source, destination:destination, "skipping bad file path"
- zipfile.readEntry() # bad path, just skip to the next file
- return
- if destFile? # only write files
- ArchiveManager._writeFileEntry zipfile, entry, destFile, (err) ->
- if err?
- logger.error err:err, source:source, destFile:destFile, "error unzipping file entry"
- zipfile.close() # bail out, stop reading file entries
- return callback(err)
- else
- zipfile.readEntry() # continue to the next file
- else # if it's a directory, continue
- zipfile.readEntry()
- # no more entries to read
- zipfile.on "end", callback
- extractZipArchive: (source, destination, _callback = (err) ->) ->
- callback = (args...) ->
- _callback(args...)
- _callback = () ->
- ArchiveManager._isZipTooLarge source, (err, isTooLarge)->
- if err?
- logger.err err:err, "error checking size of zip file"
- return callback(err)
- if isTooLarge
- return callback(new Error("zip_too_large"))
- timer = new metrics.Timer("unzipDirectory")
- logger.log source: source, destination: destination, "unzipping file"
- ArchiveManager._extractZipFiles source, destination, (err) ->
- timer.done()
- if err?
- logger.error {err, source, destination}, "unzip failed"
- callback(err)
- else
- callback()
- findTopLevelDirectory: (directory, callback = (error, topLevelDir) ->) ->
- fs.readdir directory, (error, files) ->
- return callback(error) if error?
- if files.length == 1
- childPath = Path.join(directory, files[0])
- fs.stat childPath, (error, stat) ->
- return callback(error) if error?
- if stat.isDirectory()
- return callback(null, childPath)
- else
- return callback(null, directory)
- else
- return callback(null, directory)
|