Comparing version 1.2.1 to 1.3.0
222
lib/jsftp.js
@@ -15,3 +15,2 @@ /* vim:set ts=2 sw=2 sts=2 expandtab */ | ||
var ListingParser = require("parse-listing"); | ||
var Utils = require("./utils"); | ||
var util = require("util"); | ||
@@ -22,5 +21,5 @@ var fs = require("fs"); | ||
var FTP_PORT = 21; | ||
var DEBUG_MODE = false; | ||
var TIMEOUT = 10 * 60 * 1000; | ||
var IDLE_TIME = 30000; | ||
var NOOP = function() {}; | ||
var COMMANDS = [ | ||
@@ -31,3 +30,3 @@ // Commands without parameters | ||
"cwd", "dele", "list", "mdtm", "mkd", "mode", "nlst", "pass", "retr", "rmd", | ||
"rnfr", "rnto", "site", "stat", "stor", "type", "user", "pass", "xrmd", "opts", | ||
"rnfr", "rnto", "site", "stat", "stor", "type", "user", "xrmd", "opts", | ||
// Extended features | ||
@@ -37,19 +36,28 @@ "chmod", "size" | ||
var Cmds = {}; | ||
COMMANDS.forEach(function(cmd) { | ||
cmd = cmd.toLowerCase(); | ||
Cmds[cmd] = function() { | ||
var callback = function() {}; | ||
var completeCmd = cmd; | ||
if (arguments.length) { | ||
var args = Array.prototype.slice.call(arguments); | ||
if (typeof args[args.length - 1] === "function") | ||
callback = args.pop(); | ||
function getPasvPort(text, cb) { | ||
var RE_PASV = /([-\d]+,[-\d]+,[-\d]+,[-\d]+),([-\d]+),([-\d]+)/; | ||
var match = RE_PASV.exec(text); | ||
if (!match) { | ||
return cb(new Error("Bad passive host/port combination")); | ||
} | ||
completeCmd += " " + args.join(" "); | ||
} | ||
this.execute(completeCmd.trim(), callback); | ||
}; | ||
}); | ||
cb(null, { | ||
host: match[1].replace(/,/g, "."), | ||
port: (parseInt(match[2], 10) & 255) * 256 + (parseInt(match[3], 10) & 255) | ||
}); | ||
} | ||
function runCmd(cmd) { | ||
var callback = NOOP; | ||
var args = [].slice.call(arguments); | ||
var completeCmd = args.shift(); | ||
if (args.length) { | ||
if (typeof args[args.length - 1] === "function") | ||
callback = args.pop(); | ||
completeCmd += " " + args.join(" "); | ||
} | ||
this.execute(completeCmd.trim(), callback); | ||
} | ||
var Ftp = module.exports = function(cfg) { | ||
@@ -76,5 +84,16 @@ "use strict"; | ||
// it is the responsability of the user to validate the parameters. | ||
var raw = this.raw = {}; | ||
COMMANDS.forEach(function(cmd) { raw[cmd] = Cmds[cmd].bind(this); }, this); | ||
this.raw = function() { | ||
return runCmd.apply(this, arguments); | ||
}.bind(this); | ||
COMMANDS.forEach(function(cmd) { | ||
this.raw[cmd] = runCmd.bind(this, cmd); | ||
}, this); | ||
var self = this; | ||
this.on('data', function(data) { | ||
if (self.debugMode) { | ||
self.emit('jsftp_debug', 'response', data || {}); | ||
} | ||
}); | ||
this.socket = this._createSocket(this.port, this.host); | ||
@@ -85,5 +104,14 @@ }; | ||
Ftp.prototype.setDebugMode = function(debugOn) { | ||
this.debugMode = (debugOn !== false); | ||
}; | ||
Ftp.prototype.reemit = function(event) { | ||
var self = this; | ||
return function(data) { self.emit(event, data); } | ||
return function(data) { | ||
self.emit(event, data); | ||
if (self.debugMode) { | ||
self.emit('jsftp_debug', 'event:' + event, data || {}); | ||
} | ||
} | ||
}; | ||
@@ -95,9 +123,6 @@ | ||
this.authenticated = false; | ||
var socket = Net.createConnection(port, host); | ||
var socket = Net.createConnection(port, host, firstAction || NOOP); | ||
socket.on("connect", this.reemit("connect")); | ||
socket.on("timeout", this.reemit("timeout")); | ||
if (firstAction) | ||
socket.once("connect", firstAction); | ||
this._createStreams(socket); | ||
@@ -147,6 +172,6 @@ | ||
/** | ||
* Writes a new command to the server. | ||
* Sends a new command to the server. | ||
* | ||
* @param {String} command Command to write in the FTP socket | ||
* @returns void | ||
* @return void | ||
*/ | ||
@@ -159,2 +184,6 @@ Ftp.prototype.send = function(command) { | ||
this.pipeline.write(command + "\r\n"); | ||
if (this.debugMode) { | ||
this.emit('jsftp_debug', 'user_command', command || {}); | ||
} | ||
}; | ||
@@ -184,17 +213,18 @@ | ||
Ftp.prototype.execute = function(action, callback) { | ||
if (!callback) callback = function() {}; | ||
if (!callback) callback = NOOP; | ||
if (this.socket && this.socket.writable) { | ||
this._executeCommand(action, callback); | ||
} else { | ||
var self = this; | ||
this.authenticated = false; | ||
this.socket = this._createSocket(this.port, this.host, function() { | ||
self._executeCommand(action, callback); | ||
}); | ||
return this.runCommand(action, callback); | ||
} | ||
var self = this; | ||
this.authenticated = false; | ||
this.socket = this._createSocket(this.port, this.host, function() { | ||
self.runCommand(action, callback); | ||
}); | ||
}; | ||
Ftp.prototype._executeCommand = function(action, callback) { | ||
Ftp.prototype.runCommand = function(action, callback) { | ||
var self = this; | ||
function executeCmd() { | ||
@@ -206,8 +236,8 @@ self.cmdBuffer_.push([action, callback]); | ||
if (self.authenticated || /feat|syst|user|pass/.test(action)) { | ||
executeCmd(); | ||
} else { | ||
this.getFeatures(function() { | ||
self.auth(self.user, self.pass, executeCmd); | ||
}); | ||
return executeCmd(); | ||
} | ||
this.getFeatures(function() { | ||
self.auth(self.user, self.pass, executeCmd); | ||
}); | ||
}; | ||
@@ -218,8 +248,6 @@ | ||
* together. That is, each time that there is a round trip of actions | ||
* between the client and the server. The `action` param contains an array | ||
* with the response from the server as a first element (text) and an array | ||
* with the command executed and the callback (if any) as the second | ||
* element. | ||
* between the client and the server. | ||
* | ||
* @param action {Array} Contains server response and client command info. | ||
* @param response {Object} Response from the server (contains text and code). | ||
* @param command {Array} Contains the command executed and a callback (if any). | ||
*/ | ||
@@ -239,3 +267,3 @@ Ftp.prototype.parse = function(response, command) { | ||
/** | ||
* Returns true if the current server has the requested feature. False otherwise. | ||
* Returns true if the current server has the requested feature. | ||
* | ||
@@ -246,4 +274,3 @@ * @param {String} feature Feature to look for | ||
Ftp.prototype.hasFeat = function(feature) { | ||
if (feature) | ||
return this.features.indexOf(feature.toLowerCase()) > -1; | ||
return !!feature && this.features.indexOf(feature.toLowerCase()) > -1; | ||
}; | ||
@@ -264,6 +291,4 @@ | ||
// Below this point all the methods are action helpers for FTP that compose | ||
// several actions in one command | ||
Ftp.prototype.getFeatures = function(callback) { | ||
@@ -311,6 +336,5 @@ var self = this; | ||
if (err) | ||
notifyAll(new Error("Login not accepted")); | ||
if ([230, 202].indexOf(res.code) > -1) { | ||
if (err) { | ||
notifyAll(err); | ||
} else if ([230, 202].indexOf(res.code) > -1) { | ||
self.authenticated = true; | ||
@@ -328,3 +352,3 @@ self.user = user; | ||
self.authenticating = false; | ||
notifyAll(new Error("Login not accepted")); | ||
notifyAll(err); | ||
} | ||
@@ -372,2 +396,4 @@ }); | ||
self.getPasvSocket(function(err, socket) { | ||
self.pasvTimeout.bind(self, socket, cb); | ||
socket.on("data", function(data) { | ||
@@ -409,6 +435,6 @@ listing += data; | ||
if (arguments.length === 2) { | ||
callback = once(localPath || function() {}); | ||
callback = once(localPath || NOOP); | ||
this.getGetSocket(remotePath, callback); | ||
} else { | ||
callback = once(callback || function() {}); | ||
callback = once(callback || NOOP); | ||
this.getGetSocket(remotePath, function(err, socket) { | ||
@@ -450,2 +476,3 @@ if (err) { | ||
self.pasvTimeout.bind(self, socket, cmdCallback); | ||
socket.pause(); | ||
@@ -479,2 +506,20 @@ | ||
Ftp.prototype.put = function(from, to, callback) { | ||
var self = this; | ||
function putReadable(from, to, totalSize, callback) { | ||
from.on("readable", function() { | ||
self.emitProgress({ | ||
filename: to, | ||
action: "put", | ||
socket: from, | ||
totalSize: totalSize | ||
}); | ||
}); | ||
self.getPutSocket(to, function(err, socket) { | ||
if (err) return; | ||
from.pipe(socket); | ||
}, callback); | ||
} | ||
if (from instanceof Buffer) { | ||
@@ -484,4 +529,3 @@ this.getPutSocket(to, function(err, socket) { | ||
}, callback); | ||
} else { | ||
var self = this; | ||
} else if (typeof from === "string" || from instanceof String) { | ||
fs.exists(from, function(exists) { | ||
@@ -491,22 +535,12 @@ if (!exists) | ||
self.getPutSocket(to, function(err, socket) { | ||
if (err) return; | ||
fs.stat(from, function(err, stats) { | ||
var totalSize = err ? 0 : stats.size; | ||
var read = fs.createReadStream(from, { | ||
bufferSize: 4 * 1024 | ||
}); | ||
read.pipe(socket); | ||
read.on('readable', function() { | ||
self.emitProgress({ | ||
filename: to, | ||
action: 'put', | ||
socket: read, | ||
totalSize: totalSize | ||
}); | ||
}); | ||
fs.stat(from, function(err, stats) { | ||
var totalSize = err ? 0 : stats.size; | ||
var read = fs.createReadStream(from, { | ||
bufferSize: 4 * 1024 | ||
}); | ||
}, callback); | ||
putReadable(read, to, totalSize, callback); | ||
}); | ||
}); | ||
} else { | ||
putReadable(from, to, from.size, callback); | ||
} | ||
@@ -518,3 +552,3 @@ }; | ||
doneCallback = once(doneCallback || function() {}); | ||
doneCallback = once(doneCallback || NOOP); | ||
var _callback = once(function(err, _socket) { | ||
@@ -540,2 +574,3 @@ if (err) { | ||
socket.on('error', doneCallback); | ||
self.pasvTimeout.bind(self, socket, doneCallback); | ||
_callback(null, socket); | ||
@@ -554,17 +589,24 @@ } else { | ||
Ftp.prototype.pasvTimeout = function(socket, cb) { | ||
var self = this; | ||
socket.once('timeout', function() { | ||
self.emit('timeout'); | ||
socket.destroy(); | ||
cb(new Error("Passive socket timeout")); | ||
}); | ||
}; | ||
Ftp.prototype.getPasvSocket = function(callback) { | ||
var timeout = this.timeout; | ||
callback = once(callback || function() {}); | ||
callback = once(callback || NOOP); | ||
this.execute("pasv", function(err, res) { | ||
if (err) return callback(err); | ||
var pasvRes = Utils.getPasvPort(res.text); | ||
if (pasvRes === false) | ||
return callback(new Error("PASV: Bad host/port combination")); | ||
getPasvPort(res.text, function(err, res) { | ||
if (err) return callback(err); | ||
var host = pasvRes[0]; | ||
var port = pasvRes[1]; | ||
var socket = Net.createConnection(port, host); | ||
socket.setTimeout(timeout || TIMEOUT); | ||
callback(null, socket); | ||
var socket = Net.createConnection(res.port, res.host); | ||
socket.setTimeout(timeout || TIMEOUT); | ||
callback(null, socket); | ||
}); | ||
}); | ||
@@ -643,3 +685,3 @@ }; | ||
Ftp.prototype.keepAlive = function() { | ||
Ftp.prototype.keepAlive = function(wait) { | ||
var self = this; | ||
@@ -649,3 +691,3 @@ if (this._keepAliveInterval) | ||
this._keepAliveInterval = setInterval(self.raw.noop, IDLE_TIME); | ||
this._keepAliveInterval = setInterval(self.raw.noop, wait || IDLE_TIME); | ||
}; | ||
@@ -652,0 +694,0 @@ |
{ | ||
"name": "jsftp", | ||
"id": "jsftp", | ||
"version": "1.2.1", | ||
"version": "1.3.0", | ||
"description": "A sane FTP client implementation for NodeJS", | ||
@@ -6,0 +6,0 @@ "keywords": [ "ftp", "protocol", "files", "server", "client", "async" ], |
@@ -200,4 +200,4 @@ jsftp <a href="http://flattr.com/thing/1452098/" target="_blank"><img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this" border="0" /></a> | ||
#### Ftp.keepAlive() | ||
Refreshes the interval thats keep the server connection active. | ||
#### Ftp.keepAlive([wait]) | ||
Refreshes the interval thats keep the server connection active. `wait` is an optional time period (in milliseconds) to wait between intervals. | ||
@@ -207,2 +207,50 @@ You can find more usage examples in the [unit tests](https://github.com/sergi/jsftp/blob/master/test/jsftp_test.js). This documentation | ||
Debugging | ||
--------- | ||
In order to enable debug mode in a FTP connection, a `debugMode` parameter can | ||
be used in the constructors's config object: | ||
```javascript | ||
var Ftp = new JSFtp({ | ||
host: "myserver.com", | ||
port: 3331, | ||
user: "user", | ||
pass: "1234", | ||
debugMode: true | ||
}); | ||
``` | ||
It can also be activated or deactivated by calling the `setDebugMode` method: | ||
```javascript | ||
Ftp.setDebugMode(true); // Debug Mode on | ||
Ftp.setDebugMode(false; // Debug mode off | ||
``` | ||
If the debug mode is on, the jsftp instance will emit `jsftp_debug` events with | ||
two parameters: the first is the type of the event and the second and object | ||
including data related to the event. There are 3 possible types of events: | ||
- `response` events: These are response from the FTP server to the user's FTP | ||
commands | ||
- `user_command` events: These are commands that the user issuendss to the | ||
FTP server. | ||
- `event:{event name}` events: These are other events mostly related to the server | ||
connection, such as `timeout`, `connect` or `disconnect`. For example, | ||
a timeout event will have the name `event:timeout`. | ||
In order to react to print all debug events (for example), we would listen to the | ||
debug messages like this: | ||
```javascript | ||
Ftp.on('jsftp_debug', function(eventType, data) { | ||
console.log('DEBUG: ', eventType); | ||
console.log(JSON.stringify(data, null, 2)); | ||
}); | ||
``` | ||
Installation | ||
@@ -222,4 +270,4 @@ ------------ | ||
Current overall coverage rate: | ||
lines......: 92.1% (316 of 343 lines) | ||
functions..: 91.0% (71 of 78 functions) | ||
lines......: 95.5% (278 of 291 lines) | ||
functions..: 100% (69 of 69 functions) | ||
@@ -226,0 +274,0 @@ |
@@ -16,3 +16,2 @@ /* | ||
var Path = require("path"); | ||
var Utils = require("../lib/utils"); | ||
var sinon = require("sinon"); | ||
@@ -223,2 +222,26 @@ var EventEmitter = require("events").EventEmitter; | ||
it("test invalid username", function(next) { | ||
this.timeout(10000); | ||
ftp.auth( | ||
FTPCredentials.user + '_invalid', | ||
FTPCredentials.pass, | ||
function(err, data) { | ||
assert.equal(err.code, 530); | ||
assert.equal(data, null); | ||
next(); | ||
}); | ||
}); | ||
it("test invalid password", function(next) { | ||
this.timeout(10000); | ||
ftp.auth( | ||
FTPCredentials.user, | ||
FTPCredentials.pass + '_invalid', | ||
function(err, data) { | ||
assert.equal(err.code, 530); | ||
assert.equal(data, null); | ||
next(); | ||
}); | ||
}); | ||
it("test getFeatures", function(next) { | ||
@@ -233,2 +256,6 @@ ftp.getFeatures(function(err, feats) { | ||
assert.equal(false, ftp.hasFeat("madeup-feat")); | ||
assert.equal(false, ftp.hasFeat()); | ||
assert.equal(false, ftp.hasFeat(null)); | ||
assert.equal(false, ftp.hasFeat('')); | ||
assert.equal(false, ftp.hasFeat(0)); | ||
next(); | ||
@@ -368,2 +395,22 @@ }); | ||
it("test streaming put", function(next) { | ||
var readStream = Fs.createReadStream(__filename); | ||
var remoteFileName = "file_ftp_test.txt"; | ||
var filePath = getRemotePath(remoteFileName); | ||
ftp.put(readStream, filePath, function(hadError) { | ||
assert.ok(!hadError); | ||
ftp.ls(filePath, function(err, res) { | ||
assert.ok(!err); | ||
assert.equal(res[0].size, Fs.statSync(CWD + "/jsftp_test.js").size); | ||
ftp.raw.dele(filePath, function(err, data) { | ||
assert.ok(!err); | ||
next(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it("test rename a file", function(next) { | ||
@@ -671,2 +718,51 @@ var from = getRemotePath("file_ftp_test.txt"); | ||
}); | ||
it("Test raw method with PWD", function(next) { | ||
ftp.raw('pwd', function(err, res) { | ||
assert(!err, err); | ||
var code = parseInt(res.code, 10); | ||
assert.ok(code === 257, "Raw PWD command was not successful: " + res.text); | ||
next(); | ||
}); | ||
}); | ||
it("Test raw method with HELP", function(next) { | ||
ftp.raw('help', function(err, res) { | ||
assert(!err, err); | ||
var code = parseInt(res.code, 10); | ||
assert.ok(code === 214, "Raw HELP command was not successful: " + res.text); | ||
next(); | ||
}); | ||
}); | ||
it("Test keep-alive with NOOP", function(next) { | ||
this.timeout(10000); | ||
ftp.keepAlive(); | ||
ftp.keepAlive(1000); | ||
setTimeout(function() { | ||
ftp.destroy(); | ||
next(); | ||
}, 5000); | ||
}); | ||
it("Test debug mode", function(next) { | ||
var debugCredentials = JSON.parse(JSON.stringify(FTPCredentials)); | ||
debugCredentials.debugMode = true; | ||
var ftp2 = new Ftp(debugCredentials); | ||
ftp2.once('jsftp_debug', function(type, data) { | ||
next(); | ||
}); | ||
}); | ||
it("Test debug mode `setDebugMode`", function(next) { | ||
ftp.setDebugMode(true); | ||
ftp.once('jsftp_debug', function(type, data) { | ||
next(); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
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
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
Network access
Supply chain riskThis module accesses the network.
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
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
0
284
4
2
86060
27
1291