New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

basic-ftp

Package Overview
Dependencies
Maintainers
1
Versions
112
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

basic-ftp - npm Package Compare versions

Comparing version 2.8.1 to 2.8.2

test/canarySpec.js

6

CHANGELOG.md
# Changelog
## 2.8.2
- When downloading, handle incoming data before announcement from control socket arrives.
- More tests for uploading and downloading data including directory listings.
- Use download mechanism for directory listings as well.
## 2.8.1

@@ -4,0 +10,0 @@

121

lib/ftp.js

@@ -7,2 +7,3 @@ "use strict";

const path = require("path");
const EventEmitter = require("events");
const promisify = require("util").promisify;

@@ -100,3 +101,4 @@ const defaultParseList = require("./parseList");

/**
* Return true if TLS is enabled for the control socket.
* Return true if the control socket is using TLS. This does not mean that a session
* has already been negotiated.
*

@@ -454,3 +456,4 @@ * @returns {boolean}

await this.prepareTransfer(this.ftp);
return download(this.ftp, writableStream, remoteFilename, startAt);
const command = startAt > 0 ? `REST ${startAt}` : `RETR ${remoteFilename}`;
return download(this.ftp, writableStream, command, remoteFilename);
}

@@ -465,3 +468,6 @@

await this.prepareTransfer(this.ftp);
return list(this.ftp, this.parseList);
const writable = new StringWriter(this.ftp.encoding);
await download(this.ftp, writable, "LIST");
this.ftp.log(writable.text);
return this.parseList(writable.text);
}

@@ -582,2 +588,6 @@

/**
* Instantiate a TransferResolver
* @param {FTPContext} ftp
*/
constructor(ftp) {

@@ -775,3 +785,3 @@ this.ftp = ftp;

function parseIPv4PasvResponse(message) {
// From something like "227 Entering Passive Mode (192,168,3,200,10,229)",
// From something like "227 Entering Passive Mode (192,168,1,100,10,229)",
// extract host and port.

@@ -789,32 +799,2 @@ const groups = message.match(/([-\d]+,[-\d]+,[-\d]+,[-\d]+),([-\d]+),([-\d]+)/);

/**
* List files and folders of current directory.`
*
* @param {FTP} ftp
* @param {(rawList: string) => FileInfo[]} parseList
* @return {Promise<FileInfo[]>}
*/
function list(ftp, parseList = defaultParseList) {
const resolver = new TransferResolver(ftp);
let rawList = "";
return ftp.handle("LIST", (res, task) => {
if (res.code === 150 || res.code === 125) { // Ready to download
ftp.log(`Downloading list (${describeTLS(ftp.dataSocket)})`);
ftp.dataSocket.on("data", data => {
rawList += data.toString(ftp.encoding);
});
ftp.dataSocket.once("end", () => {
ftp.log(rawList);
resolver.resolve(task, parseList(rawList));
});
}
else if (positiveCompletion(res.code)) { // Transfer complete
resolver.confirm(task);
}
else if (res.code >= 400 || res.error) {
resolver.reject(task, res);
}
});
}
/**
* Upload stream data as a file. For example:

@@ -834,4 +814,6 @@ *

if (res.code === 150 || res.code === 125) { // Ready to upload
// The actual upload mechanism.
const execute = function() {
// If we are using TLS, we have to wait until the dataSocket issued
// 'secureConnect'. If this hasn't happened yet, getCipher() returns undefined.
const canUpload = ftp.hasTLS === false || ftp.dataSocket.getCipher() !== undefined;
conditionOrEvent(canUpload, ftp.dataSocket, "secureConnect", () => {
ftp.log(`Uploading (${describeTLS(ftp.dataSocket)})`);

@@ -842,13 +824,4 @@ readableStream.pipe(ftp.dataSocket).once("finish", () => {

resolver.confirm(task);
});
};
// If we are using TLS, we have to wait until the dataSocket issued
// 'secureConnect'. If this hasn't happened yet, getCipher() returns undefined.
const waitForSecureConnect = ftp.hasTLS && ftp.dataSocket.getCipher() === undefined;
if (waitForSecureConnect) {
ftp.dataSocket.once("secureConnect", execute);
}
else {
execute();
}
});
});
}

@@ -865,20 +838,23 @@ else if (positiveCompletion(res.code)) { // Transfer complete

/**
* Download a remote file as a stream. For example:
* Download data from the data connection. Used for downloading files and directory listings.
*
* `download(ftp, fs.createWriteStream(localFilePath), remoteFilename)`
*
* @param {FTP} ftp
* @param {stream.Writable} writableStream
* @param {string} remoteFilename
* @param {number} startAt
* @param {string} command
* @param {filename} [remoteFilename]
* @returns {Promise<PositiveResponse>}
*/
function download(ftp, writableStream, remoteFilename, startAt = 0) {
function download(ftp, writableStream, command, remoteFilename = "") {
// It's possible that data transmission begins before the control socket
// receives the announcement. Start listening for data immediately.
ftp.dataSocket.pipe(writableStream);
const resolver = new TransferResolver(ftp);
const command = startAt > 0 ? `REST ${startAt}` : `RETR ${remoteFilename}`;
return ftp.handle(command, (res, task) => {
if (res.code === 150 || res.code === 125) { // Ready to download
ftp.log(`Downloading (${describeTLS(ftp.dataSocket)})`);
ftp.dataSocket.once("end", () => resolver.confirm(task));
ftp.dataSocket.pipe(writableStream);
// Confirm the transfer as soon as the data socket transmission ended.
// It's possible, though, that the data transmission is complete before
// the control socket receives the accouncement that it will begin.
// Check if the data socket is not already closed.
conditionOrEvent(ftp.dataSocket.destroyed, ftp.dataSocket, "end", () => resolver.confirm(task));
}

@@ -898,2 +874,35 @@ else if (res.code === 350) { // Restarting at startAt.

/**
* Calls a function immediately if a condition is met or subscribes to an event and calls
* it once the event is emitted.
*
* @param {boolean} condition The condition to test.
* @param {*} emitter The emitter to use if the condition is not met.
* @param {string} eventName The event to subscribe to if the condition is not met.
* @param {() => any} action The function to call.
*/
function conditionOrEvent(condition, emitter, eventName, action) {
if (condition === true) {
action();
}
else {
emitter.once(eventName, () => action());
}
}
class StringWriter extends EventEmitter {
constructor(encoding) {
super();
this.encoding = encoding;
this.text = "";
this.write = this.end = this.append;
}
append(chunk) {
if (chunk) {
this.text += chunk.toString(this.encoding);
}
}
}
/**
* Upload the contents of a local directory to the working directory. This will overwrite

@@ -900,0 +909,0 @@ * existing files and reuse existing directories.

{
"name": "basic-ftp",
"version": "2.8.1",
"version": "2.8.2",
"description": "FTP client for Node.js with support for explicit FTPS over TLS.",

@@ -13,3 +13,3 @@ "main": "./lib/ftp",

"type": "git",
"url" : "https://github.com/patrickjuchli/basic-ftp.git"
"url": "https://github.com/patrickjuchli/basic-ftp.git"
},

@@ -16,0 +16,0 @@ "author": "Patrick Juchli <patrickjuchli@gmail.com>",

const assert = require("assert");
const FTPContext = require("../lib/ftp").FTPContext;
const EventEmitter = require("events");
const SocketMock = require("./SocketMock");
const tls = require("tls");
const net = require("net");
class SocketMock extends EventEmitter {
constructor() {
super();
this.destroyed = false;
}
removeAllListeners() {
}
setKeepAlive() {
}
setTimeout() {
}
destroy() {
this.destroyed = true;
}
write() {
}
}
describe("FTPContext", function() {

@@ -32,9 +16,15 @@

it("Setting data socket undefined destroys current", function() {
it("Setting new control socket doesn't destroy current", function() {
const old = ftp.socket;
ftp.socket = undefined;
assert.equal(old.destroyed, false, "Socket not destroyed.");
});
it("Setting new data socket destroys current", function() {
const old = ftp.dataSocket;
ftp.dataSocket = undefined;
assert.equal(old.destroyed, true, "Socket not destroyed.");
assert.equal(old.destroyed, true, "Socket destroyed.");
});
it("Relays control timeout event", function(done) {
it("Relays control socket timeout event", function(done) {
ftp.handle(undefined, (res, task) => {

@@ -47,3 +37,3 @@ assert.deepEqual(res, { error: "Timeout" });

it("Relays control error event", function(done) {
it("Relays control socket error event", function(done) {
ftp.handle(undefined, (res, task) => {

@@ -56,2 +46,18 @@ assert.deepEqual(res, { error: { foo: "bar" } });

it("Relays data socket timeout event", function(done) {
ftp.handle(undefined, (res, task) => {
assert.deepEqual(res, { error: "Timeout" });
done();
});
ftp.dataSocket.emit("timeout");
});
it("Relays data socket error event", function(done) {
ftp.handle(undefined, (res, task) => {
assert.deepEqual(res, { error: { foo: "bar" } });
done();
});
ftp.dataSocket.emit("error", { foo: "bar" });
});
it("Relays single line control response", function(done) {

@@ -106,2 +112,31 @@ ftp.handle(undefined, (res, task) => {

});
it("can send a command", function(done) {
ftp.socket.once("didSend", buf => {
assert.equal(buf.toString(), "HELLO TEST\r\n");
done();
});
ftp.send("HELLO TEST");
});
it("is using UTF-8 by default", function(done) {
ftp.socket.once("didSend", buf => {
assert.equal(buf.toString(), "HELLO 直己\r\n");
done();
});
ftp.send("HELLO 直己");
});
it("destroys sockets when closing", function() {
ftp.close();
assert(ftp.socket.destroyed, "Control socket");
assert(ftp.dataSocket.destroyed, "Data socket");
});
it("reports whether socket has TLS", function() {
ftp.socket = new net.Socket();
assert(!ftp.hasTLS);
ftp.socket = new tls.TLSSocket();
assert(ftp.hasTLS);
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc