fs-plug
Version:
tcp server for simple file sharing
207 lines (174 loc) • 5.65 kB
JavaScript
var { createReadStream, createWriteStream, lstat, stat } = require("fs")
var { createGzip, createGunzip } = require("zlib")
var { timingSafeEqual } = require("crypto")
var { connect, Server } = require("net")
var { inherits } = require("util")
var { extract, pack } = require("tar-fs")
var pump = require("pump")
var rimraf = require("rimraf")
var ERR = {
BLACKLISTED_RESOURCE: Error("request for non-whitelisted resource"),
UNSUPPORTED_RESOURCE: Error("request for unsupported resource"),
UNAUTHORIZED: Error("unathorized access attempt"),
TIMEOUT: Error("consume timeout"),
NULL: Error("zero bytes consumed")
}
function noop() {}
function xstat(entry, opts, cb) {
opts.dereference ? stat(entry, cb) : lstat(entry, cb)
}
function Plug(opts, onconsumer) {
if (!(this instanceof Plug)) return new Plug(opts, onconsumer)
Server.call(this)
if (typeof opts === "function") {
onconsumer = opts
opts = {}
}
if (!opts) opts = {}
if (!onconsumer) onconsumer = noop
this._opts = opts
this._opts.dereference = !!opts.dereference
this._opts.timeout = typeof opts.timeout === "number" ? opts.timeout : 500
this._opts.interval = typeof opts.interval === "number" ? opts.interval : 250
this._opts.enforceWhitelist = opts.enforceWhitelist !== false
this._opts.passphrase =
typeof opts.passphrase === "string" || Buffer.isBuffer(opts.passphrase)
? Buffer.from(opts.passphrase)
: null
this._supplied = 0
this._consumed = 0
this._whitelist = new Set(opts.whitelist)
var self = this
Object.defineProperty(self, "supplied", {
get() {
return self._supplied
},
set(count) {
self._supplied = count
}
})
Object.defineProperty(self, "consumed", {
get() {
return self._consumed
},
set(count) {
self._consumed = count
}
})
self.on("connection", function(socket) {
socket.once("data", function(buf) {
try {
var preflight = JSON.parse(buf)
} catch (err) {
socket.destroy()
return onconsumer(err)
}
if (self._opts.passphrase) {
if (
typeof preflight.passphrase !== "string" ||
preflight.passphrase.length !== self._opts.passphrase.length
) {
socket.destroy()
return onconsumer(ERR.UNAUTHORIZED)
}
if (
!timingSafeEqual(
Buffer.from(preflight.passphrase),
self._opts.passphrase
)
) {
socket.destroy()
return onconsumer(ERR.UNAUTHORIZED)
}
}
if (self._opts.enforceWhitelist && !self._whitelist.has(preflight.path)) {
socket.destroy()
return onconsumer(ERR.BLACKLISTED_RESOURCE)
}
xstat(preflight.path, self._opts, function(err, stats) {
if (err) {
socket.destroy()
return onconsumer(err)
}
var readStream
if (stats.isDirectory() && preflight.only) {
readStream = pack(preflight.path, { entries: preflight.only })
} else if (stats.isDirectory()) {
readStream = pack(preflight.path)
} else if (stats.isFile()) {
readStream = createReadStream(preflight.path)
} else {
return onconsumer(ERR.UNSUPPORTED_RESOURCE)
}
var interval = setInterval(function() {
self.emit("bytes-supplied", socket.bytesWritten)
}, self._opts.interval)
pump(readStream, createGzip(), socket, function(err) {
clearInterval(interval)
if (err) return onconsumer(err)
self._supplied++
onconsumer(null, preflight.path)
})
})
})
})
}
inherits(Plug, Server)
Plug.prototype.consume = function(conf, cb) {
var self = this
var preflight = {
path: conf.remotePath,
only: conf.only,
passphrase: conf.passphrase
}
if (!cb) cb = noop
var socket = connect(
conf.port,
conf.host,
function() {
socket.write(JSON.stringify(preflight), function() {
var dump =
conf.type === "file"
? createWriteStream(conf.localPath)
: extract(conf.localPath)
socket.once("readable", function() {
var interval = setInterval(function() {
self.emit("bytes-consumed", dump.bytesWritten)
}, self._opts.interval)
pump(socket, createGunzip(), dump, function(err) {
clearInterval(interval)
if (err) return cb(err)
if (!socket.bytesRead) return cb(ERR.NULL)
self._consumed++
cb(null, conf.localPath)
})
setTimeout(function() {
if (!socket.bytesRead) {
clearInterval(interval)
socket.destroy(ERR.TIMEOUT)
rimraf(conf.localPath, noop)
}
}, self._opts.timeout)
})
})
}
)
}
Plug.prototype.whitelist = function(filepath) {
return this._whitelist.add(filepath)
}
Plug.prototype.blacklist = function(filepath) {
return this._whitelist.delete(filepath)
}
Plug.prototype.enforceWhitelist = function(v) {
this._opts.enforceWhitelist = !!v
}
Plug.prototype.clearWhitelist = function() {
this._whitelist.clear()
}
Plug.prototype.setPassphrase = function(passphrase) {
if (typeof passphrase === "string" || Buffer.isBuffer(passphrase)) {
this._opts.passphrase = Buffer.from(passphrase)
}
}
module.exports = Plug