Comparing version 0.2.9 to 0.3.0
@@ -20,6 +20,6 @@ /* | ||
var FTPCredentials = { | ||
host: "CN20081656.p-client.net", | ||
user: "danielatest", | ||
host: "localhost", | ||
user: "sergi", | ||
port: 21, | ||
pass: "test1324" | ||
pass: "2x8hebsndr9" | ||
}; | ||
@@ -104,47 +104,47 @@ | ||
var ftp = this.ftp; | ||
ftp.raw.pwd(function(err, res) { | ||
if (err) throw err; | ||
ftp.raw.pwd(function(err, res) { | ||
if (err) throw err; | ||
var code = parseInt(res.code, 10); | ||
assert.ok(code === 257, "PWD command was not successful"); | ||
var code = parseInt(res.code, 10); | ||
assert.ok(code === 257, "PWD command was not successful"); | ||
ftp.raw.quit(function(err, res) { | ||
if (err) throw err; | ||
ftp.raw.quit(function(err, res) { | ||
if (err) throw err; | ||
next(); | ||
next(); | ||
}); | ||
}); | ||
}); | ||
}, | ||
"test current working directory": function(next) { | ||
var ftp = this.ftp; | ||
ftp.raw.cwd(remoteCWD, function(err, res) { | ||
if (err) throw err; | ||
var code = parseInt(res.code, 10); | ||
assert.ok(code === 200 || code === 250, "CWD command was not successful"); | ||
ftp.raw.pwd(function(err, res) { | ||
ftp.raw.cwd(remoteCWD, function(err, res) { | ||
if (err) throw err; | ||
var code = parseInt(res.code, 10); | ||
assert.ok(code === 257, "PWD command was not successful"); | ||
assert.ok(res.text.indexOf(remoteCWD), "Unexpected CWD"); | ||
}); | ||
assert.ok(code === 200 || code === 250, "CWD command was not successful"); | ||
ftp.raw.cwd("/unexistentDir/", function(err, res) { | ||
if (err) | ||
assert.ok(err); | ||
else { | ||
code = parseInt(res.code, 10); | ||
assert.ok(code === 550, "A (wrong) CWD command was successful. It should have failed"); | ||
} | ||
next(); | ||
ftp.raw.pwd(function(err, res) { | ||
if (err) throw err; | ||
var code = parseInt(res.code, 10); | ||
assert.ok(code === 257, "PWD command was not successful"); | ||
assert.ok(res.text.indexOf(remoteCWD), "Unexpected CWD"); | ||
}); | ||
ftp.raw.cwd("/unexistentDir/", function(err, res) { | ||
if (err) | ||
assert.ok(err); | ||
else { | ||
code = parseInt(res.code, 10); | ||
assert.ok(code === 550, "A (wrong) CWD command was successful. It should have failed"); | ||
} | ||
next(); | ||
}); | ||
}); | ||
}); | ||
}, | ||
"test passive listing of current directory": function(next) { | ||
this.ftp.list(remoteCWD, function(err, res){ | ||
assert.ok(!err, err); | ||
next(); | ||
}); | ||
assert.ok(!err, err); | ||
next(); | ||
}); | ||
}, | ||
@@ -154,13 +154,13 @@ | ||
var ftp = this.ftp; | ||
ftp.raw.pwd(function(err, res) { | ||
var parent = /.*"(.*)".*/.exec(res.text)[1]; | ||
var path = Path.resolve(parent + "/" + remoteCWD); | ||
ftp.raw.stat(path, function(err, res) { | ||
assert.ok(!err, res); | ||
assert.ok(res); | ||
ftp.raw.pwd(function(err, res) { | ||
var parent = /.*"(.*)".*/.exec(res.text)[1]; | ||
var path = Path.resolve(parent + "/" + remoteCWD); | ||
ftp.raw.stat(path, function(err, res) { | ||
assert.ok(!err, res); | ||
assert.ok(res); | ||
assert.ok(res.code === 211 || res.code === 212 || res.code === 213); | ||
next(); | ||
assert.ok(res.code === 211 || res.code === 212 || res.code === 213); | ||
next(); | ||
}); | ||
}); | ||
}); | ||
}, | ||
@@ -173,11 +173,11 @@ | ||
var ftp = this.ftp; | ||
ftp.raw.mkd(newDir, function(err, res) { | ||
assert.ok(!err); | ||
ftp.raw.mkd(newDir, function(err, res) { | ||
assert.ok(!err); | ||
assert.equal(res.code, 257); | ||
ftp.raw.rmd(newDir, function(err, res) { | ||
assert.ok(!err); | ||
next(); | ||
ftp.raw.rmd(newDir, function(err, res) { | ||
assert.ok(!err); | ||
next(); | ||
}); | ||
}); | ||
}); | ||
}, | ||
@@ -190,11 +190,11 @@ | ||
var ftp = this.ftp; | ||
ftp.raw.mkd(newDir, function(err, res) { | ||
assert.ok(!err); | ||
ftp.raw.mkd(newDir, function(err, res) { | ||
assert.ok(!err); | ||
assert.equal(res.code, 257); | ||
ftp.raw.rmd(newDir, function(err, res) { | ||
assert.ok(!err); | ||
next(); | ||
ftp.raw.rmd(newDir, function(err, res) { | ||
assert.ok(!err); | ||
next(); | ||
}); | ||
}); | ||
}); | ||
}, | ||
@@ -207,19 +207,19 @@ | ||
var ftp = this.ftp; | ||
Fs.readFile(CWD + "/jsftp_test.js", "binary", function(err, data) { | ||
var buffer = new Buffer(data, "binary"); | ||
ftp.put(filePath, buffer, function(err, res) { | ||
assert.ok(!err, err); | ||
Fs.readFile(CWD + "/jsftp_test.js", "binary", function(err, data) { | ||
var buffer = new Buffer(data, "binary"); | ||
ftp.put(filePath, buffer, function(err, res) { | ||
assert.ok(!err, err); | ||
ftp.ls(filePath, function(err, res) { | ||
assert.ok(!err); | ||
assert.equal(buffer.length, Fs.statSync(CWD + "/jsftp_test.js").size); | ||
ftp.raw.dele(filePath, function(err, data) { | ||
ftp.ls(filePath, function(err, res) { | ||
assert.ok(!err); | ||
assert.equal(buffer.length, Fs.statSync(CWD + "/jsftp_test.js").size); | ||
next(); | ||
ftp.raw.dele(filePath, function(err, data) { | ||
assert.ok(!err); | ||
next(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}, | ||
@@ -233,16 +233,17 @@ | ||
var ftp = this.ftp; | ||
Fs.readFile(CWD + "/jsftp_test.js", "binary", function(err, data) { | ||
var buffer = new Buffer(data, "binary"); | ||
ftp.put(from, buffer, function(err, res) { | ||
assert.ok(!err, err); | ||
Fs.readFile(CWD + "/jsftp_test.js", "binary", function(err, data) { | ||
var buffer = new Buffer(data, "binary"); | ||
ftp.put(from, buffer, function(err, res) { | ||
assert.ok(!err, err); | ||
ftp.rename(from, to, function(err, res) { | ||
ftp.ls(to, function(err, res) { | ||
assert.ok(!err); | ||
ftp.rename(from, to, function(err, res) { | ||
ftp.ls(to, function(err, res) { | ||
assert.ok(!err); | ||
assert.equal(buffer.length, Fs.statSync(CWD + "/jsftp_test.js").size); | ||
assert.equal(buffer.length, Fs.statSync(CWD + "/jsftp_test.js").size); | ||
ftp.raw.dele(to, function(err, data) { | ||
assert.ok(!err); | ||
next(); | ||
ftp.raw.dele(to, function(err, data) { | ||
assert.ok(!err); | ||
next(); | ||
}); | ||
}); | ||
@@ -252,3 +253,2 @@ }); | ||
}); | ||
}); | ||
}, | ||
@@ -262,17 +262,17 @@ | ||
Fs.readFile(localPath, "binary", function(err, data) { | ||
var buffer = new Buffer(data, "binary"); | ||
ftp.put(remotePath, buffer, function(err, res) { | ||
assert.ok(!err, err); | ||
ftp.get(remotePath, function(err, data) { | ||
Fs.readFile(localPath, "binary", function(err, data) { | ||
var buffer = new Buffer(data, "binary"); | ||
ftp.put(remotePath, buffer, function(err, res) { | ||
assert.ok(!err, err); | ||
ftp.get(remotePath, function(err, data) { | ||
assert.ok(!err, err); | ||
assert.equal(buffer.length, data.length); | ||
ftp.raw.dele(remotePath, function(err, data) { | ||
assert.ok(!err); | ||
next(); | ||
assert.equal(buffer.length, data.length); | ||
ftp.raw.dele(remotePath, function(err, data) { | ||
assert.ok(!err); | ||
next(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}, | ||
@@ -285,55 +285,55 @@ | ||
ftp.put(filePath, new Buffer("test"), handler); | ||
ftp.get(filePath, function(err, data) { | ||
assert.ok(!err, err); | ||
assert.ok(data); | ||
}); | ||
ftp.put(filePath, new Buffer("test"), handler); | ||
ftp.get(filePath, function(err, data) { | ||
assert.ok(!err, err); | ||
assert.ok(data); | ||
}); | ||
ftp.put(filePath, new Buffer("test"), handler); | ||
ftp.get(filePath, function(err, data) { | ||
assert.ok(!err, err); | ||
assert.ok(data); | ||
ftp.put(filePath, new Buffer("test"), handler); | ||
ftp.get(filePath, function(err, data) { | ||
assert.ok(!err, err); | ||
assert.ok(data); | ||
assert.equal(counter, 2); | ||
next(); | ||
}); | ||
}); | ||
function handler() { counter++; }; | ||
function handler() { counter++; }; | ||
}, | ||
">test get fileList array": function(next) { | ||
"test get fileList array": function(next) { | ||
var ftp = this.ftp; | ||
var file1 = "testfile.txt"; | ||
ftp.raw.pwd(function(err, res) { | ||
var parent, pathDir, path; | ||
if (remoteCWD.charAt(0) !== "/") { | ||
parent = /.*"(.*)".*/.exec(res.text)[1]; | ||
pathDir = Path.resolve(parent + "/" + remoteCWD); | ||
path = Path.resolve(pathDir + "/" + file1); | ||
} | ||
else { | ||
pathDir = remoteCWD; | ||
path = Path.resolve(remoteCWD + "/" + file1); | ||
} | ||
ftp.raw.pwd(function(err, res) { | ||
var parent, pathDir, path; | ||
if (remoteCWD.charAt(0) !== "/") { | ||
parent = /.*"(.*)".*/.exec(res.text)[1]; | ||
pathDir = Path.resolve(parent + "/" + remoteCWD); | ||
path = Path.resolve(pathDir + "/" + file1); | ||
} | ||
else { | ||
pathDir = remoteCWD; | ||
path = Path.resolve(remoteCWD + "/" + file1); | ||
} | ||
ftp.put(path, new Buffer("test"), function(err, res) { | ||
assert.ok(!err); | ||
ftp.put(path, new Buffer("test"), function(err, res) { | ||
assert.ok(!err); | ||
ftp.raw.cwd(pathDir, function(err, res) { | ||
ftp.ls(".", function(err, res) { | ||
assert.ok(!err); | ||
ftp.raw.cwd(pathDir, function(err, res) { | ||
ftp.ls(".", function(err, res) { | ||
assert.ok(!err); | ||
assert.ok(Array.isArray(res)); | ||
assert.ok(Array.isArray(res)); | ||
var fileNames = res.map(function(file) { | ||
return file ? file.name : null; | ||
}); | ||
var fileNames = res.map(function(file) { | ||
return file ? file.name : null; | ||
}); | ||
assert.ok(fileNames.indexOf(file1) > -1); | ||
assert.ok(fileNames.indexOf(file1) > -1); | ||
next(); | ||
next(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}, | ||
@@ -346,29 +346,29 @@ "test multiple concurrent pasvs": function(next) { | ||
ftp.raw.pwd(function(err, res) { | ||
var parent, pathDir, path1, path2, path3; | ||
if (remoteCWD.charAt(0) !== "/") { | ||
parent = /.*"(.*)".*/.exec(res.text)[1]; | ||
pathDir = Path.resolve(parent + "/" + remoteCWD); | ||
path1 = Path.resolve(pathDir + "/" + file1); | ||
path2 = Path.resolve(pathDir + "/" + file2); | ||
path3 = Path.resolve(pathDir + "/" + file3); | ||
} | ||
else { | ||
pathDir = remoteCWD; | ||
path1 = Path.resolve(remoteCWD + "/" + file1); | ||
path2 = Path.resolve(remoteCWD + "/" + file2); | ||
path3 = Path.resolve(remoteCWD + "/" + file3); | ||
} | ||
ftp.raw.pwd(function(err, res) { | ||
var parent, pathDir, path1, path2, path3; | ||
if (remoteCWD.charAt(0) !== "/") { | ||
parent = /.*"(.*)".*/.exec(res.text)[1]; | ||
pathDir = Path.resolve(parent + "/" + remoteCWD); | ||
path1 = Path.resolve(pathDir + "/" + file1); | ||
path2 = Path.resolve(pathDir + "/" + file2); | ||
path3 = Path.resolve(pathDir + "/" + file3); | ||
} | ||
else { | ||
pathDir = remoteCWD; | ||
path1 = Path.resolve(remoteCWD + "/" + file1); | ||
path2 = Path.resolve(remoteCWD + "/" + file2); | ||
path3 = Path.resolve(remoteCWD + "/" + file3); | ||
} | ||
ftp.put(path1, new Buffer("test"), handler); | ||
ftp.put(path2, new Buffer("test"), handler); | ||
ftp.put(path3, new Buffer("test"), handler); | ||
ftp.put(path1, new Buffer("test"), handler); | ||
ftp.put(path2, new Buffer("test"), handler); | ||
ftp.put(path3, new Buffer("test"), handler); | ||
var count = 0; | ||
function handler(err, res) { | ||
var count = 0; | ||
function handler(err, res) { | ||
assert.ok(!err); | ||
if (++count == 3) | ||
next(); | ||
} | ||
}); | ||
if (++count == 3) | ||
next(); | ||
} | ||
}); | ||
}, | ||
@@ -375,0 +375,0 @@ "test stat and pasv calls in parallel without auth": function(next) { |
433
jsftp.js
@@ -5,10 +5,11 @@ /* | ||
* @author Sergi Mansilla <sergi.mansilla@gmail.com> | ||
* @license https://github.com/sergi/jsftp/blob/master/LICENSE MIT License | ||
* @license https://github.com/sergi/jsFTP/blob/master/LICENSE MIT License | ||
*/ | ||
var Net = require("net"); | ||
var ftpPasv = require("./lib/ftpPasv"); | ||
var Parser = require('./lib/ftpParser'); | ||
var S = require("streamer"); | ||
var Util = require("util"); | ||
var EventEmitter = require("events").EventEmitter; | ||
var Parser = require("./lib/ftpParser"); | ||
var Gab = require("gab"); | ||
@@ -19,4 +20,5 @@ var slice = Array.prototype.slice; | ||
var RE_PASV = /[-\d]+,[-\d]+,[-\d]+,[-\d]+,([-\d]+),([-\d]+)/; | ||
var RE_RES = /^(\d{3})\s(.*)/; | ||
var RE_MULTI = /^(\d{3})-/; | ||
var RE_RES = /^(\d\d\d)\s(.*)/; | ||
var RE_MULTI = /^(\d\d\d)-/; | ||
var RE_NL_END = /\r\n$/; | ||
var RE_NL = /\r\n/; | ||
@@ -37,25 +39,30 @@ | ||
// From https://github.com/coolaj86/node-bufferjs | ||
var concat = function(bufs) { | ||
var buffer; | ||
var length = 0; | ||
var index = 0; | ||
// Queue function that maintains an ordered queue of streams. | ||
var queue = function queue() { | ||
var next; | ||
var buffer = slice.call(arguments); | ||
if (!Array.isArray(bufs)) | ||
bufs = Array.prototype.slice.call(arguments); | ||
var stream = function stream($, stop) { | ||
next = $; | ||
stream._update(); | ||
}; | ||
for (var i=0, l=bufs.length; i<l; i++) { | ||
buffer = bufs[i]; | ||
length += buffer.length; | ||
} | ||
stream._update = function _update() { | ||
buffer.push.apply(buffer, arguments); | ||
if (next && buffer.length) { | ||
if (false !== next(buffer.shift())) | ||
_update(); | ||
else | ||
next = null; | ||
} | ||
}; | ||
return stream; | ||
}; | ||
buffer = new Buffer(length); | ||
bufs.forEach(function (buf, i) { | ||
buf.copy(buffer, index, 0, buf.length); | ||
index += buf.length; | ||
}); | ||
// Enqueues an `element` in the `stream` object, which has to be a reference to | ||
// a queue. | ||
var enqueue = function enqueue(stream, element) { | ||
stream._update.apply(null, slice.call(arguments, 1)); | ||
}; | ||
return buffer; | ||
} | ||
// Codes from 100 to 200 are FTP marks | ||
@@ -67,3 +74,4 @@ var isMark = function isMark(code) { | ||
var Ftp = module.exports = function(cfg) { | ||
Gab.apply(this, arguments); | ||
"use strict"; | ||
var Emitter = function() { EventEmitter.call(this); }; | ||
@@ -74,6 +82,3 @@ Util.inherits(Emitter, EventEmitter); | ||
this.raw = {}; | ||
this.data = ""; | ||
this.buffer = []; | ||
this.options = cfg; | ||
this.cmdQueue = [["NOOP", function() {}]]; | ||
// This variable will be true if the server doesn't support the `stat` | ||
@@ -84,6 +89,8 @@ // command. Since listing a directory or retrieving file properties is | ||
this.useList = false; | ||
this.currentCode = 0; | ||
this.host = cfg.host; | ||
this.port = cfg.port || FTP_PORT; | ||
if (cfg) { | ||
this.onError = cfg.onError; | ||
this.onTimeout = cfg.onTimeout; | ||
this.onConnect = cfg.onConnect; | ||
} | ||
@@ -93,29 +100,20 @@ // Generate generic methods from parameter names. They can easily be | ||
// it is the responsability of the user to validate the parameters. | ||
COMMANDS.forEach(this.generateCommand, this); | ||
var self = this; | ||
COMMANDS.forEach(function(cmd) { | ||
var lcCmd = cmd.toLowerCase(); | ||
self.raw[lcCmd] = function() { | ||
var callback; | ||
var action = lcCmd; | ||
if (DEBUG_MODE) { | ||
this.emitter.on("command", this._log); | ||
this.emitter.on("response", this._log); | ||
} | ||
if (arguments.length) { | ||
var args = slice.call(arguments); | ||
this.connect(this.port, this.host); | ||
}; | ||
if (typeof args[args.length - 1] == "function") | ||
callback = args.pop(); | ||
Ftp.prototype = new Gab; | ||
Ftp.prototype.constructor = Ftp; | ||
(function() { | ||
"use strict"; | ||
this.generateCommand = function(cmd) { | ||
var self = this; | ||
cmd = cmd.toLowerCase(); | ||
this.raw[cmd] = function() { | ||
var callback; | ||
var args = slice.call(arguments); | ||
if (typeof args[args.length - 1] === "function") { | ||
callback = args.pop(); | ||
if (args.length) | ||
action += " " + args.join(" "); | ||
} | ||
if (cmd === "quit" && self._keepAliveInterval) | ||
if (lcCmd === "quit" && self._keepAliveInterval) | ||
clearInterval(self._keepAliveInterval); | ||
@@ -133,94 +131,221 @@ else | ||
// slight slopiness in the code flow. | ||
var action = cmd + " " + args.join(" "); | ||
var isAuthCmd = /feat|user|pass/.test(action); | ||
if (!self.authenticated && !isAuthCmd) { | ||
self.auth(self.options.user, self.options.pass, function() { | ||
self.push([action.trim(), callback]); | ||
enqueue(self.cmdQueue, [action, callback]); | ||
}); | ||
} | ||
else { | ||
self.push([action.trim(), callback]); | ||
enqueue(self.cmdQueue, [action, callback]); | ||
} | ||
}; | ||
}, | ||
}); | ||
this.collectIncomingData = function(data) { | ||
// if (isMark(data.code)) | ||
// return; | ||
if (DEBUG_MODE) { | ||
this.emitter.on("command", this._log); | ||
this.emitter.on("response", this._log); | ||
} | ||
this.data += data; | ||
}, | ||
this.host = cfg.host; | ||
this.port = cfg.port || FTP_PORT; | ||
var socket = this._createSocket(this.port, this.host); | ||
var cmd; | ||
// Writes a new command to the server, but before that it pushes the | ||
// command into `cmds` list. This command will get paired with its response | ||
// once that one is received | ||
this.push = function(data, onWriteCallback) { | ||
var command = data[0]; | ||
Gab.prototype.push.call(this, command + "\r\n"); | ||
this.push = function(command, callback, onWriteCallback) { | ||
if (!command || typeof command != "string") | ||
return; | ||
this.emitter.emit("command", this._sanitize(command)); | ||
this.cmdQueue.push(data); | ||
var self = this; | ||
function send() { | ||
socket.write(command + "\r\n"); | ||
self.emitter.emit("command", self._sanitize(command)); | ||
if (onWriteCallback) { | ||
onWriteCallback(); | ||
if (onWriteCallback) | ||
onWriteCallback(); | ||
} | ||
}; | ||
this.foundTerminator = function() { | ||
var NL = "\n"; | ||
var line = this.data.replace("\r", ""); | ||
this.data = ""; | ||
if (socket.writable) { | ||
send(); | ||
} | ||
else { | ||
if (DEBUG_MODE) | ||
console.log("FTP socket is not writable, reopening socket..."); | ||
var simpleRes = RE_RES.exec(line); | ||
var multiRes = RE_MULTI.exec(line); | ||
if (simpleRes) { | ||
var code = parseInt(simpleRes[1], 10); | ||
if (this.buffer.length) { | ||
this.buffer.push(line); | ||
if (!this.connecting) { | ||
this.connecting = true; | ||
// Multiline responses from FTP signal last line by | ||
// starting the last line with the code that they started with. | ||
if (this.currentCode === code) { | ||
line = this.buffer.join(NL); | ||
this.buffer = []; | ||
this.currentCode = 0; | ||
var reConnect = function() { | ||
self.connecting = false; | ||
send(); | ||
}; | ||
try { | ||
socket = this._createSocket(this.port, this.host, reConnect); | ||
createStreams(); | ||
} | ||
else { | ||
return; | ||
catch (e) { | ||
console.log(e); | ||
} | ||
} | ||
} | ||
}; | ||
var ftpResponse = { | ||
code: code, | ||
text: line | ||
}; | ||
// Stream of incoming data from the FTP server. | ||
var input = function(next, stop) { | ||
socket.on("connect", self.onConnect || function(){}); | ||
socket.on("data", next); | ||
socket.on("end", stop); | ||
socket.on("error", stop); | ||
socket.on("close", stop); | ||
}; | ||
if (this.cmdQueue.length) { | ||
var cmd = this.cmdQueue.shift(); | ||
var cbk = cmd[1]; | ||
var cmds, tasks; | ||
var createStreams = this.createStreams = function() { | ||
self.cmdQueue = queue(); | ||
(self.nextCmd = function nextCmd() { | ||
S.head(self.cmdQueue)(function(obj) { | ||
cmd(obj, self.nextCmd); | ||
self.push(obj[0], obj[1], obj[2] || null); | ||
}); | ||
})(); | ||
if (!cbk) | ||
return; | ||
// Stream of FTP commands from the client. | ||
cmds = function(next, stop) { | ||
cmd = next; | ||
}; | ||
this.emitter.emit("response", ftpResponse.text); | ||
// In FTP every response code above 399 means error in some way. | ||
// Since the RFC is not respected by many servers, we are going to | ||
// overgeneralize and consider every value above 399 as an error. | ||
if (ftpResponse && ftpResponse.code > 399) { | ||
var err = new Error(ftpResponse.text || "Unknown FTP error."); | ||
err.code = ftpResponse.code; | ||
cbk(err); | ||
} | ||
else { | ||
cbk(null, ftpResponse); | ||
} | ||
/** | ||
* Zips (as in array zipping) commands with responses. This creates | ||
* a stream that keeps yielding command/response pairs as soon as each pair | ||
* becomes available. | ||
*/ | ||
tasks = S.zip(S.filter(function(x) { | ||
// We ignore FTP marks for now. They don't convey useful | ||
// information. A more elegant solution should be found in the | ||
// future. | ||
return !isMark(x.code); | ||
}, self.serverResponse(input)), S.append(S.list(null), cmds)); | ||
tasks(self.parse.bind(self), function(err) { | ||
if (DEBUG_MODE) { | ||
console.log("Ftp socket closed its doors to the public."); | ||
} | ||
} | ||
else { | ||
if (!this.buffer.length && multiRes) | ||
this.currentCode = parseInt(multiRes[1], 10); | ||
}); | ||
}; | ||
this.buffer.push(line); | ||
createStreams(); | ||
this.cmd = cmd; | ||
}; | ||
(function() { | ||
this.addCmdListener = function(listener, action) { | ||
this.emitter.on(action || "command", listener); | ||
}; | ||
this._createSocket = function(port, host, firstTask) { | ||
this.connecting = true; | ||
var socket = this.socket = Net.createConnection(port, host); | ||
socket.setTimeout(TIMEOUT, function() { | ||
if (this.onTimeout) | ||
this.onTimeout(new Error("FTP socket timeout")); | ||
this.destroy(); | ||
}.bind(this)); | ||
var self = this; | ||
socket.on("connect", function() { | ||
if (DEBUG_MODE) console.log("FTP socket connected"); | ||
firstTask && firstTask(); | ||
self.connecting = false; | ||
}); | ||
return this.socket; | ||
}; | ||
/** | ||
* `serverResponse` receives a stream of responses from the server and filters | ||
* them before pushing them back into the stream. The filtering is | ||
* necessary to detect multiline responses, in which several responses from | ||
* the server belong to a single command. | ||
*/ | ||
this.serverResponse = function(source) { | ||
var self = this; | ||
var NL = "\n"; | ||
var buffer = []; | ||
var currentCode = 0; | ||
return function stream(next, stop) { | ||
source(function(data) { | ||
var lines = data.toString().replace(RE_NL_END, "").replace(RE_NL, NL).split(NL); | ||
lines.forEach(function(line) { | ||
var simpleRes = RE_RES.exec(line); | ||
var multiRes; | ||
if (simpleRes) { | ||
var code = parseInt(simpleRes[1], 10); | ||
if (buffer.length) { | ||
buffer.push(line); | ||
if (currentCode === code) { | ||
line = buffer.join(NL); | ||
buffer = []; | ||
currentCode = 0; | ||
} | ||
} | ||
self.emitter.emit("response", line); | ||
next({ code: code, text: line }); | ||
} | ||
else { | ||
if (!buffer.length && (multiRes = RE_MULTI.exec(line))) | ||
currentCode = parseInt(multiRes[1], 10); | ||
buffer.push(line); | ||
} | ||
}, this); | ||
}, stop); | ||
}; | ||
}; | ||
/** | ||
* Parse is called each time that a comand and a request are paired | ||
* together. That is, each time that there is a round trip of actions | ||
* between the client and the server. The `exp` 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. | ||
* | ||
* @param action {Array} Contains server response and client command info. | ||
*/ | ||
this.parse = function(action) { | ||
if (!action || !action[1]) | ||
return; | ||
var ftpResponse = action[0]; | ||
var command = action[1]; | ||
var callback = command[1]; | ||
var err; | ||
if (callback) { | ||
// In FTP every response code above 399 means error in some way. | ||
// Since the RFC is not respected by many servers, we are goiong to | ||
// overgeneralize and consider every value above 399 as an error. | ||
if (ftpResponse && ftpResponse.code > 399) { | ||
err = new Error(ftpResponse.text || "Unknown FTP error.") | ||
err.code = ftpResponse.code; | ||
callback(err); | ||
} else { | ||
callback(null, ftpResponse); | ||
} | ||
} | ||
}, | ||
this.nextCmd(); | ||
}; | ||
@@ -239,8 +364,4 @@ this._initialize = function(callback) { | ||
this.addCmdListener = function(listener) { | ||
this.emitter.on("command", listener); | ||
}; | ||
this._log = function(msg) { | ||
console.log("\n" + msg); | ||
console.log("\n" + msg.text); | ||
}; | ||
@@ -256,2 +377,5 @@ | ||
this._sanitize = function(cmd) { | ||
if (!cmd) | ||
return; | ||
var _cmd = cmd.slice(0, 5); | ||
@@ -280,3 +404,3 @@ if (_cmd === "pass ") | ||
features = features.slice(1, -1).map(function(feature) { | ||
return (/^\s*(\w*)\s*/).exec(feature)[1].trim().toLowerCase(); | ||
return /^\s*(\w*)\s*/.exec(feature)[1].trim().toLowerCase(); | ||
}); | ||
@@ -293,4 +417,7 @@ } | ||
if (this.dataConn) | ||
this.dataConn.socket.destroy(); | ||
this.features = null; | ||
this.tasks = null; | ||
this.tasks = null; | ||
this.authenticated = false; | ||
@@ -366,3 +493,3 @@ }; | ||
* | ||
* mode {String}, optional: "I" or "A", referring to binary or text format, respectively. Default is binary. | ||
* mode {String}, optional: "I" or "A", referring to binary or text format, respectively. Default is binary., | ||
* cmd {String}: String of the command to execute, | ||
@@ -385,33 +512,9 @@ * onCmdWrite {function}, optional: Function to execute just after writing the command to the socket. | ||
var port = (parseInt(match[1], 10) & 255) * 256 + (parseInt(match[2], 10) & 255); | ||
var onConnect = function() { | ||
self.push([data.cmd], function() { | ||
if (data.onCmdWrite) | ||
data.onCmdWrite(psvSocket); | ||
}); | ||
}; | ||
var pieces = []; | ||
var onData = function(result) { | ||
pieces.push(result); | ||
}; | ||
var onEnd = function(error) { | ||
if (error) | ||
callback(error); | ||
else if (data.mode === "I") | ||
callback(null, concat(pieces)); | ||
else | ||
callback(null, pieces.join("\n")); | ||
}; | ||
var psvSocket = Net.createConnection(port, self.host); | ||
if (data.mode !== "I") { | ||
psvSocket.setEncoding("utf8"); | ||
} | ||
psvSocket.once("connect", onConnect); | ||
psvSocket.on("data", onData); | ||
psvSocket.on("end", onEnd); | ||
psvSocket.on("error", onEnd); | ||
self.dataConn = new ftpPasv({ | ||
host: self.host, | ||
port: port, | ||
mode: data.mode, | ||
callback: callback, | ||
ftp: self | ||
}); | ||
}; | ||
@@ -421,4 +524,5 @@ | ||
// operation. | ||
this.raw.type(data.mode, function(err, res) { | ||
self.push(["pasv", doPasv]); | ||
this.raw.type(data.mode || "I", function(err, res) { | ||
enqueue(self.cmdQueue, ["pasv", doPasv]); | ||
enqueue(self.cmdQueue, [data.cmd, null, data.onCmdWrite]); | ||
}); | ||
@@ -449,3 +553,4 @@ }; | ||
this.get = function(filePath, callback) { | ||
this.setPassive({ | ||
var self = this; | ||
self.setPassive({ | ||
mode: "I", | ||
@@ -462,14 +567,8 @@ cmd: "retr " + filePath, | ||
cmd: "stor " + filePath, | ||
onCmdWrite: function(socket) { | ||
if (socket && socket.writable) | ||
onCmdWrite: function() { | ||
var socket = self.dataConn.socket; | ||
if (socket.writable) | ||
socket.end(buffer); | ||
}, | ||
pasvCallback: function(error, contents) { | ||
// noop function in place because 'STOR' returns an extra command | ||
// giving the state of the transfer. | ||
if (!error) | ||
self.cmdQueue.push(["NOOP", function() {}]) | ||
callback(error, contents); | ||
} | ||
pasvCallback: callback | ||
}); | ||
@@ -476,0 +575,0 @@ }; |
{ | ||
"name": "jsftp", | ||
"id": "jsftp", | ||
"version": "0.2.9", | ||
"version": "0.3.0", | ||
"description": "A sane FTP client implementation for NodeJS", | ||
@@ -6,0 +6,0 @@ "keywords": [ "ftp", "protocol", "files", "server", "client", "async" ], |
Sorry, the diff of this file is not supported yet
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
359709
25
1651
4