Comparing version 0.2.0 to 0.3.0
@@ -1,77 +0,1 @@ | ||
var Channel = (function () { | ||
function Channel(session, ws) { | ||
this.session = session; | ||
this.ws = ws; | ||
this.options = { binary: true }; //WEB: // removed | ||
} | ||
Channel.prototype.start = function () { | ||
var _this = this; | ||
this.ws.on('close', function (code, message) { | ||
//WEB: var code = e.code; | ||
//WEB: var message = e.reason; | ||
_this.log.info("Connection closed:", code, message); | ||
_this.close(1000); // normal close | ||
}); //WEB: }; | ||
this.ws.on('error', function (err) { | ||
//this.emit('error', err); | ||
var name = err.name; //WEB: var name = typeof err; | ||
_this.log.error("Socket error:", err.message, name); | ||
_this.close(1011); // unexpected condition | ||
}); //WEB: }; | ||
this.ws.on('message', function (data, flags) { | ||
var request; | ||
if (flags.binary) { | ||
request = data; //WEB: request = new Uint8Array(message.data); | ||
} | ||
else { | ||
_this.log.error("Text packet received, but not supported yet."); | ||
_this.close(1003); // unsupported data | ||
return; | ||
} | ||
try { | ||
_this.session._process(request); | ||
} | ||
catch (error) { | ||
_this.log.error("Error while processing packet:", error); | ||
_this.close(1011); // unexpected condition | ||
} | ||
}); //WEB: }; | ||
}; | ||
Channel.prototype.send = function (packet) { | ||
var _this = this; | ||
if (this.ws == null) | ||
return; | ||
this.ws.send(packet, this.options, function (err) { | ||
if (typeof err !== 'undefined' && err != null) { | ||
_this.log.error("Error while sending:", err.message, err.name); //WEB: // removed | ||
_this.close(1011); //WEB: // removed | ||
} //WEB: // removed | ||
}); //WEB: // removed | ||
}; | ||
Channel.prototype.close = function (reason) { | ||
if (this.ws == null) | ||
return; | ||
if (typeof reason === 'undefined') | ||
reason = 1000; // normal close | ||
try { | ||
this.ws.close(reason, "closed"); | ||
} | ||
catch (error) { | ||
this.log.error("Error while closing WebSocket:", error); | ||
} | ||
finally { | ||
this.ws = null; | ||
} | ||
try { | ||
this.session._end(); | ||
} | ||
catch (error) { | ||
this.log.error("Error while closing session:", error); | ||
} | ||
finally { | ||
this.session = null; | ||
} | ||
}; | ||
return Channel; | ||
})(); | ||
exports.Channel = Channel; | ||
var fs = require("fs"); | ||
var Path = require("path"); | ||
var misc = require("./fs-misc"); | ||
var Path = misc.Path; | ||
var FileUtil = misc.FileUtil; | ||
var LocalError = (function () { | ||
@@ -15,4 +17,22 @@ function LocalError(message, isPublic) { | ||
} | ||
LocalFilesystem.prototype.checkPath = function (path, name) { | ||
var localPath = Path.create(path, this, name); | ||
var path = localPath.path; | ||
if (path[0] == '~') { | ||
var home = (process.env.HOME || process.env.USERPROFILE || "."); | ||
if (path.length == 1) | ||
return home; | ||
if (path[1] === '/' || (path[1] === '\\' && this.isWindows)) { | ||
path = localPath.join(home, path.substr(2)).path; | ||
} | ||
} | ||
return path; | ||
}; | ||
LocalFilesystem.prototype.open = function (path, flags, attrs, callback) { | ||
var mode = typeof attrs === 'object' ? attrs.mode : undefined; | ||
if (typeof attrs === 'function' && typeof callback === 'undefined') { | ||
callback = attrs; | ||
attrs = null; | ||
} | ||
path = this.checkPath(path, 'path'); | ||
var mode = (attrs && typeof attrs === 'object') ? attrs.mode : undefined; | ||
fs.open(path, flags, mode, function (err, fd) { return callback(err, fd); }); | ||
@@ -48,6 +68,7 @@ //LATER: pay attemtion to attrs other than mode (low priority - many SFTP servers ignore these as well) | ||
if (typeof err === 'undefined' || err == null) { | ||
offset += bytesRead; | ||
length -= bytesRead; | ||
totalBytes += bytesRead; | ||
if (length > 0 && bytesRead > 0) { | ||
offset += bytesRead; | ||
position += bytesRead; | ||
read(); | ||
@@ -58,3 +79,3 @@ return; | ||
if (typeof callback === 'function') | ||
callback(err, totalBytes, buffer.slice(initialOffset, initialOffset + totalBytes)); | ||
callback(err, totalBytes, buffer); | ||
}); | ||
@@ -68,5 +89,6 @@ }; | ||
if (typeof err === 'undefined' || err == null) { | ||
offset += bytesWritten; | ||
length -= bytesWritten; | ||
if (length > 0) { | ||
offset += bytesWritten; | ||
position += bytesWritten; | ||
write(); | ||
@@ -83,2 +105,3 @@ return; | ||
LocalFilesystem.prototype.lstat = function (path, callback) { | ||
path = this.checkPath(path, 'path'); | ||
fs.lstat(path, callback); | ||
@@ -115,2 +138,3 @@ }; | ||
LocalFilesystem.prototype.setstat = function (path, attrs, callback) { | ||
path = this.checkPath(path, 'path'); | ||
var actions = new Array(); | ||
@@ -166,3 +190,7 @@ if (!isNaN(attrs.uid) || !isNaN(attrs.gid)) | ||
LocalFilesystem.prototype.opendir = function (path, callback) { | ||
var _this = this; | ||
path = this.checkPath(path, 'path'); | ||
fs.readdir(path, function (err, files) { | ||
if (files) | ||
files.splice(0, 0, ".", ".."); | ||
if (typeof err !== 'undefined' && err != null) { | ||
@@ -172,3 +200,3 @@ files = null; | ||
else if (Array.isArray(files)) { | ||
files["path"] = path; | ||
files["path"] = new Path(path, _this).normalize(); | ||
err = null; | ||
@@ -194,3 +222,3 @@ } | ||
path = handle.path; | ||
if (typeof path !== 'string') | ||
if (typeof path !== 'object') | ||
err = new LocalError("Invalid handle", true); | ||
@@ -202,8 +230,10 @@ } | ||
} | ||
var windows = this.isWindows; | ||
var items = []; | ||
if (err == null) { | ||
var list = handle; | ||
if (list.length > 0) { | ||
var next = function () { | ||
if (items.length >= 64 || list.length == 0) { | ||
var paths = handle.splice(0, 64); | ||
if (paths.length > 0) { | ||
function next() { | ||
var name = paths.shift(); | ||
if (!name) { | ||
if (typeof callback == 'function') { | ||
@@ -214,4 +244,3 @@ callback(null, (items.length > 0) ? items : false); | ||
} | ||
var name = list.shift(); | ||
var itemPath = Path.join(path, name); | ||
var itemPath = path.join(name).path; | ||
fs.stat(itemPath, function (err, stats) { | ||
@@ -221,7 +250,13 @@ if (typeof err !== 'undefined' && err != null) { | ||
else { | ||
items.push({ filename: name, stats: stats }); | ||
// | ||
items.push({ | ||
filename: name, | ||
longname: FileUtil.toString(name, stats), | ||
stats: stats | ||
}); | ||
} | ||
next(); | ||
}); | ||
}; | ||
} | ||
; | ||
next(); | ||
@@ -238,6 +273,12 @@ return; | ||
LocalFilesystem.prototype.unlink = function (path, callback) { | ||
path = this.checkPath(path, 'path'); | ||
fs.unlink(path, callback); | ||
}; | ||
LocalFilesystem.prototype.mkdir = function (path, attrs, callback) { | ||
var mode = typeof attrs === 'object' ? attrs.mode : undefined; | ||
path = this.checkPath(path, 'path'); | ||
if (typeof attrs === 'function' && typeof callback === 'undefined') { | ||
callback = attrs; | ||
attrs = null; | ||
} | ||
var mode = (attrs && typeof attrs === 'object') ? attrs.mode : undefined; | ||
fs.mkdir(path, mode, callback); | ||
@@ -247,23 +288,36 @@ //LATER: pay attemtion to attrs other than mode (low priority - many SFTP servers ignore these as well) | ||
LocalFilesystem.prototype.rmdir = function (path, callback) { | ||
path = this.checkPath(path, 'path'); | ||
fs.rmdir(path, callback); | ||
}; | ||
LocalFilesystem.prototype.realpath = function (path, callback) { | ||
path = this.checkPath(path, 'path'); | ||
fs.realpath(path, callback); | ||
}; | ||
LocalFilesystem.prototype.stat = function (path, callback) { | ||
path = this.checkPath(path, 'path'); | ||
fs.stat(path, callback); | ||
}; | ||
LocalFilesystem.prototype.rename = function (oldPath, newPath, callback) { | ||
oldPath = this.checkPath(oldPath, 'oldPath'); | ||
newPath = this.checkPath(newPath, 'newPath'); | ||
fs.rename(oldPath, newPath, callback); | ||
}; | ||
LocalFilesystem.prototype.readlink = function (path, callback) { | ||
path = this.checkPath(path, 'path'); | ||
fs.readlink(path, callback); | ||
}; | ||
LocalFilesystem.prototype.symlink = function (targetpath, linkpath, callback) { | ||
LocalFilesystem.prototype.symlink = function (targetPath, linkPath, callback) { | ||
targetPath = this.checkPath(targetPath, 'targetPath'); | ||
linkPath = this.checkPath(linkPath, 'linkPath'); | ||
//TODO: make sure the order is correct (beware - other SFTP client and server vendors are confused as well) | ||
//TODO: make sure this work on Windows | ||
fs.symlink(linkpath, targetpath, 'file', callback); | ||
fs.symlink(linkPath, targetPath, 'file', callback); | ||
}; | ||
LocalFilesystem.prototype.link = function (oldPath, newPath, callback) { | ||
oldPath = this.checkPath(oldPath, 'oldPath'); | ||
newPath = this.checkPath(newPath, 'newPath'); | ||
fs.link(oldPath, newPath, callback); | ||
}; | ||
return LocalFilesystem; | ||
})(); | ||
exports.LocalFilesystem = LocalFilesystem; |
@@ -7,37 +7,26 @@ var __extends = this.__extends || function (d, b) { | ||
}; | ||
var events = require("events"); //WEB: /// <reference path="misc-web.ts" /> | ||
var misc = require("./fs-misc"); | ||
var sources = require("./fs-sources"); | ||
var targets = require("./fs-targets"); | ||
var util = require("./util"); | ||
var glob = require("./fs-glob"); | ||
var events = require("events"); | ||
var FileUtil = misc.FileUtil; | ||
var Path = misc.Path; | ||
var FileDataTarget = targets.FileDataTarget; | ||
var StringDataTarget = targets.StringDataTarget; | ||
var BufferDataTarget = targets.BufferDataTarget; | ||
var FileDataSource = sources.FileDataSource; | ||
var toDataSource = sources.toDataSource; | ||
var Task = util.Task; | ||
var wrapCallback = util.wrapCallback; | ||
var EventEmitter = events.EventEmitter; | ||
var search = glob.search; | ||
var FilesystemPlus = (function (_super) { | ||
__extends(FilesystemPlus, _super); | ||
function FilesystemPlus(fs) { | ||
function FilesystemPlus(fs, local) { | ||
_super.call(this); | ||
this._fs = fs; | ||
this._local = local; | ||
} | ||
FilesystemPlus.prototype.wrapCallback = function (callback) { | ||
var _this = this; | ||
if (typeof callback !== 'function') { | ||
// use dummy callback to prevent having to check this later | ||
return function () { | ||
}; | ||
} | ||
else { | ||
return function () { | ||
try { | ||
callback.apply(_this, arguments); | ||
} | ||
catch (error) { | ||
_this.emit("error", error); | ||
} | ||
}; | ||
} | ||
}; | ||
FilesystemPlus.prototype.on = function (event, listener) { | ||
return _super.prototype.on.call(this, event, listener); | ||
}; | ||
FilesystemPlus.prototype.once = function (event, listener) { | ||
return _super.prototype.once.call(this, event, listener); //WEB: // removed | ||
}; //WEB: // removed | ||
FilesystemPlus.prototype.addListener = function (event, listener) { | ||
return _super.prototype.addListener.call(this, event, listener); | ||
}; | ||
FilesystemPlus.prototype.open = function (path, flags, attrs, callback) { | ||
@@ -48,70 +37,54 @@ if (typeof attrs === 'function' && typeof callback === 'undefined') { | ||
} | ||
callback = this.wrapCallback(callback); | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.open(path, flags, attrs, callback); | ||
}; | ||
FilesystemPlus.prototype.close = function (handle, callback) { | ||
callback = this.wrapCallback(callback); | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.close(handle, callback); | ||
}; | ||
FilesystemPlus.prototype.read = function (handle, buffer, offset, length, position, callback) { | ||
callback = this.wrapCallback(callback); | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.read(handle, buffer, offset, length, position, callback); | ||
}; | ||
FilesystemPlus.prototype.write = function (handle, buffer, offset, length, position, callback) { | ||
callback = this.wrapCallback(callback); | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.write(handle, buffer, offset, length, position, callback); | ||
}; | ||
FilesystemPlus.prototype.lstat = function (path, callback) { | ||
callback = this.wrapCallback(callback); | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.lstat(path, callback); | ||
}; | ||
FilesystemPlus.prototype.fstat = function (handle, callback) { | ||
callback = this.wrapCallback(callback); | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.fstat(handle, callback); | ||
}; | ||
FilesystemPlus.prototype.setstat = function (path, attrs, callback) { | ||
callback = this.wrapCallback(callback); | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.setstat(path, attrs, callback); | ||
}; | ||
FilesystemPlus.prototype.fsetstat = function (handle, attrs, callback) { | ||
callback = this.wrapCallback(callback); | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.fsetstat(handle, attrs, callback); | ||
}; | ||
FilesystemPlus.prototype.opendir = function (path, callback) { | ||
callback = this.wrapCallback(callback); | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.opendir(path, callback); | ||
}; | ||
FilesystemPlus.prototype.readdir = function (handle, callback) { | ||
var _this = this; | ||
callback = this.wrapCallback(callback); | ||
if (typeof handle !== 'string') | ||
return this._fs.readdir(handle, callback); | ||
var path = handle; | ||
var list = []; | ||
var next = function (err, items) { | ||
if (err != null) { | ||
_this.close(handle); | ||
callback(err, list); | ||
return; | ||
} | ||
if (items === false) { | ||
_this.close(handle, function (err) { | ||
callback(err, list); | ||
}); | ||
return; | ||
} | ||
list = list.concat(items); | ||
_this._fs.readdir(handle, next); | ||
}; | ||
this.opendir(path, function (err, h) { | ||
if (err != null) { | ||
callback(err, null); | ||
return; | ||
} | ||
handle = h; | ||
next(null, []); | ||
}); | ||
if (typeof handle === 'string') { | ||
var path = Path.check(handle, 'path'); | ||
var options = { | ||
noglobstar: true, | ||
nowildcard: true, | ||
listonly: true, | ||
dotdirs: true | ||
}; | ||
search(this._fs, path, null, options, callback); | ||
return; | ||
} | ||
callback = wrapCallback(this, null, callback); | ||
return this._fs.readdir(handle, callback); | ||
}; | ||
FilesystemPlus.prototype.unlink = function (path, callback) { | ||
callback = this.wrapCallback(callback); | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.unlink(path, callback); | ||
@@ -124,31 +97,254 @@ }; | ||
} | ||
callback = this.wrapCallback(callback); | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.mkdir(path, attrs, callback); | ||
}; | ||
FilesystemPlus.prototype.rmdir = function (path, callback) { | ||
callback = this.wrapCallback(callback); | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.rmdir(path, callback); | ||
}; | ||
FilesystemPlus.prototype.realpath = function (path, callback) { | ||
callback = this.wrapCallback(callback); | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.realpath(path, callback); | ||
}; | ||
FilesystemPlus.prototype.stat = function (path, callback) { | ||
callback = this.wrapCallback(callback); | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.stat(path, callback); | ||
}; | ||
FilesystemPlus.prototype.rename = function (oldPath, newPath, callback) { | ||
callback = this.wrapCallback(callback); | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.rename(oldPath, newPath, callback); | ||
}; | ||
FilesystemPlus.prototype.readlink = function (path, callback) { | ||
callback = this.wrapCallback(callback); | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.readlink(path, callback); | ||
}; | ||
FilesystemPlus.prototype.symlink = function (targetpath, linkpath, callback) { | ||
callback = this.wrapCallback(callback); | ||
this._fs.symlink(targetpath, linkpath); | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.symlink(targetpath, linkpath, callback); | ||
}; | ||
FilesystemPlus.prototype.join = function () { | ||
var paths = []; | ||
for (var _i = 0; _i < arguments.length; _i++) { | ||
paths[_i - 0] = arguments[_i]; | ||
} | ||
var path = new Path("", this._fs); | ||
return path.join.apply(path, arguments).normalize().path; | ||
}; | ||
FilesystemPlus.prototype.link = function (oldPath, newPath, callback) { | ||
callback = wrapCallback(this, null, callback); | ||
this._fs.link(oldPath, newPath, callback); | ||
}; | ||
FilesystemPlus.prototype.list = function (remotePath, callback) { | ||
var remotePath = Path.check(remotePath, 'remotePath'); | ||
var options = { | ||
directories: true, | ||
files: true, | ||
nosort: false, | ||
dotdirs: false, | ||
noglobstar: true, | ||
listonly: true | ||
}; | ||
var task = new Task(); | ||
callback = wrapCallback(this, task, callback); | ||
search(this._fs, remotePath, task, options, callback); | ||
return task; | ||
}; | ||
FilesystemPlus.prototype.search = function (remotePath, options, callback) { | ||
var remotePath = Path.check(remotePath, 'remotePath'); | ||
if (typeof options === 'function' && typeof callback === 'undefined') { | ||
callback = options; | ||
options = null; | ||
} | ||
var task = new Task(); | ||
callback = wrapCallback(this, task, callback); | ||
search(this._fs, remotePath, task, options, callback); | ||
return task; | ||
}; | ||
FilesystemPlus.prototype.info = function (remotePath, callback) { | ||
var remotePath = Path.check(remotePath, 'remotePath'); | ||
var options = { | ||
itemonly: true | ||
}; | ||
var task = new Task(); | ||
callback = wrapCallback(this, task, callback); | ||
search(this._fs, remotePath, task, options, function (err, items) { | ||
if (err) | ||
return callback(err, null); | ||
if (!items || items.length != 1) | ||
return callback(new Error("Unexpected result"), null); | ||
callback(null, items[0]); | ||
}); | ||
return task; | ||
}; | ||
FilesystemPlus.prototype.readFile = function (remotePath, options, callback) { | ||
var remote = Path.create(remotePath, this._fs, 'remotePath'); | ||
if (typeof options === 'function' && typeof callback === 'undefined') { | ||
callback = options; | ||
options = null; | ||
} | ||
var task = new Task(); | ||
callback = wrapCallback(this, task, callback); | ||
// process options | ||
options = options || {}; | ||
var type = options.type; | ||
var encoding = options.encoding; | ||
if (type) { | ||
type = (type + "").toLowerCase(); | ||
if (type == "string" || type == "text") | ||
encoding = encoding || "utf8"; | ||
} | ||
else { | ||
type = encoding ? "string" : "buffer"; | ||
} | ||
// create appropriate target | ||
var target; | ||
switch (type) { | ||
case "text": | ||
case "string": | ||
target = new StringDataTarget(encoding); | ||
break; | ||
case "array": | ||
case "buffer": | ||
target = new BufferDataTarget(); | ||
break; | ||
case "blob": | ||
default: | ||
throw new Error("Unsupported data kind: " + options.type); | ||
} | ||
// create source | ||
var source = new FileDataSource(remote.fs, remote.path); | ||
// copy file data | ||
FileUtil.copy(source, target, task, function (err) { | ||
if (err) | ||
return callback(err, null); | ||
callback(null, target.result()); | ||
}); | ||
return task; | ||
}; | ||
FilesystemPlus.prototype.putFile = function (localPath, remotePath, callback) { | ||
var local = Path.create(localPath, this._local, 'localPath'); | ||
var remote = Path.create(remotePath, this._fs, 'remotePath'); | ||
return this._copyFile(local, remote, callback); | ||
}; | ||
FilesystemPlus.prototype.getFile = function (remotePath, localPath, callback) { | ||
var remote = Path.create(remotePath, this._fs, 'remotePath'); | ||
var local = Path.create(localPath, this._local, 'localPath'); | ||
return this._copyFile(remote, local, callback); | ||
}; | ||
FilesystemPlus.prototype._copyFile = function (sourcePath, targetPath, callback) { | ||
var task = new Task(); | ||
callback = wrapCallback(this, task, callback); | ||
// append filename if target path ens with slash | ||
if (targetPath.endsWithSlash()) { | ||
var filename = sourcePath.getName(); | ||
targetPath = targetPath.join(filename); | ||
} | ||
// create source and target | ||
var source = new FileDataSource(sourcePath.fs, sourcePath.path); | ||
var target = new FileDataTarget(targetPath.fs, targetPath.path); | ||
// copy file data | ||
FileUtil.copy(source, target, task, function (err) { return callback(err); }); | ||
return task; | ||
}; | ||
FilesystemPlus.prototype.upload = function (input, remotePath, callback) { | ||
var remote = Path.create(remotePath, this._fs, 'remotePath'); | ||
return this._copy(input, this._local, remote, callback); | ||
}; | ||
FilesystemPlus.prototype.download = function (remotePath, localPath, callback) { | ||
var local = Path.create(localPath, this._local, 'localPath'); | ||
return this._copy(remotePath, this._fs, local, callback); | ||
}; | ||
FilesystemPlus.prototype._copy = function (from, fromFs, toPath, callback) { | ||
var task = new Task(); | ||
callback = wrapCallback(this, task, callback); | ||
var sources = null; | ||
var toFs = toPath.fs; | ||
toPath = toPath.removeTrailingSlash(); | ||
toFs.stat(toPath.path, prepare); | ||
var directories = {}; | ||
return task; | ||
function prepare(err, stats) { | ||
if (err) | ||
return callback(err); | ||
if (!FileUtil.isDirectory(stats)) | ||
return callback(new Error("Target path is not a directory")); | ||
try { | ||
toDataSource(fromFs, from, task, function (err, src) { | ||
if (err) | ||
return callback(err); | ||
try { | ||
sources = src; | ||
sources.forEach(function (source) { | ||
//TODO: calculate total size | ||
//TODO: make sure that source.name is valid on target fs | ||
}); | ||
next(); | ||
} | ||
catch (err) { | ||
callback(err); | ||
} | ||
}); | ||
} | ||
catch (err) { | ||
callback(err); | ||
} | ||
} | ||
function next() { | ||
var source = sources.shift(); | ||
if (!source) | ||
return callback(null); | ||
var targetPath; | ||
if (typeof source.relativePath === "string") { | ||
var relativePath = new Path(source.relativePath, fromFs); | ||
targetPath = toPath.join(relativePath).normalize().path; | ||
checkParent(relativePath, transfer); | ||
} | ||
else { | ||
targetPath = toPath.join(source.name).path; | ||
transfer(null); | ||
} | ||
function transfer(err) { | ||
if (err) | ||
return callback(err); | ||
if (FileUtil.isDirectory(source.stats)) { | ||
FileUtil.mkdir(toFs, targetPath, transferred); | ||
} | ||
else { | ||
var target = new FileDataTarget(toFs, targetPath); | ||
FileUtil.copy(source, target, task, transferred); | ||
} | ||
} | ||
function transferred(err) { | ||
if (err) | ||
return callback(err); | ||
next(); | ||
} | ||
} | ||
function checkParent(path, callback) { | ||
var parent = path.getParent(); | ||
if (parent.isTop()) | ||
return callback(null); | ||
var exists = directories[parent]; | ||
if (exists) | ||
return callback(null); | ||
checkParent(parent, function (err) { | ||
if (err) | ||
return callback(err); | ||
try { | ||
var targetPath = toPath.join(parent).path; | ||
FileUtil.mkdir(toFs, targetPath, function (err) { | ||
if (err) | ||
return callback(err); | ||
directories[parent] = true; | ||
callback(null); | ||
}); | ||
} | ||
catch (err) { | ||
callback(err); | ||
} | ||
}); | ||
} | ||
}; | ||
return FilesystemPlus; | ||
})(EventEmitter); | ||
exports.FilesystemPlus = FilesystemPlus; |
@@ -286,4 +286,18 @@ var Path = require("path"); | ||
}; | ||
SafeFilesystem.prototype.link = function (oldPath, newPath, callback) { | ||
if (this.isReadOnly()) { | ||
this.reportReadOnly(callback); | ||
return; | ||
} | ||
oldPath = this.toRealPath(oldPath); | ||
newPath = this.toRealPath(newPath); | ||
try { | ||
this.fs.link(oldPath, newPath, callback); | ||
} | ||
catch (err) { | ||
callback(err); | ||
} | ||
}; | ||
return SafeFilesystem; | ||
})(); | ||
exports.SafeFilesystem = SafeFilesystem; |
@@ -11,6 +11,4 @@ var __extends = this.__extends || function (d, b) { | ||
var plus = require("./fs-plus"); | ||
var channel = require("./channel"); | ||
var util = require("./util"); | ||
var fsmisc = require("./fs-misc"); | ||
var FilesystemPlus = plus.FilesystemPlus; | ||
var Channel = channel.Channel; | ||
var SftpPacket = packet.SftpPacket; | ||
@@ -21,4 +19,27 @@ var SftpPacketWriter = packet.SftpPacketWriter; | ||
var SftpAttributes = misc.SftpAttributes; | ||
var SftpItem = misc.SftpItem; | ||
var toLogWriter = util.toLogWriter; | ||
var SftpExtensions = misc.SftpExtensions; | ||
var Path = fsmisc.Path; | ||
var SftpItem = (function () { | ||
function SftpItem() { | ||
} | ||
return SftpItem; | ||
})(); | ||
var SftpHandle = (function () { | ||
function SftpHandle(handle, owner) { | ||
this._handle = handle; | ||
this._this = owner; | ||
} | ||
SftpHandle.prototype.toString = function () { | ||
var value = "0x"; | ||
for (var i = 0; i < this._handle.length; i++) { | ||
var b = this._handle[i]; | ||
var c = b.toString(16); | ||
if (b < 16) | ||
value += "0"; | ||
value += c; | ||
} | ||
return value; | ||
}; | ||
return SftpHandle; | ||
})(); | ||
var SftpClientCore = (function () { | ||
@@ -30,2 +51,3 @@ function SftpClientCore() { | ||
this._requests = []; | ||
this._extensions = {}; | ||
this._maxWriteBlockLength = 32 * 1024; | ||
@@ -35,3 +57,3 @@ this._maxReadBlockLength = 256 * 1024; | ||
SftpClientCore.prototype.getRequest = function (type) { | ||
var request = new SftpPacketWriter(this._maxWriteBlockLength + 1024); //TODO: cache buffers | ||
var request = new SftpPacketWriter(this._maxWriteBlockLength + 1024); | ||
request.type = type; | ||
@@ -55,8 +77,18 @@ request.id = this._id; | ||
}; | ||
SftpClientCore.prototype.execute = function (request, callback, responseParser) { | ||
SftpClientCore.prototype.execute = function (request, callback, responseParser, info) { | ||
var _this = this; | ||
if (typeof callback !== 'function') { | ||
// use dummy callback to prevent having to check this later | ||
callback = function () { | ||
callback = function (err) { | ||
if (err) | ||
throw err; | ||
}; | ||
} | ||
if (!this._host) { | ||
process.nextTick(function () { | ||
var error = _this.createError(6 /* NO_CONNECTION */, "Not connected", info); | ||
callback(error); | ||
}); | ||
return; | ||
} | ||
if (typeof this._requests[request.id] !== 'undefined') | ||
@@ -66,22 +98,41 @@ throw new Error("Duplicate request"); | ||
this._host.send(packet); | ||
this._requests[request.id] = { callback: callback, responseParser: responseParser }; | ||
this._requests[request.id] = { callback: callback, responseParser: responseParser, info: info }; | ||
}; | ||
SftpClientCore.prototype._init = function (host, callback) { | ||
var _this = this; | ||
if (this._host) | ||
throw new Error("Already bound"); | ||
this._host = host; | ||
this._extensions = {}; | ||
var request = this.getRequest(1 /* INIT */); | ||
request.writeInt32(3); // SFTPv3 | ||
var info = { command: "init" }; | ||
this.execute(request, callback, function (response, cb) { | ||
if (response.type != 2 /* VERSION */) { | ||
callback(new Error("Protocol violation")); | ||
return; | ||
host.close(3002); | ||
var error = _this.createError(5 /* BAD_MESSAGE */, "Unexpected message", info); | ||
return callback(new Error("Protocol violation")); | ||
} | ||
var version = response.readInt32(); | ||
if (version != 3) { | ||
callback(new Error("Protocol violation")); | ||
return; | ||
host.close(3002); | ||
var error = _this.createError(5 /* BAD_MESSAGE */, "Unexpected protocol version", info); | ||
return callback(error); | ||
} | ||
while ((response.length - response.position) >= 4) { | ||
var extensionName = response.readString(); | ||
var value; | ||
if (SftpExtensions.isKnown(extensionName)) { | ||
value = response.readString(); | ||
} | ||
else { | ||
value = response.readData(true); | ||
} | ||
var values = _this._extensions[extensionName] || []; | ||
values.push(value); | ||
_this._extensions[extensionName] = values; | ||
} | ||
_this._ready = true; | ||
callback(null); | ||
}); | ||
}, info); | ||
}; | ||
@@ -94,11 +145,30 @@ SftpClientCore.prototype._process = function (packet) { | ||
delete this._requests[response.id]; | ||
response.info = request.info; | ||
request.responseParser.call(this, response, request.callback); | ||
}; | ||
SftpClientCore.prototype._end = function () { | ||
var host = this._host; | ||
if (host) | ||
this._host = null; | ||
this.failRequests(7 /* CONNECTION_LOST */, "Connection lost"); | ||
}; | ||
SftpClientCore.prototype.end = function () { | ||
this._host.close(); | ||
var host = this._host; | ||
if (host) { | ||
this._host = null; | ||
host.close(); | ||
} | ||
this.failRequests(7 /* CONNECTION_LOST */, "Connection closed"); | ||
}; | ||
SftpClientCore.prototype.failRequests = function (code, message) { | ||
var _this = this; | ||
var requests = this._requests; | ||
this._requests = []; | ||
requests.forEach(function (request) { | ||
var error = _this.createError(code, message, request.info); | ||
request.callback(error); | ||
}); | ||
}; | ||
SftpClientCore.prototype.open = function (path, flags, attrs, callback) { | ||
path = this.toPath(path, 'path'); | ||
path = this.checkPath(path, 'path'); | ||
if (typeof attrs === 'function' && typeof callback === 'undefined') { | ||
@@ -110,15 +180,15 @@ callback = attrs; | ||
request.writeString(path); | ||
request.writeInt32(SftpFlags.toFlags(flags)); | ||
request.writeInt32(SftpFlags.toNumber(flags)); | ||
this.writeStats(request, attrs); | ||
this.execute(request, callback, this.parseHandle); | ||
this.execute(request, callback, this.parseHandle, { command: "open", path: path }); | ||
}; | ||
SftpClientCore.prototype.close = function (handle, callback) { | ||
handle = this.toHandle(handle); | ||
var h = this.toHandle(handle); | ||
var request = this.getRequest(4 /* CLOSE */); | ||
request.writeData(handle); | ||
this.execute(request, callback, this.parseStatus); | ||
request.writeData(h); | ||
this.execute(request, callback, this.parseStatus, { command: "close", handle: handle }); | ||
}; | ||
SftpClientCore.prototype.read = function (handle, buffer, offset, length, position, callback) { | ||
var _this = this; | ||
handle = this.toHandle(handle); | ||
var h = this.toHandle(handle); | ||
this.checkBuffer(buffer, offset, length); | ||
@@ -130,9 +200,9 @@ this.checkPosition(position); | ||
var request = this.getRequest(5 /* READ */); | ||
request.writeData(handle); | ||
request.writeData(h); | ||
request.writeInt64(position); | ||
request.writeInt32(length); | ||
this.execute(request, callback, function (response, cb) { return _this.parseData(response, cb, buffer, offset, length); }); | ||
this.execute(request, callback, function (response, cb) { return _this.parseData(response, callback, 0, h, buffer, offset, length, position); }, { command: "read", handle: handle }); | ||
}; | ||
SftpClientCore.prototype.write = function (handle, buffer, offset, length, position, callback) { | ||
handle = this.toHandle(handle); | ||
var h = this.toHandle(handle); | ||
this.checkBuffer(buffer, offset, length); | ||
@@ -143,47 +213,47 @@ this.checkPosition(position); | ||
var request = this.getRequest(6 /* WRITE */); | ||
request.writeData(handle); | ||
request.writeData(h); | ||
request.writeInt64(position); | ||
request.writeData(buffer, offset, offset + length); | ||
this.execute(request, callback, this.parseStatus); | ||
this.execute(request, callback, this.parseStatus, { command: "write", handle: handle }); | ||
}; | ||
SftpClientCore.prototype.lstat = function (path, callback) { | ||
path = this.toPath(path, 'path'); | ||
this.command(7 /* LSTAT */, [path], callback, this.parseAttribs); | ||
path = this.checkPath(path, 'path'); | ||
this.command(7 /* LSTAT */, [path], callback, this.parseAttribs, { command: "lstat", path: path }); | ||
}; | ||
SftpClientCore.prototype.fstat = function (handle, callback) { | ||
handle = this.toHandle(handle); | ||
var h = this.toHandle(handle); | ||
var request = this.getRequest(8 /* FSTAT */); | ||
request.writeData(handle); | ||
this.execute(request, callback, this.parseAttribs); | ||
request.writeData(h); | ||
this.execute(request, callback, this.parseAttribs, { command: "fstat", handle: handle }); | ||
}; | ||
SftpClientCore.prototype.setstat = function (path, attrs, callback) { | ||
path = this.toPath(path, 'path'); | ||
path = this.checkPath(path, 'path'); | ||
var request = this.getRequest(9 /* SETSTAT */); | ||
request.writeString(path); | ||
this.writeStats(request, attrs); | ||
this.execute(request, callback, this.parseStatus); | ||
this.execute(request, callback, this.parseStatus, { command: "setstat", path: path }); | ||
}; | ||
SftpClientCore.prototype.fsetstat = function (handle, attrs, callback) { | ||
handle = this.toHandle(handle); | ||
var h = this.toHandle(handle); | ||
var request = this.getRequest(10 /* FSETSTAT */); | ||
request.writeData(handle); | ||
request.writeData(h); | ||
this.writeStats(request, attrs); | ||
this.execute(request, callback, this.parseStatus); | ||
this.execute(request, callback, this.parseStatus, { command: "fsetstat", handle: handle }); | ||
}; | ||
SftpClientCore.prototype.opendir = function (path, callback) { | ||
path = this.toPath(path, 'path'); | ||
this.command(11 /* OPENDIR */, [path], callback, this.parseHandle); | ||
path = this.checkPath(path, 'path'); | ||
this.command(11 /* OPENDIR */, [path], callback, this.parseHandle, { command: "opendir", path: path }); | ||
}; | ||
SftpClientCore.prototype.readdir = function (handle, callback) { | ||
handle = this.toHandle(handle); | ||
var h = this.toHandle(handle); | ||
var request = this.getRequest(12 /* READDIR */); | ||
request.writeData(handle); | ||
this.execute(request, callback, this.parseItems); | ||
request.writeData(h); | ||
this.execute(request, callback, this.parseItems, { command: "readdir", handle: handle }); | ||
}; | ||
SftpClientCore.prototype.unlink = function (path, callback) { | ||
path = this.toPath(path, 'path'); | ||
this.command(13 /* REMOVE */, [path], callback, this.parseStatus); | ||
path = this.checkPath(path, 'path'); | ||
this.command(13 /* REMOVE */, [path], callback, this.parseStatus, { command: "unline", path: path }); | ||
}; | ||
SftpClientCore.prototype.mkdir = function (path, attrs, callback) { | ||
path = this.toPath(path, 'path'); | ||
path = this.checkPath(path, 'path'); | ||
if (typeof attrs === 'function' && typeof callback === 'undefined') { | ||
@@ -196,52 +266,45 @@ callback = attrs; | ||
this.writeStats(request, attrs); | ||
this.execute(request, callback, this.parseStatus); | ||
this.execute(request, callback, this.parseStatus, { command: "mkdir", path: path }); | ||
}; | ||
SftpClientCore.prototype.rmdir = function (path, callback) { | ||
path = this.toPath(path, 'path'); | ||
this.command(15 /* RMDIR */, [path], callback, this.parseStatus); | ||
path = this.checkPath(path, 'path'); | ||
this.command(15 /* RMDIR */, [path], callback, this.parseStatus, { command: "rmdir", path: path }); | ||
}; | ||
SftpClientCore.prototype.realpath = function (path, callback) { | ||
path = this.toPath(path, 'path'); | ||
this.command(16 /* REALPATH */, [path], callback, this.parsePath); | ||
path = this.checkPath(path, 'path'); | ||
this.command(16 /* REALPATH */, [path], callback, this.parsePath, { command: "realpath", path: path }); | ||
}; | ||
SftpClientCore.prototype.stat = function (path, callback) { | ||
path = this.toPath(path, 'path'); | ||
this.command(17 /* STAT */, [path], callback, this.parseAttribs); | ||
path = this.checkPath(path, 'path'); | ||
this.command(17 /* STAT */, [path], callback, this.parseAttribs, { command: "stat", path: path }); | ||
}; | ||
SftpClientCore.prototype.rename = function (oldPath, newPath, callback) { | ||
oldPath = this.toPath(oldPath, 'oldPath'); | ||
newPath = this.toPath(newPath, 'newPath'); | ||
this.command(18 /* RENAME */, [oldPath, newPath], callback, this.parseStatus); | ||
oldPath = this.checkPath(oldPath, 'oldPath'); | ||
newPath = this.checkPath(newPath, 'newPath'); | ||
this.command(18 /* RENAME */, [oldPath, newPath], callback, this.parseStatus, { command: "rename", oldPath: oldPath, newPath: newPath }); | ||
}; | ||
SftpClientCore.prototype.readlink = function (path, callback) { | ||
path = this.toPath(path, 'path'); | ||
this.command(19 /* READLINK */, [path], callback, this.parsePath); | ||
path = this.checkPath(path, 'path'); | ||
this.command(19 /* READLINK */, [path], callback, this.parsePath, { command: "readlink", path: path }); | ||
}; | ||
SftpClientCore.prototype.symlink = function (targetPath, linkPath, callback) { | ||
targetPath = this.toPath(targetPath, 'targetPath'); | ||
linkPath = this.toPath(linkPath, 'linkPath'); | ||
this.command(20 /* SYMLINK */, [targetPath, linkPath], callback, this.parseStatus); | ||
targetPath = this.checkPath(targetPath, 'targetPath'); | ||
linkPath = this.checkPath(linkPath, 'linkPath'); | ||
this.command(20 /* SYMLINK */, [targetPath, linkPath], callback, this.parseStatus, { command: "symlink", targetPath: targetPath, linkPath: linkPath }); | ||
}; | ||
SftpClientCore.prototype.link = function (oldPath, newPath, callback) { | ||
oldPath = this.checkPath(oldPath, 'oldPath'); | ||
newPath = this.checkPath(newPath, 'newPath'); | ||
this.command(SftpExtensions.HARDLINK, [oldPath, newPath], callback, this.parseStatus, { command: "link", oldPath: oldPath, newPath: newPath }); | ||
}; | ||
SftpClientCore.prototype.toHandle = function (handle) { | ||
if (typeof handle === 'object') { | ||
if (!handle) { | ||
throw new Error("Missing handle"); | ||
} | ||
else if (typeof handle === 'object') { | ||
if (SftpPacket.isBuffer(handle._handle) && handle._this == this) | ||
return handle._handle; | ||
} | ||
else if (handle == null || typeof handle === 'undefined') { | ||
throw new Error("Missing handle"); | ||
} | ||
throw new Error("Invalid handle"); | ||
}; | ||
SftpClientCore.prototype.toPath = function (path, name) { | ||
if (typeof path !== 'string') { | ||
if (path == null || typeof path === 'undefined') | ||
throw new Error("Missing " + name); | ||
if (typeof path === 'function') | ||
throw new Error("Invalid " + name); | ||
path = new String(path); | ||
} | ||
if (path.length == 0) | ||
throw new Error("Empty " + name); | ||
return path; | ||
}; | ||
SftpClientCore.prototype.checkBuffer = function (buffer, offset, length) { | ||
@@ -257,2 +320,14 @@ if (!SftpPacket.isBuffer(buffer)) | ||
}; | ||
SftpClientCore.prototype.checkPath = function (path, name) { | ||
path = Path.check(path, name); | ||
if (path[0] === '~') { | ||
if (path[1] === '/') { | ||
path = "." + path.substr(1); | ||
} | ||
else if (path.length == 1) { | ||
path = "."; | ||
} | ||
} | ||
return path; | ||
}; | ||
SftpClientCore.prototype.checkPosition = function (position) { | ||
@@ -262,3 +337,3 @@ if (typeof position !== 'number' || position < 0 || position > 0x7FFFFFFFFFFFFFFF) | ||
}; | ||
SftpClientCore.prototype.command = function (command, args, callback, responseParser) { | ||
SftpClientCore.prototype.command = function (command, args, callback, responseParser, info) { | ||
var request = this.getRequest(command); | ||
@@ -268,11 +343,80 @@ for (var i = 0; i < args.length; i++) { | ||
} | ||
this.execute(request, callback, responseParser); | ||
this.execute(request, callback, responseParser, info); | ||
}; | ||
SftpClientCore.prototype.readStatus = function (response) { | ||
var code = response.readInt32(); | ||
var nativeCode = response.readInt32(); | ||
var message = response.readString(); | ||
if (code == 0 /* OK */) | ||
if (nativeCode == 0 /* OK */) | ||
return null; | ||
var error = new Error("SFTP error " + code + ": " + message); | ||
var info = response.info; | ||
return this.createError(nativeCode, message, info); | ||
}; | ||
SftpClientCore.prototype.readItem = function (response) { | ||
var item = new SftpItem(); | ||
item.filename = response.readString(); | ||
item.longname = response.readString(); | ||
item.stats = new SftpAttributes(response); | ||
return item; | ||
}; | ||
SftpClientCore.prototype.createError = function (nativeCode, message, info) { | ||
var code; | ||
var errno; | ||
switch (nativeCode) { | ||
case 1 /* EOF */: | ||
code = "EOF"; | ||
errno = 1; | ||
break; | ||
case 2 /* NO_SUCH_FILE */: | ||
code = "ENOENT"; | ||
errno = 34; | ||
break; | ||
case 3 /* PERMISSION_DENIED */: | ||
code = "EACCES"; | ||
errno = 3; | ||
break; | ||
case 0 /* OK */: | ||
case 4 /* FAILURE */: | ||
case 5 /* BAD_MESSAGE */: | ||
code = "EFAILURE"; | ||
errno = -2; | ||
break; | ||
case 6 /* NO_CONNECTION */: | ||
code = "ENOTCONN"; | ||
errno = 31; | ||
break; | ||
case 7 /* CONNECTION_LOST */: | ||
code = "ESHUTDOWN"; | ||
errno = 46; | ||
break; | ||
case 8 /* OP_UNSUPPORTED */: | ||
code = "ENOSYS"; | ||
errno = 35; | ||
break; | ||
case 5 /* BAD_MESSAGE */: | ||
code = "ESHUTDOWN"; | ||
errno = 46; | ||
break; | ||
default: | ||
code = "UNKNOWN"; | ||
errno = -1; | ||
break; | ||
} | ||
var command = info.command; | ||
var arg = info.path || info.handle; | ||
if (typeof arg === "string") | ||
arg = "'" + arg + "'"; | ||
else if (arg) | ||
arg = new String(arg); | ||
else | ||
arg = ""; | ||
var error = new Error(code + ", " + command + " " + arg); | ||
error['errno'] = errno; | ||
error['code'] = code; | ||
for (var name in info) { | ||
if (name == "command") | ||
continue; | ||
if (info.hasOwnProperty(name)) | ||
error[name] = info[name]; | ||
} | ||
error['nativeCode'] = nativeCode; | ||
error['description'] = message; | ||
@@ -309,3 +453,3 @@ return error; | ||
var handle = response.readData(true); | ||
callback(null, { _handle: handle, _this: this }); | ||
callback(null, new SftpHandle(handle, this)); | ||
}; | ||
@@ -321,5 +465,14 @@ SftpClientCore.prototype.parsePath = function (response, callback) { | ||
}; | ||
SftpClientCore.prototype.parseData = function (response, callback, buffer, offset, length) { | ||
if (!this.checkResponse(response, 103 /* DATA */, callback)) | ||
return; | ||
SftpClientCore.prototype.parseData = function (response, callback, retries, h, buffer, offset, length, position) { | ||
var _this = this; | ||
if (response.type == 101 /* STATUS */) { | ||
var error = this.readStatus(response); | ||
if (error != null) { | ||
if (error['nativeCode'] == 1 /* EOF */) | ||
callback(null, 0, buffer); | ||
else | ||
callback(error, 0, null); | ||
return; | ||
} | ||
} | ||
var data = response.readData(false); | ||
@@ -329,5 +482,20 @@ if (data.length > length) | ||
length = data.length; | ||
if (length == 0) { | ||
// workaround for broken servers such as Globalscape 7.1.x that occasionally send empty data | ||
if (retries > 4) { | ||
var error = this.createError(4 /* FAILURE */, "Unable to read data", response.info); | ||
error['code'] = "EIO"; | ||
error['errno'] = 55; | ||
callback(error, 0, null); | ||
return; | ||
} | ||
var request = this.getRequest(5 /* READ */); | ||
request.writeData(h); | ||
request.writeInt64(position); | ||
request.writeInt32(length); | ||
this.execute(request, callback, function (response, cb) { return _this.parseData(response, callback, retries + 1, h, buffer, offset, length, position); }, response.info); | ||
return; | ||
} | ||
data.copy(buffer, offset, 0, length); //WEB: buffer.set(data, offset); | ||
var view = buffer.slice(offset, offset + length); //WEB: var view = buffer.subarray(offset, offset + length); | ||
callback(null, length, view); //TODO: make sure that this corresponds to the behavior of fs.read | ||
callback(null, length, buffer); | ||
}; | ||
@@ -338,3 +506,3 @@ SftpClientCore.prototype.parseItems = function (response, callback) { | ||
if (error != null) { | ||
if (error['code'] == 1 /* EOF */) | ||
if (error['nativeCode'] == 1 /* EOF */) | ||
callback(null, false); | ||
@@ -351,3 +519,3 @@ else | ||
for (var i = 0; i < count; i++) { | ||
items[i] = new SftpItem(response); | ||
items[i] = this.readItem(response); | ||
} | ||
@@ -360,22 +528,67 @@ callback(null, items); | ||
__extends(SftpClient, _super); | ||
function SftpClient(ws, log) { | ||
function SftpClient(local) { | ||
var sftp = new SftpClientCore(); | ||
_super.call(this, sftp, local); | ||
} | ||
SftpClient.prototype.bind = function (channel, callback) { | ||
var _this = this; | ||
var sftp = new SftpClientCore(); | ||
var channel = new Channel(sftp, ws); | ||
channel.log = toLogWriter(log); | ||
_super.call(this, sftp); | ||
ws.on("open", function () { | ||
channel.start(); | ||
sftp._init(channel, function (err) { | ||
if (err != null) { | ||
_this.emit('error', err); | ||
var sftp = this._fs; | ||
if (this._bound) | ||
throw new Error("Already bound"); | ||
this._bound = true; | ||
var ready = false; | ||
var self = this; | ||
channel.on("ready", function () { | ||
ready = true; | ||
sftp._init(channel, function (error) { | ||
if (error) { | ||
sftp._end(); | ||
_this._bound = false; | ||
return done(error); | ||
} | ||
else { | ||
_this.emit('ready'); | ||
done(null); | ||
_this.emit('ready'); | ||
}); | ||
}); | ||
channel.on("message", function (packet) { | ||
try { | ||
sftp._process(packet); | ||
} | ||
catch (err) { | ||
_this.emit("error", err); | ||
sftp.end(); | ||
} | ||
}); | ||
channel.on("error", function (err) { | ||
_this.emit("error", err); | ||
sftp.end(); | ||
}); | ||
channel.on("close", function (err) { | ||
if (!ready) { | ||
err = err || new Error("Unable to connect"); | ||
done(err); | ||
} | ||
else { | ||
sftp._end(); | ||
_this._bound = false; | ||
_this.emit('close', err); | ||
} | ||
}); | ||
function done(error) { | ||
if (typeof callback === "function") { | ||
try { | ||
callback(error); | ||
error = null; | ||
} | ||
}); | ||
}); //WEB: }; | ||
} | ||
catch (err) { | ||
error = err; | ||
} | ||
} | ||
if (error) | ||
self.emit("error", error); | ||
} | ||
}; | ||
SftpClient.prototype.end = function () { | ||
this._fs.end(); | ||
var sftp = this._fs; | ||
sftp.end(); | ||
}; | ||
@@ -382,0 +595,0 @@ return SftpClient; |
var packet = require("./sftp-packet"); | ||
var api = require("./fs-api"); | ||
var enums = require("./sftp-enums"); | ||
@@ -6,3 +7,5 @@ var SftpFlags = (function () { | ||
} | ||
SftpFlags.toFlags = function (flags) { | ||
SftpFlags.toNumber = function (flags) { | ||
if (typeof flags === 'number') | ||
return flags & 63 /* ALL */; | ||
switch (flags) { | ||
@@ -19,6 +22,6 @@ case 'r': | ||
case 'xw': | ||
return 2 /* WRITE */ | 8 /* CREATE */ | 16 /* TRUNC */ | 32 /* EXCL */; | ||
return 2 /* WRITE */ | 8 /* CREATE */ | 32 /* EXCL */; | ||
case 'wx+': | ||
case 'xw+': | ||
return 2 /* WRITE */ | 8 /* CREATE */ | 16 /* TRUNC */ | 32 /* EXCL */ | 1 /* READ */; | ||
return 2 /* WRITE */ | 8 /* CREATE */ | 32 /* EXCL */ | 1 /* READ */; | ||
case 'a': | ||
@@ -38,39 +41,35 @@ return 2 /* WRITE */ | 8 /* CREATE */ | 4 /* APPEND */; | ||
}; | ||
SftpFlags.fromFlags = function (flags) { | ||
var read = ((flags & 1 /* READ */) != 0); | ||
var write = ((flags & 2 /* WRITE */) != 0); | ||
var append = ((flags & 4 /* APPEND */) != 0); | ||
var create = ((flags & 8 /* CREATE */) != 0); | ||
var trunc = ((flags & 16 /* TRUNC */) != 0); | ||
var excl = ((flags & 32 /* EXCL */) != 0); | ||
var modes = []; | ||
if (create) { | ||
if (excl) { | ||
modes.push("wx+"); | ||
} | ||
else if (trunc) { | ||
modes.push("w+"); | ||
} | ||
else { | ||
modes.push("wx+"); | ||
create = false; | ||
} | ||
SftpFlags.fromNumber = function (flags) { | ||
flags &= 63 /* ALL */; | ||
// 'truncate' does not apply when creating a new file | ||
if ((flags & 32 /* EXCL */) != 0) | ||
flags &= 63 /* ALL */ ^ 16 /* TRUNC */; | ||
// 'append' does not apply when truncating | ||
if ((flags & 16 /* TRUNC */) != 0) | ||
flags &= 63 /* ALL */ ^ 4 /* APPEND */; | ||
// 'read' or 'write' must be specified (or both) | ||
if ((flags & (1 /* READ */ | 2 /* WRITE */)) == 0) | ||
flags |= 1 /* READ */; | ||
// when not creating a new file, only 'read' or 'write' applies | ||
// (and when creating a new file, 'write' is required) | ||
if ((flags & 8 /* CREATE */) == 0) | ||
flags &= 1 /* READ */ | 2 /* WRITE */; | ||
else | ||
flags |= 2 /* WRITE */; | ||
switch (flags) { | ||
case 1: return ["r"]; | ||
case 2: | ||
case 3: return ["r+"]; | ||
case 10: return ["wx", "r+"]; | ||
case 11: return ["wx+", "r+"]; | ||
case 14: return ["a"]; | ||
case 15: return ["a+"]; | ||
case 26: return ["w"]; | ||
case 27: return ["w+"]; | ||
case 42: return ["wx"]; | ||
case 43: return ["wx+"]; | ||
case 46: return ["ax"]; | ||
case 47: return ["ax+"]; | ||
} | ||
if (!create) { | ||
if (append) { | ||
if (read) { | ||
modes.push("a+"); | ||
} | ||
else { | ||
modes.push("a"); | ||
} | ||
} | ||
else if (write) { | ||
modes.push("r+"); | ||
} | ||
else { | ||
modes.push("r"); | ||
} | ||
} | ||
return modes; | ||
throw Error("Unsupported flags"); | ||
}; | ||
@@ -80,2 +79,25 @@ return SftpFlags; | ||
exports.SftpFlags = SftpFlags; | ||
var SftpExtensions = (function () { | ||
function SftpExtensions() { | ||
} | ||
SftpExtensions.isKnown = function (name) { | ||
return SftpExtensions.hasOwnProperty("_" + name); | ||
}; | ||
SftpExtensions.POSIX_RENAME = "posix-rename@openssh.com"; // "1" | ||
SftpExtensions.STATVFS = "statvfs@openssh.com"; // "2" | ||
SftpExtensions.FSTATVFS = "fstatvfs@openssh.com"; // "2" | ||
SftpExtensions.HARDLINK = "hardlink@openssh.com"; // "1" | ||
SftpExtensions.FSYNC = "fsync@openssh.com"; // "1" | ||
SftpExtensions.NEWLINE = "newline@sftp.ws"; // "\n" | ||
SftpExtensions.CHARSET = "charset@sftp.ws"; // "utf-8" | ||
SftpExtensions._constructor = (function () { | ||
for (var name in SftpExtensions) { | ||
if (SftpExtensions.hasOwnProperty(name)) { | ||
SftpExtensions["_" + SftpExtensions[name]] = true; | ||
} | ||
} | ||
})(); | ||
return SftpExtensions; | ||
})(); | ||
exports.SftpExtensions = SftpExtensions; | ||
var SftpStatus = (function () { | ||
@@ -94,98 +116,2 @@ function SftpStatus() { | ||
}; | ||
SftpStatus.writeError = function (response, err) { | ||
var message; | ||
var code = 4 /* FAILURE */; | ||
switch (err.errno | 0) { | ||
default: | ||
if (err["isPublic"] === true) | ||
message = err.message; | ||
else | ||
message = "Unknown error"; | ||
break; | ||
case 1: | ||
message = "End of file"; | ||
code = 1 /* EOF */; | ||
break; | ||
case 3: | ||
message = "Permission denied"; | ||
code = 3 /* PERMISSION_DENIED */; | ||
break; | ||
case 4: | ||
message = "Try again"; | ||
break; | ||
case 9: | ||
message = "Bad file number"; | ||
break; | ||
case 10: | ||
message = "Device or resource busy"; | ||
break; | ||
case 18: | ||
message = "Invalid argument"; | ||
break; | ||
case 20: | ||
message = "Too many open files"; | ||
break; | ||
case 24: | ||
message = "File table overflow"; | ||
break; | ||
case 25: | ||
message = "No buffer space available"; | ||
break; | ||
case 26: | ||
message = "Out of memory"; | ||
break; | ||
case 27: | ||
message = "Not a directory"; | ||
break; | ||
case 28: | ||
message = "Is a directory"; | ||
break; | ||
case 34: | ||
message = "No such file or directory"; | ||
code = 2 /* NO_SUCH_FILE */; | ||
break; | ||
case 35: | ||
message = "Function not implemented"; | ||
code = 8 /* OP_UNSUPPORTED */; | ||
break; | ||
case 47: | ||
message = "File exists"; | ||
break; | ||
case 49: | ||
message = "File name too long"; | ||
break; | ||
case 50: | ||
message = "Operation not permitted"; | ||
break; | ||
case 51: | ||
message = "Too many symbolic links encountered"; | ||
break; | ||
case 52: | ||
message = "Cross-device link "; | ||
break; | ||
case 53: | ||
message = "Directory not empty"; | ||
break; | ||
case 54: | ||
message = "No space left on device"; | ||
break; | ||
case 55: | ||
message = "I/O error"; | ||
break; | ||
case 56: | ||
message = "Read-only file system"; | ||
break; | ||
case 57: | ||
message = "No such device"; | ||
code = 2 /* NO_SUCH_FILE */; | ||
break; | ||
case 58: | ||
message = "Illegal seek"; | ||
break; | ||
case 59: | ||
message = "Operation canceled"; | ||
break; | ||
} | ||
this.write(response, code, message); | ||
}; | ||
return SftpStatus; | ||
@@ -200,63 +126,41 @@ })(); | ||
exports.SftpOptions = SftpOptions; | ||
var SftpItem = (function () { | ||
function SftpItem(arg, stats) { | ||
if (typeof arg === 'object') { | ||
var request = arg; | ||
this.filename = request.readString(); | ||
this.longname = request.readString(); | ||
this.stats = new SftpAttributes(request); | ||
} | ||
else { | ||
var filename = arg; | ||
this.filename = filename; | ||
this.longname = filename; | ||
if (typeof stats === 'object') { | ||
var attr = new SftpAttributes(); | ||
attr.from(stats); | ||
this.stats = attr; | ||
this.longname = attr.toString() + " " + filename; | ||
} | ||
} | ||
} | ||
SftpItem.prototype.write = function (response) { | ||
response.writeString(this.filename); | ||
response.writeString(this.longname); | ||
if (typeof this.stats === "object") | ||
this.stats.write(response); | ||
else | ||
response.writeInt32(0); | ||
}; | ||
return SftpItem; | ||
})(); | ||
exports.SftpItem = SftpItem; | ||
var SftpAttributes = (function () { | ||
function SftpAttributes(request) { | ||
if (typeof request === 'undefined') { | ||
function SftpAttributes(reader) { | ||
if (typeof reader === 'undefined') { | ||
this.flags = 0; | ||
return; | ||
} | ||
var flags = this.flags = request.readUint32(); | ||
var flags = this.flags = reader.readUint32(); | ||
if (flags & 1 /* SIZE */) { | ||
this.size = request.readInt64(); | ||
this.size = reader.readInt64(); | ||
} | ||
if (flags & 2 /* UIDGID */) { | ||
this.uid = request.readInt32(); | ||
this.gid = request.readInt32(); | ||
this.uid = reader.readInt32(); | ||
this.gid = reader.readInt32(); | ||
} | ||
if (flags & 4 /* PERMISSIONS */) { | ||
this.mode = request.readUint32(); | ||
this.mode = reader.readUint32(); | ||
} | ||
if (flags & 8 /* ACMODTIME */) { | ||
this.atime = new Date(1000 * request.readUint32()); | ||
this.mtime = new Date(1000 * request.readUint32()); | ||
this.atime = new Date(1000 * reader.readUint32()); | ||
this.mtime = new Date(1000 * reader.readUint32()); | ||
} | ||
if (flags & 2147483648 /* EXTENDED */) { | ||
this.flags &= ~2147483648 /* EXTENDED */; | ||
this.size = request.readInt64(); | ||
this.size = reader.readInt64(); | ||
for (var i = 0; i < this.size; i++) { | ||
request.skipString(); | ||
request.skipString(); | ||
reader.skipString(); | ||
reader.skipString(); | ||
} | ||
} | ||
} | ||
SftpAttributes.prototype.isDirectory = function () { | ||
return (this.mode & 61440 /* ALL */) == 16384 /* DIRECTORY */; | ||
}; | ||
SftpAttributes.prototype.isFile = function () { | ||
return (this.mode & 61440 /* ALL */) == 32768 /* REGULAR_FILE */; | ||
}; | ||
SftpAttributes.prototype.isSymbolicLink = function () { | ||
return (this.mode & 61440 /* ALL */) == 40960 /* SYMLINK */; | ||
}; | ||
SftpAttributes.prototype.write = function (response) { | ||
@@ -313,57 +217,4 @@ var flags = this.flags; | ||
}; | ||
SftpAttributes.prototype.toString = function () { | ||
var attrs = this.mode; | ||
var perms; | ||
switch (attrs & 0xE000) { | ||
case 8192 /* CHARACTER_DEVICE */: | ||
perms = "c"; | ||
break; | ||
case 16384 /* DIRECTORY */: | ||
perms = "d"; | ||
break; | ||
case 24576 /* BLOCK_DEVICE */: | ||
perms = "b"; | ||
break; | ||
case 32768 /* REGULAR_FILE */: | ||
perms = "-"; | ||
break; | ||
case 40960 /* SYMLINK */: | ||
perms = "l"; | ||
break; | ||
case 49152 /* SOCKET */: | ||
perms = "s"; | ||
break; | ||
case 4096 /* FIFO */: | ||
perms = "p"; | ||
break; | ||
default: | ||
perms = "-"; | ||
break; | ||
} | ||
attrs &= 0x1FF; | ||
for (var j = 0; j < 3; j++) { | ||
var mask = (attrs >> ((2 - j) * 3)) & 0x7; | ||
perms += (mask & 4) ? "r" : "-"; | ||
perms += (mask & 2) ? "w" : "-"; | ||
perms += (mask & 1) ? "x" : "-"; | ||
} | ||
var len = this.size.toString(); | ||
if (len.length < 9) | ||
len = " ".slice(len.length - 9) + len; | ||
else | ||
len = " " + len; | ||
var modified = this.mtime; | ||
var diff = (new Date().getTime() - modified.getTime()) / (3600 * 24); | ||
var date = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][modified.getUTCMonth()]; | ||
var day = modified.getUTCDate(); | ||
date += ((day <= 9) ? " " : " ") + day; | ||
if (diff < -30 || diff > 180) | ||
date += " " + modified.getUTCFullYear(); | ||
else | ||
date += " " + ("0" + modified.getUTCHours()).slice(-2) + ":" + ("0" + modified.getUTCMinutes()).slice(-2); | ||
var nlink = (typeof this.nlink === 'undefined') ? 1 : this.nlink; | ||
return perms + " " + nlink + " user group " + len + " " + date; | ||
}; | ||
return SftpAttributes; | ||
})(); | ||
exports.SftpAttributes = SftpAttributes; |
@@ -8,2 +8,4 @@ var __extends = this.__extends || function (d, b) { | ||
var enums = require("./sftp-enums"); | ||
var charsets = require("./charsets"); | ||
var encodeUTF8 = charsets.encodeUTF8; | ||
var SftpPacket = (function () { | ||
@@ -22,3 +24,3 @@ function SftpPacket() { | ||
SftpPacket.isBuffer = function (obj) { | ||
return Buffer.isBuffer(obj); | ||
return Buffer.isBuffer(obj); //WEB: return obj && obj.buffer instanceof ArrayBuffer && typeof obj.byteLength !== "undefined"; | ||
}; | ||
@@ -44,2 +46,5 @@ return SftpPacket; | ||
this.id = this.readInt32(); | ||
if (this.type == 200 /* EXTENDED */) { | ||
this.type = this.readString(); | ||
} | ||
} | ||
@@ -49,10 +54,9 @@ } | ||
this.check(1); | ||
var value = this.buffer.readUInt8(this.position, true); | ||
this.position += 1; | ||
var value = this.buffer.readUInt8(this.position++, true); //WEB: var value = this.buffer[this.position++] & 0xFF; | ||
return value; | ||
}; | ||
SftpPacketReader.prototype.readInt32 = function () { | ||
this.check(4); | ||
var value = this.buffer.readInt32BE(this.position, true); | ||
this.position += 4; | ||
this.check(4); //WEB: var value = this.readUint32(); | ||
var value = this.buffer.readInt32BE(this.position, true); //WEB: if (value & 0x80000000) value -= 0x100000000; | ||
this.position += 4; //WEB: // removed | ||
return value; | ||
@@ -62,4 +66,8 @@ }; | ||
this.check(4); | ||
var value = this.buffer.readUInt32BE(this.position, true); | ||
this.position += 4; | ||
var value = this.buffer.readUInt32BE(this.position, true); //WEB: // removed | ||
this.position += 4; //WEB: var value = 0; | ||
//WEB: value |= (this.buffer[this.position++] & 0xFF) << 24; | ||
//WEB: value |= (this.buffer[this.position++] & 0xFF) << 16; | ||
//WEB: value |= (this.buffer[this.position++] & 0xFF) << 8; | ||
//WEB: value |= (this.buffer[this.position++] & 0xFF); | ||
return value; | ||
@@ -77,3 +85,3 @@ }; | ||
var end = this.position + length; | ||
var value = this.buffer.toString('utf8', this.position, end); | ||
var value = this.buffer.toString('utf8', this.position, end); //WEB: var value = decodeUTF8(this.buffer, this.position, end); | ||
this.position = end; | ||
@@ -94,9 +102,10 @@ return value; | ||
this.position = end; | ||
//WEB: var view = this.buffer.subarray(start, end); | ||
if (clone) { | ||
var buffer = new Buffer(length); | ||
this.buffer.copy(buffer, 0, start, end); | ||
var buffer = new Buffer(length); //WEB: var buffer = new Uint8Array(length); | ||
this.buffer.copy(buffer, 0, start, end); //WEB: buffer.set(view, 0); | ||
return buffer; | ||
} | ||
else { | ||
return this.buffer.slice(start, end); | ||
return this.buffer.slice(start, end); //WEB: return view; | ||
} | ||
@@ -118,3 +127,8 @@ }; | ||
this.writeInt32(0); // length placeholder | ||
this.writeByte(this.type | 0); | ||
if (typeof this.type === "number") { | ||
this.writeByte(this.type); | ||
} | ||
else { | ||
this.writeByte(200 /* EXTENDED */); | ||
} | ||
if (this.type == 1 /* INIT */ || this.type == 2 /* VERSION */) { | ||
@@ -124,2 +138,5 @@ } | ||
this.writeInt32(this.id | 0); | ||
if (typeof this.type !== "number") { | ||
this.writeString(this.type); | ||
} | ||
} | ||
@@ -129,14 +146,18 @@ }; | ||
var length = this.position; | ||
this.buffer.writeInt32BE(length - 4, 0, true); | ||
return this.buffer.slice(0, length); | ||
this.position = 0; | ||
this.buffer.writeInt32BE(length - 4, 0, true); //WEB: this.writeInt32(length - 4); | ||
return this.buffer.slice(0, length); //WEB: return this.buffer.subarray(0, length); | ||
}; | ||
SftpPacketWriter.prototype.writeByte = function (value) { | ||
this.check(1); | ||
this.buffer.writeInt8(value, this.position, true); | ||
this.position += 1; | ||
this.buffer.writeInt8(value, this.position++, true); //WEB: this.buffer[this.position++] = value & 0xFF; | ||
}; | ||
SftpPacketWriter.prototype.writeInt32 = function (value) { | ||
this.check(4); | ||
this.buffer.writeInt32BE(value, this.position, true); | ||
this.position += 4; | ||
this.buffer.writeInt32BE(value, this.position, true); //WEB: // removed | ||
this.position += 4; //WEB: // removed | ||
//WEB: this.buffer[this.position++] = (value >> 24) & 0xFF; | ||
//WEB: this.buffer[this.position++] = (value >> 16) & 0xFF; | ||
//WEB: this.buffer[this.position++] = (value >> 8) & 0xFF; | ||
//WEB: this.buffer[this.position++] = value & 0xFF; | ||
}; | ||
@@ -150,21 +171,21 @@ SftpPacketWriter.prototype.writeInt64 = function (value) { | ||
SftpPacketWriter.prototype.writeString = function (value) { | ||
if (typeof value !== "string") | ||
value = "" + value; | ||
var offset = this.position; | ||
this.writeInt32(0); // will get overwritten later | ||
var charLength = value.length; | ||
this.check(value.length); // does not ensure there is enough space (because of UTF-8) | ||
this.buffer._charsWritten = 0; | ||
var bytesWritten = this.buffer.write(value, this.position, undefined, 'utf-8'); | ||
var bytesWritten = encodeUTF8(value, this.buffer, this.position); | ||
if (bytesWritten < 0) | ||
throw new Error("Not enough space in the buffer"); | ||
// write number of bytes and seek back to the end | ||
this.position = offset; | ||
this.writeInt32(bytesWritten); | ||
this.position += bytesWritten; | ||
if (Buffer._charsWritten != charLength) | ||
throw new Error("Not enough space in the buffer"); | ||
// write number of bytes | ||
this.buffer.writeInt32BE(bytesWritten, offset, true); | ||
}; | ||
SftpPacketWriter.prototype.writeData = function (data, start, end) { | ||
if (typeof start !== 'undefined') | ||
data = data.slice(start, end); | ||
data = data.slice(start, end); //WEB: data = data.subarray(start, end); | ||
var length = data.length; | ||
this.writeInt32(length); | ||
this.check(length); | ||
data.copy(this.buffer, this.position, 0, length); | ||
data.copy(this.buffer, this.position, 0, length); //WEB: this.buffer.set(data, this.position); | ||
this.position += length; | ||
@@ -171,0 +192,0 @@ }; |
@@ -10,11 +10,11 @@ var __extends = this.__extends || function (d, b) { | ||
var safe = require("./fs-safe"); | ||
var fsmisc = require("./fs-misc"); | ||
var enums = require("./sftp-enums"); | ||
var channel = require("./channel"); | ||
var Channel = channel.Channel; | ||
var FileUtil = fsmisc.FileUtil; | ||
var SftpPacketWriter = packet.SftpPacketWriter; | ||
var SftpPacketReader = packet.SftpPacketReader; | ||
var SftpItem = misc.SftpItem; | ||
var SftpAttributes = misc.SftpAttributes; | ||
var SftpStatus = misc.SftpStatus; | ||
var SftpFlags = misc.SftpFlags; | ||
var SftpExtensions = misc.SftpExtensions; | ||
var SftpResponse = (function (_super) { | ||
@@ -36,13 +36,136 @@ __extends(SftpResponse, _super); | ||
})(); | ||
var SftpServerSessionCore = (function () { | ||
function SftpServerSessionCore(host, fs) { | ||
var SftpException = (function () { | ||
function SftpException(err) { | ||
var message; | ||
var code = 4 /* FAILURE */; | ||
var errno = err.errno | 0; | ||
switch (errno) { | ||
default: | ||
if (err["isPublic"] === true) | ||
message = err.message; | ||
else | ||
message = "Unspecified error (" + errno + ")"; | ||
break; | ||
case 1: | ||
message = "End of file"; | ||
code = 1 /* EOF */; | ||
break; | ||
case 3: | ||
message = "Permission denied"; | ||
code = 3 /* PERMISSION_DENIED */; | ||
break; | ||
case 4: | ||
message = "Try again"; | ||
break; | ||
case 9: | ||
message = "Bad file number"; | ||
break; | ||
case 10: | ||
message = "Device or resource busy"; | ||
break; | ||
case 18: | ||
message = "Invalid argument"; | ||
break; | ||
case 20: | ||
message = "Too many open files"; | ||
break; | ||
case 24: | ||
message = "File table overflow"; | ||
break; | ||
case 25: | ||
message = "No buffer space available"; | ||
break; | ||
case 26: | ||
message = "Out of memory"; | ||
break; | ||
case 27: | ||
message = "Not a directory"; | ||
break; | ||
case 28: | ||
message = "Is a directory"; | ||
break; | ||
case -4058: | ||
case 34: | ||
message = "No such file or directory"; | ||
code = 2 /* NO_SUCH_FILE */; | ||
break; | ||
case 35: | ||
message = "Function not implemented"; | ||
code = 8 /* OP_UNSUPPORTED */; | ||
break; | ||
case 47: | ||
message = "File exists"; | ||
break; | ||
case 49: | ||
message = "File name too long"; | ||
break; | ||
case 50: | ||
message = "Operation not permitted"; | ||
break; | ||
case 51: | ||
message = "Too many symbolic links encountered"; | ||
break; | ||
case 52: | ||
message = "Cross-device link"; | ||
break; | ||
case 53: | ||
message = "Directory not empty"; | ||
break; | ||
case 54: | ||
message = "No space left on device"; | ||
break; | ||
case 55: | ||
message = "I/O error"; | ||
break; | ||
case 56: | ||
message = "Read-only file system"; | ||
break; | ||
case 57: | ||
message = "No such device"; | ||
code = 2 /* NO_SUCH_FILE */; | ||
break; | ||
case 58: | ||
message = "Illegal seek"; | ||
break; | ||
case 59: | ||
message = "Operation canceled"; | ||
break; | ||
} | ||
this.name = "SftpException"; | ||
this.message = message; | ||
this.code = code; | ||
this.errno = errno; | ||
} | ||
return SftpException; | ||
})(); | ||
var SftpServerSession = (function () { | ||
function SftpServerSession(channel, fs, emitter, log) { | ||
var _this = this; | ||
this._fs = fs; | ||
this._host = host; | ||
this._handles = new Array(SftpServerSessionCore.MAX_HANDLE_COUNT + 1); | ||
this._channel = channel; | ||
this._log = log; | ||
this._handles = new Array(SftpServerSession.MAX_HANDLE_COUNT + 1); | ||
this.nextHandle = 1; | ||
channel.on("message", function (packet) { | ||
try { | ||
_this._process(packet); | ||
} | ||
catch (err) { | ||
emitter.emit("error", err, _this); | ||
_this.end(); | ||
} | ||
}); | ||
channel.on("error", function (err) { | ||
emitter.emit("error", err, _this); | ||
_this.end(); | ||
}); | ||
channel.on("close", function (err) { | ||
_this._end(); | ||
emitter.emit("closedSession", _this, err); | ||
}); | ||
} | ||
SftpServerSessionCore.prototype.send = function (response) { | ||
SftpServerSession.prototype.send = function (response) { | ||
// send packet | ||
var packet = response.finish(); | ||
this._host.send(packet); | ||
this._channel.send(packet); | ||
// start next task | ||
@@ -53,20 +176,29 @@ if (typeof response.handleInfo === 'object') { | ||
}; | ||
SftpServerSessionCore.prototype.sendStatus = function (response, code, message) { | ||
SftpServerSession.prototype.sendStatus = function (response, code, message) { | ||
SftpStatus.write(response, code, message); | ||
this.send(response); | ||
}; | ||
SftpServerSessionCore.prototype.sendError = function (response, err) { | ||
var log = this._host.log; | ||
if (typeof log === 'object' && typeof log.error === 'function') | ||
log.error(err); | ||
SftpStatus.writeError(response, err); | ||
SftpServerSession.prototype.sendError = function (response, err, isFatal) { | ||
var message; | ||
var code; | ||
if (!isFatal) { | ||
var error = new SftpException(err); | ||
code = error.code; | ||
message = error.message; | ||
} | ||
else { | ||
code = 4 /* FAILURE */; | ||
message = "Internal server error"; | ||
this._log.error("Fatal error while processing request #" + response.id + ": " + err); | ||
} | ||
SftpStatus.write(response, code, message); | ||
this.send(response); | ||
}; | ||
SftpServerSessionCore.prototype.sendIfError = function (response, err) { | ||
SftpServerSession.prototype.sendIfError = function (response, err) { | ||
if (err == null || typeof err === 'undefined') | ||
return false; | ||
this.sendError(response, err); | ||
this.sendError(response, err, false); | ||
return true; | ||
}; | ||
SftpServerSessionCore.prototype.sendSuccess = function (response, err) { | ||
SftpServerSession.prototype.sendSuccess = function (response, err) { | ||
if (this.sendIfError(response, err)) | ||
@@ -77,3 +209,3 @@ return; | ||
}; | ||
SftpServerSessionCore.prototype.sendAttribs = function (response, err, stats) { | ||
SftpServerSession.prototype.sendAttribs = function (response, err, stats) { | ||
if (this.sendIfError(response, err)) | ||
@@ -88,3 +220,3 @@ return; | ||
}; | ||
SftpServerSessionCore.prototype.sendHandle = function (response, handleInfo) { | ||
SftpServerSession.prototype.sendHandle = function (response, handleInfo) { | ||
response.type = 102 /* HANDLE */; | ||
@@ -96,3 +228,3 @@ response.start(); | ||
}; | ||
SftpServerSessionCore.prototype.sendPath = function (response, err, path) { | ||
SftpServerSession.prototype.sendPath = function (response, err, path) { | ||
if (this.sendIfError(response, err)) | ||
@@ -108,3 +240,12 @@ return; | ||
}; | ||
SftpServerSessionCore.prototype.readHandleInfo = function (request) { | ||
SftpServerSession.prototype.writeItem = function (response, item) { | ||
var attr = new SftpAttributes(); | ||
attr.from(item.stats); | ||
var filename = item.filename; | ||
var longname = item.longname || FileUtil.toString(filename, attr); | ||
response.writeString(filename); | ||
response.writeString(longname); | ||
attr.write(response); | ||
}; | ||
SftpServerSession.prototype.readHandleInfo = function (request) { | ||
// read a 4-byte handle | ||
@@ -119,5 +260,5 @@ if (request.readInt32() != 4) | ||
}; | ||
SftpServerSessionCore.prototype.createHandleInfo = function () { | ||
SftpServerSession.prototype.createHandleInfo = function () { | ||
var h = this.nextHandle; | ||
var max = SftpServerSessionCore.MAX_HANDLE_COUNT; | ||
var max = SftpServerSession.MAX_HANDLE_COUNT; | ||
for (var i = 0; i < max; i++) { | ||
@@ -136,4 +277,7 @@ var next = (h % max) + 1; // 1..MAX_HANDLE_COUNT | ||
}; | ||
SftpServerSessionCore.prototype.deleteHandleInfo = function (handleInfo) { | ||
SftpServerSession.prototype.deleteHandleInfo = function (handleInfo) { | ||
var h = handleInfo.h; | ||
if (h < 0) | ||
return; | ||
handleInfo.h = -1; | ||
var handleInfo = this._handles[h]; | ||
@@ -144,6 +288,6 @@ if (typeof handleInfo !== 'object') | ||
}; | ||
SftpServerSessionCore.prototype.end = function () { | ||
this._host.close(); | ||
SftpServerSession.prototype.end = function () { | ||
this._channel.close(); | ||
}; | ||
SftpServerSessionCore.prototype._end = function () { | ||
SftpServerSession.prototype._end = function () { | ||
var _this = this; | ||
@@ -159,3 +303,3 @@ if (typeof this._fs === 'undefined') | ||
}; | ||
SftpServerSessionCore.prototype._process = function (data) { | ||
SftpServerSession.prototype._process = function (data) { | ||
var _this = this; | ||
@@ -200,8 +344,13 @@ var request = new SftpPacketReader(data); | ||
else { | ||
handleInfo.tasks.push(function () { return _this.processRequest(request, response, handleInfo); }); | ||
handleInfo.tasks.push(function () { | ||
if (handleInfo.h < 0) | ||
_this.sendStatus(response, 4 /* FAILURE */, "Invalid handle"); | ||
else | ||
_this.processRequest(request, response, handleInfo); | ||
}); | ||
} | ||
}; | ||
SftpServerSessionCore.prototype.processNext = function (handleInfo) { | ||
SftpServerSession.prototype.processNext = function (handleInfo) { | ||
if (handleInfo.tasks.length > 0) { | ||
var task = handleInfo.tasks.pop(); | ||
var task = handleInfo.tasks.shift(); | ||
task(); | ||
@@ -213,3 +362,3 @@ } | ||
}; | ||
SftpServerSessionCore.prototype.processRequest = function (request, response, handleInfo) { | ||
SftpServerSession.prototype.processRequest = function (request, response, handleInfo) { | ||
var _this = this; | ||
@@ -231,3 +380,3 @@ var fs = this._fs; | ||
var attrs = new SftpAttributes(request); | ||
var modes = SftpFlags.fromFlags(pflags); | ||
var modes = SftpFlags.fromNumber(pflags); | ||
if (modes.length == 0) { | ||
@@ -281,2 +430,6 @@ this.sendStatus(response, 4 /* FAILURE */, "Unsupported flags"); | ||
return; | ||
if (bytesRead == 0) { | ||
_this.sendStatus(response, 1 /* EOF */, "EOF"); | ||
return; | ||
} | ||
response.writeInt32(bytesRead); | ||
@@ -348,5 +501,4 @@ response.skip(bytesRead); | ||
while (list.length > 0) { | ||
var it = list.shift(); | ||
var item = new SftpItem(it.filename, it.stats); | ||
item.write(response); | ||
var item = list.shift(); | ||
_this.writeItem(response, item); | ||
count++; | ||
@@ -411,2 +563,7 @@ if (response.position > 0x7000) { | ||
return; | ||
case SftpExtensions.HARDLINK: | ||
var oldpath = request.readString(); | ||
var newpath = request.readString(); | ||
fs.link(oldpath, newpath, function (err) { return _this.sendSuccess(response, err); }); | ||
return; | ||
default: | ||
@@ -417,19 +574,8 @@ this.sendStatus(response, 8 /* OP_UNSUPPORTED */, "Not supported"); | ||
catch (err) { | ||
this.sendError(response, err); | ||
this.sendError(response, err, true); | ||
} | ||
}; | ||
SftpServerSessionCore.MAX_HANDLE_COUNT = 512; | ||
return SftpServerSessionCore; | ||
SftpServerSession.MAX_HANDLE_COUNT = 512; | ||
return SftpServerSession; | ||
})(); | ||
exports.SftpServerSessionCore = SftpServerSessionCore; | ||
var SftpServerSession = (function (_super) { | ||
__extends(SftpServerSession, _super); | ||
function SftpServerSession(ws, fs, log) { | ||
var channel = new Channel(this, ws); | ||
channel.log = log; | ||
_super.call(this, channel, fs); | ||
channel.start(); | ||
} | ||
return SftpServerSession; | ||
})(SftpServerSessionCore); | ||
exports.SftpServerSession = SftpServerSession; |
@@ -11,2 +11,3 @@ /// <reference path="../typings/node/node.d.ts" /> | ||
var path = require("path"); | ||
var events = require("events"); | ||
var client = require("./sftp-client"); | ||
@@ -16,7 +17,9 @@ var server = require("./sftp-server"); | ||
var local = require("./fs-local"); | ||
var channel = require("./channel"); | ||
var plus = require("./fs-plus"); | ||
var channel_ws = require("./channel-ws"); | ||
var channel_stream = require("./channel-stream"); | ||
var util = require("./util"); | ||
var SftpClient = client.SftpClient; | ||
var SafeFilesystem = safe.SafeFilesystem; | ||
var WebSocketServer = WebSocket.Server; | ||
var WebSocketChannel = channel_ws.WebSocketChannel; | ||
var SftpServerSession = server.SftpServerSession; | ||
@@ -27,6 +30,14 @@ var SFTP; | ||
__extends(Client, _super); | ||
function Client(address, options) { | ||
if (typeof options == 'undefined') { | ||
options = {}; | ||
} | ||
function Client() { | ||
var localFs = new local.LocalFilesystem(); | ||
_super.call(this, localFs); | ||
} | ||
Client.prototype.on = function (event, listener) { | ||
return _super.prototype.on.call(this, event, listener); | ||
}; | ||
Client.prototype.once = function (event, listener) { | ||
return _super.prototype.on.call(this, event, listener); | ||
}; | ||
Client.prototype.connect = function (address, options, callback) { | ||
options = options || {}; | ||
if (typeof options.protocol == 'undefined') { | ||
@@ -36,7 +47,22 @@ options.protocol = 'sftp'; | ||
var ws = new WebSocket(address, options); | ||
_super.call(this, ws, options.log); | ||
} | ||
var channel = new WebSocketChannel(ws); | ||
_super.prototype.bind.call(this, channel, callback); | ||
}; | ||
return Client; | ||
})(SftpClient); | ||
})(client.SftpClient); | ||
SFTP.Client = Client; | ||
var Local = (function (_super) { | ||
__extends(Local, _super); | ||
function Local() { | ||
var fs = new local.LocalFilesystem(); | ||
_super.call(this, fs, null); | ||
} | ||
return Local; | ||
})(plus.FilesystemPlus); | ||
SFTP.Local = Local; | ||
var Channels; | ||
(function (Channels) { | ||
Channels.StreamChannel = channel_stream.StreamChannel; | ||
Channels.WebSocketChannel = channel_ws.WebSocketChannel; | ||
})(Channels = SFTP.Channels || (SFTP.Channels = {})); | ||
var RequestInfo = (function () { | ||
@@ -48,5 +74,7 @@ function RequestInfo() { | ||
SFTP.RequestInfo = RequestInfo; | ||
var Server = (function () { | ||
var Server = (function (_super) { | ||
__extends(Server, _super); | ||
function Server(options) { | ||
var _this = this; | ||
_super.call(this); | ||
var serverOptions = {}; | ||
@@ -149,11 +177,12 @@ var noServer = false; | ||
log.info("Connection accepted."); | ||
var options = { binary: true }; | ||
var fs = new SafeFilesystem(this._fs, this._virtualRoot, this._readOnly); | ||
var session = new SftpServerSession(ws, fs, log); | ||
var channel = new WebSocketChannel(ws); | ||
var session = new SftpServerSession(channel, fs, this, log); | ||
this.emit("startedSession", this); | ||
ws.session = session; | ||
}; | ||
return Server; | ||
})(); | ||
})(events.EventEmitter); | ||
SFTP.Server = Server; | ||
})(SFTP || (SFTP = {})); | ||
module.exports = SFTP; |
@@ -0,1 +1,10 @@ | ||
var __extends = this.__extends || function (d, b) { | ||
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; | ||
function __() { this.constructor = d; } | ||
__.prototype = b.prototype; | ||
d.prototype = new __(); | ||
}; | ||
/// <reference path="../typings/node/node.d.ts" /> | ||
var events = require("events"); | ||
var EventEmitter = events.EventEmitter; | ||
function toLogWriter(writer) { | ||
@@ -28,1 +37,71 @@ writer = writer || {}; | ||
exports.toLogWriter = toLogWriter; | ||
var Task = (function (_super) { | ||
__extends(Task, _super); | ||
function Task() { | ||
_super.call(this); | ||
} | ||
Task.prototype.on = function (event, listener) { | ||
return _super.prototype.on.call(this, event, listener); | ||
}; | ||
return Task; | ||
})(EventEmitter); | ||
exports.Task = Task; | ||
function wrapCallback(owner, task, callback) { | ||
return finish; | ||
function finish(err) { | ||
var args = []; | ||
for (var _i = 1; _i < arguments.length; _i++) { | ||
args[_i - 1] = arguments[_i]; | ||
} | ||
var error = arguments[0]; | ||
try { | ||
if (typeof callback === 'function') { | ||
callback.apply(owner, arguments); | ||
error = null; | ||
} | ||
else if (task) { | ||
if (!error) { | ||
switch (arguments.length) { | ||
case 0: | ||
case 1: | ||
task.emit("success"); | ||
task.emit("finish", error); | ||
break; | ||
case 2: | ||
task.emit("success", arguments[1]); | ||
task.emit("finish", error, arguments[1]); | ||
break; | ||
case 3: | ||
task.emit("success", arguments[1], arguments[2]); | ||
task.emit("finish", error, arguments[1], arguments[2]); | ||
break; | ||
default: | ||
arguments[0] = "success"; | ||
task.emit.apply(task, arguments); | ||
if (EventEmitter.listenerCount(task, "finish") > 0) { | ||
arguments[0] = "finish"; | ||
Array.prototype.splice.call(arguments, 1, 0, error); | ||
task.emit.apply(task, arguments); | ||
} | ||
break; | ||
} | ||
} | ||
else { | ||
if (EventEmitter.listenerCount(task, "error")) { | ||
task.emit("error", error); | ||
error = null; | ||
} | ||
task.emit("finish", error); | ||
} | ||
} | ||
} | ||
catch (err) { | ||
if (error) | ||
owner.emit("error", error); | ||
error = err; | ||
} | ||
if (error) | ||
owner.emit("error", error); | ||
} | ||
} | ||
exports.wrapCallback = wrapCallback; |
{ | ||
"name": "sftp-ws", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"description": "SFTP over WebSockets - client and server library", | ||
@@ -22,6 +22,3 @@ "main": "./lib/sftp.js", | ||
"keywords": ["sftp", "server", "client", "ws"], | ||
"licenses": [{ | ||
"type": "MIT", | ||
"url": "https://github.com/lukaaash/sftp-ws/blob/master/LICENSE" | ||
}], | ||
"license": "MIT", | ||
"repository": { | ||
@@ -28,0 +25,0 @@ "type": "git", |
sftp-ws | ||
======= | ||
v0.2.0 | ||
v0.3.0 | ||
------ | ||
@@ -12,2 +12,3 @@ | ||
SFTP is a simple remote filesystem protocol misnamed as *SSH File Transfer Protocol*. This package provides SFTP v3, but layers it on top of WebSockets instead of SSH. | ||
This makes it possible to run an SFTP client in any modern web browser. | ||
Check out my [blogpost](http://lukas.pokorny.eu/sftp-over-websockets/) for more information. | ||
@@ -25,3 +26,4 @@ | ||
The client API aims to be compatible with SFTP client in [ssh2 module](https://github.com/mscdex/ssh2) by Brian White. | ||
The SFTP client provides a high-level API for multi-file operations, but it also aims to be compatible with SFTP client in [ssh2 module](https://github.com/mscdex/ssh2) by Brian White. | ||
Einaros [ws module](https://github.com/einaros/ws) is used to handle WebSockets and this is reflected in parts of the client and server API as well. | ||
@@ -34,8 +36,11 @@ | ||
// initialize SFTP over WebSocket connection | ||
var client = new SftpClient('ws://localhost/path'); | ||
// create an SFTP over WebSockets object | ||
var client = new SFTP.Client(); | ||
// connect to a server | ||
client.connect('ws://localhost/path'); | ||
// handle errors | ||
client.on('error', function (err) { | ||
console.log('Error : %s', err.message); | ||
console.log('Error: %s', err.message); | ||
}); | ||
@@ -47,8 +52,11 @@ | ||
// retrieve directory listing | ||
client.readdir('.', function (err, listing) { | ||
console.log(listing); | ||
client.list('.').on('success', function (list) { | ||
// display the listing | ||
list.forEach(function (item) { | ||
return console.log(item.longname); | ||
}); | ||
// close the connection | ||
client.end(); | ||
client.end(); | ||
}); | ||
@@ -58,2 +66,13 @@ }); | ||
### SFTP client - downloading files | ||
```javascript | ||
// initialize an SFTP client object here | ||
// download all files matching the pattern | ||
// (into the current local directory) | ||
client.download('sftp-ws-'.tgz", '.'); | ||
``` | ||
### SFTP server - listening for connections: | ||
@@ -76,3 +95,3 @@ | ||
This includes a proof-of-concept version of a [browser-based SFTP/WS client](https://github.com/lukaaash/sftp-ws/tree/v0.1.0/examples/web-client). | ||
This includes a proof-of-concept version of a [browser-based SFTP/WS client](https://github.com/lukaaash/sftp-ws/tree/v0.3.0/examples/web-client). | ||
@@ -84,3 +103,3 @@ ## Virtual filesystems | ||
```typescript | ||
export interface IFilesystem { | ||
interface IFilesystem { | ||
open(path: string, flags: string, attrs?: IStats, callback?: (err: Error, handle: any) => any): void; | ||
@@ -95,3 +114,3 @@ close(handle: any, callback?: (err: Error) => any): void; | ||
opendir(path: string, callback?: (err: Error, handle: any) => any): void; | ||
readdir(handle: any, callback?: (err: Error, items: IItem[]|boolean) => any): void; | ||
readdir(handle: any, callback?: (err: Error, items: IItem[]|boolean) => any): void; | ||
unlink(path: string, callback?: (err: Error) => any): void; | ||
@@ -105,9 +124,5 @@ mkdir(path: string, attrs?: IStats, callback?: (err: Error) => any): void; | ||
symlink(targetpath: string, linkpath: string, callback?: (err: Error) => any): void; | ||
link(oldPath: string, newPath: string, callback?: (err: Error) => any): void; | ||
} | ||
export interface IItem { | ||
filename: string; | ||
stats?: IStats; | ||
} | ||
export interface IStats { | ||
@@ -120,3 +135,15 @@ mode?: number; | ||
mtime?: Date; | ||
isFile? (): boolean; | ||
isDirectory? (): boolean; | ||
isSymbolicLink? (): boolean; | ||
} | ||
export interface IItem { | ||
filename: string; | ||
stats: IStats; | ||
longname?: string; | ||
path?: string; | ||
} | ||
``` | ||
@@ -136,3 +163,3 @@ | ||
- More unit tests | ||
- Documentation | ||
- Better documentation | ||
- Proper browser-based client | ||
@@ -139,0 +166,0 @@ - Client-side wrapper around `IFilesystem` to simplify common tasks |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
177241
22
4642
157
3