Huge News!Announcing our $40M Series B led by Abstract Ventures.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 4.0.2 to 4.1.0

4

CHANGELOG.md
# Changelog
## 4.1.0
- Added: Support symbolic links in MLSD listings.
## 4.0.2

@@ -4,0 +8,0 @@

2

dist/Client.js

@@ -179,3 +179,3 @@ "use strict";

await this.sendIgnoringError("OPTS UTF8 ON"); // Some servers expect UTF-8 to be enabled explicitly
await this.sendIgnoringError("OPTS MLST type;size;modify;unix.mode;unix.owner;unix.group;unix.ownername;unix.groupname;"); // Make sure MLSD listings include all we can parse
await this.sendIgnoringError("OPTS MLST type;size;modify;unique;unix.mode;unix.owner;unix.group;unix.ownername;unix.groupname;"); // Make sure MLSD listings include all we can parse
if (this.ftp.hasTLS) {

@@ -182,0 +182,0 @@ await this.sendIgnoringError("PBSZ 0"); // Set to 0 for TLS

@@ -13,4 +13,3 @@ export declare enum FileType {

/**
* Describes a file, directory or symbolic link. Note that FTP file listings are not standardized. It depends
* on the operating system of the FTP server how complete the information is.
* Describes a file, directory or symbolic link.
*/

@@ -27,20 +26,47 @@ export declare class FileInfo {

/**
* Unix permissions if present. If the underlying FTP server is not running on Unix or doesn't report
* permissions this will be undefined. If set, you might be able to edit permissions with the FTP command `SITE CHMOD`.
* Unparsed, raw modification date as a string.
*
* If `modifiedAt` is undefined, the FTP server you're connected to doesn't support the more modern
* MLSD command for machine-readable directory listings. The older command LIST is then used returning
* results that vary a lot between servers as the format hasn't been standardized. Here, directory listings
* and especially modification dates were meant to be human-readable first.
*
* Be careful when still trying to parse this by yourself. Parsing dates from listings using LIST is
* unreliable. This library decides to offer parsed dates only when they're absolutely reliable and safe to
* use e.g. for comparisons.
*/
permissions: UnixPermissions | undefined;
hardLinkCount: number;
link: string;
group: string;
user: string;
rawModifiedAt: string;
/**
* Unparsed date as a string. Be careful when trying to parse this by yourself. There is no
* standard format on which FTP servers agree when using the LIST command. Date information is meant
* to be human-readable but not necessarily easy to parse. See `modifiedAt` for a parsed date.
* Parsed modification date.
*
* Available if the FTP server supports the MLSD command. Only MLSD guarantees dates than can be reliably
* parsed with the correct timezone and a resolution down to seconds. See `rawModifiedAt` property for the unparsed
* date that is always available.
*/
date: string;
modifiedAt?: Date;
/**
* Parsed modification date is available (and reliable) if the MLSD command is supported by the FTP server.
* Unix permissions if present. If the underlying FTP server is not running on Unix this will be undefined.
* If set, you might be able to edit permissions with the FTP command `SITE CHMOD`.
*/
modifiedAt: Date | undefined;
permissions?: UnixPermissions;
/**
* Hard link count if available.
*/
hardLinkCount?: number;
/**
* Link name for symbolic links if available.
*/
link?: string;
/**
* Unix group if available.
*/
group?: string;
/**
* Unix user if available.
*/
user?: string;
/**
* Unique ID if available.
*/
uniqueID?: string;
constructor(name: string);

@@ -50,2 +76,7 @@ readonly isDirectory: boolean;

readonly isFile: boolean;
/**
* Deprecated, legacy API. Use `rawModifiedAt` instead.
* @deprecated
*/
date: string;
}

@@ -11,4 +11,3 @@ "use strict";

/**
* Describes a file, directory or symbolic link. Note that FTP file listings are not standardized. It depends
* on the operating system of the FTP server how complete the information is.
* Describes a file, directory or symbolic link.
*/

@@ -20,12 +19,48 @@ class FileInfo {

this.size = 0;
this.hardLinkCount = 0;
this.link = "";
this.group = "";
this.user = "";
/**
* Unparsed date as a string. Be careful when trying to parse this by yourself. There is no
* standard format on which FTP servers agree when using the LIST command. Date information is meant
* to be human-readable but not necessarily easy to parse. See `modifiedAt` for a parsed date.
* Unparsed, raw modification date as a string.
*
* If `modifiedAt` is undefined, the FTP server you're connected to doesn't support the more modern
* MLSD command for machine-readable directory listings. The older command LIST is then used returning
* results that vary a lot between servers as the format hasn't been standardized. Here, directory listings
* and especially modification dates were meant to be human-readable first.
*
* Be careful when still trying to parse this by yourself. Parsing dates from listings using LIST is
* unreliable. This library decides to offer parsed dates only when they're absolutely reliable and safe to
* use e.g. for comparisons.
*/
this.date = "";
this.rawModifiedAt = "";
/**
* Parsed modification date.
*
* Available if the FTP server supports the MLSD command. Only MLSD guarantees dates than can be reliably
* parsed with the correct timezone and a resolution down to seconds. See `rawModifiedAt` property for the unparsed
* date that is always available.
*/
this.modifiedAt = undefined;
/**
* Unix permissions if present. If the underlying FTP server is not running on Unix this will be undefined.
* If set, you might be able to edit permissions with the FTP command `SITE CHMOD`.
*/
this.permissions = undefined;
/**
* Hard link count if available.
*/
this.hardLinkCount = undefined;
/**
* Link name for symbolic links if available.
*/
this.link = undefined;
/**
* Unix group if available.
*/
this.group = undefined;
/**
* Unix user if available.
*/
this.user = undefined;
/**
* Unique ID if available.
*/
this.uniqueID = undefined;
this.name = name;

@@ -42,2 +77,12 @@ }

}
/**
* Deprecated, legacy API. Use `rawModifiedAt` instead.
* @deprecated
*/
get date() {
return this.rawModifiedAt;
}
set date(rawModifiedAt) {
this.rawModifiedAt = rawModifiedAt;
}
}

@@ -44,0 +89,0 @@ exports.FileInfo = FileInfo;

@@ -22,2 +22,9 @@ "use strict";

];
function firstCompatibleParser(line, parsers) {
return parsers.find(parser => parser.testLine(line) === true);
}
function stringIsNotBlank(str) {
return str.trim() !== "";
}
const REGEX_NEWLINE = /\r?\n/;
/**

@@ -27,23 +34,18 @@ * Parse raw directory listing.

function parseList(rawList) {
const lines = rawList.split(/\r?\n/) // Split by newline
.map(line => (/^(\d\d\d)-/.test(line)) ? line.substr(3) : line) // Strip possible multiline prefix
.filter(line => line.trim() !== ""); // Remove blank lines
const lines = rawList
.split(REGEX_NEWLINE)
.filter(stringIsNotBlank);
if (lines.length === 0) {
return [];
}
// Pick the last line of the list as a test candidate to find a compatible parser.
const test = lines[lines.length - 1];
const parser = firstCompatibleParser(test, availableParsers);
const testLine = lines[lines.length - 1];
const parser = firstCompatibleParser(testLine, availableParsers);
if (!parser) {
throw new Error("This library only supports MLSD, Unix- or DOS-style directory listing. Your FTP server seems to be using another format. You can see the transmitted listing when setting `client.ftp.verbose = true`. You can then provide a custom parser to `client.parseList`, see the documentation for details.");
}
return lines.map(parser.parseLine)
const files = lines
.map(parser.parseLine)
.filter((info) => info !== undefined);
return parser.transformList(files);
}
exports.parseList = parseList;
/**
* Returns the first parser that doesn't return undefined for the given line.
*/
function firstCompatibleParser(line, parsers) {
return parsers.find(parser => parser.testLine(line) === true);
}
import { FileInfo } from "./FileInfo";
export declare function testLine(line: string): boolean;
export declare function parseLine(line: string): FileInfo | undefined;
export declare function transformList(files: FileInfo[]): FileInfo[];

@@ -8,3 +8,3 @@ "use strict";

*
* http://svn.apache.org/viewvc/commons/proper/net/tags/NET_3_6/src/main/java/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java?revision=1783048&view=markup
* https://github.com/apache/commons-net/blob/master/src/main/java/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java
*/

@@ -37,3 +37,3 @@ const RE_LINE = new RegExp("(\\S+)\\s+(\\S+)\\s+" // MM-dd-yy whitespace hh:mma|kk:mm swallow trailing spaces

}
file.date = groups[1] + " " + groups[2];
file.rawModifiedAt = groups[1] + " " + groups[2];
return file;

@@ -44,1 +44,5 @@ }

exports.parseLine = parseLine;
function transformList(files) {
return files;
}
exports.transformList = transformList;

@@ -12,2 +12,3 @@ import { FileInfo } from "./FileInfo";

export declare function parseLine(line: string): FileInfo | undefined;
export declare function transformList(files: FileInfo[]): FileInfo[];
/**

@@ -14,0 +15,0 @@ * Parse date as specified in https://tools.ietf.org/html/rfc3659#section-2.3.

@@ -11,3 +11,3 @@ "use strict";

// - " filename only"
return /\S+=\S+;/.test(line) || line.startsWith(" ");
return /^\S+=\S+;/.test(line) || line.startsWith(" ");
}

@@ -20,9 +20,27 @@ exports.testLine = testLine;

"size": parseSize,
"sized": parseSize,
"sizd": parseSize,
"unique": (value, info) => {
info.uniqueID = value;
},
"modify": (value, info) => {
info.modifiedAt = parseMLSxDate(value);
info.date = info.modifiedAt.toISOString();
info.rawModifiedAt = info.modifiedAt.toISOString();
},
"type": (value, info) => {
// There seems to be confusion on how to handle symbolic links for Unix. RFC 3659 doesn't describe
// this but mentions some examples using the syntax `type=OS.unix=slink:<target>`. But according to
// an entry in the Errata (https://www.rfc-editor.org/errata/eid1500) this syntax can't be valid.
// Instead it proposes to use `type=OS.unix=symlink` and to then list the actual target of the
// symbolic link as another entry in the directory listing. The unique identifiers can then be used
// to derive the connection between link(s) and target. We'll have to handle both cases as there
// are differing opinions on how to deal with this. Here are some links on this topic:
// - ProFTPD source: https://github.com/proftpd/proftpd/blob/56e6dfa598cbd4ef5c6cba439bcbcd53a63e3b21/modules/mod_facts.c#L531
// - ProFTPD bug: http://bugs.proftpd.org/show_bug.cgi?id=3318
// - ProFTPD statement: http://www.proftpd.org/docs/modules/mod_facts.html
// – FileZilla bug: https://trac.filezilla-project.org/ticket/9310
if (value.startsWith("OS.unix=slink")) {
info.type = FileInfo_1.FileType.SymbolicLink;
info.link = value.substr(value.indexOf(":") + 1);
return false;
}
switch (value) {

@@ -35,2 +53,7 @@ case "file":

break;
case "OS.unix=symlink":
info.type = FileInfo_1.FileType.SymbolicLink;
// The target of the symbolic link might be defined in another line in the directory listing.
// We'll handle this in `transformList()` below.
break;
case "cdir": // Current directory being listed

@@ -56,8 +79,7 @@ case "pdir": // Parent directory

"unix.owner": (value, info) => {
if (info.user === "")
if (info.user === undefined)
info.user = value;
},
"unix.uid": (value, info) => {
if (info.user === "")
info.user = value;
get "unix.uid"() {
return this["unix.owner"];
},

@@ -68,8 +90,7 @@ "unix.groupname": (value, info) => {

"unix.group": (value, info) => {
if (info.group === "")
if (info.group === undefined)
info.group = value;
},
"unix.gid": (value, info) => {
if (info.group === "")
info.group = value;
get "unix.gid"() {
return this["unix.group"];
}

@@ -104,3 +125,5 @@ // Regarding the fact "perm":

for (const fact of facts) {
const [factName, factValue] = fact.split("=", 2);
const firstEqualSignPos = fact.indexOf("="); // Consider `type=OS.unix=slink:<target>`
const factName = fact.substr(0, firstEqualSignPos);
const factValue = fact.substr(firstEqualSignPos + 1);
if (!factValue) {

@@ -113,3 +136,3 @@ continue;

}
const shouldIgnoreEntry = factHandler(factValue.toLowerCase(), info);
const shouldIgnoreEntry = factHandler(factValue, info);
if (shouldIgnoreEntry === true) {

@@ -122,2 +145,36 @@ return undefined;

exports.parseLine = parseLine;
function transformList(files) {
// Resolve symbolic links encoded as `type=OS.unix=symlink`. The corresponding target might be
// somewhere in the list. We can identify it using the unique identifier.
const unresolvedSymLinks = [];
for (const file of files) {
if (file.type === FileInfo_1.FileType.SymbolicLink && file.link === undefined && file.uniqueID !== undefined) {
unresolvedSymLinks.push(file);
}
}
if (unresolvedSymLinks.length === 0) {
return files;
}
const resolvedFiles = [];
for (const file of files) {
// It's possible that multiple symbolic links point to the same target.
// We can't resolve anything without unique identifiers.
if (file.type !== FileInfo_1.FileType.SymbolicLink && file.uniqueID !== undefined) {
for (const symLink of unresolvedSymLinks) {
if (symLink.uniqueID === file.uniqueID) {
symLink.link = file.name;
}
}
}
// The targets of a symbolic link is listed as a file in the directory listing but might
// have a path pointing outside of this directory. In that case we don't want this entry
// to be part of the listing. We don't want these kind of entries in general.
const isDirectoryFile = !file.name.includes("/");
if (isDirectoryFile) {
resolvedFiles.push(file);
}
}
return resolvedFiles;
}
exports.transformList = transformList;
/**

@@ -124,0 +181,0 @@ * Parse date as specified in https://tools.ietf.org/html/rfc3659#section-2.3.

import { FileInfo } from "./FileInfo";
export declare function testLine(line: string): boolean;
export declare function parseLine(line: string): FileInfo | undefined;
export declare function transformList(files: FileInfo[]): FileInfo[];
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const FileInfo_1 = require("./FileInfo");
const JA_MONTH = "\u6708";
const JA_DAY = "\u65e5";
const JA_YEAR = "\u5e74";
/**

@@ -8,3 +11,3 @@ * This parser is based on the FTP client library source code in Apache Commons Net provided

*
* http://svn.apache.org/viewvc/commons/proper/net/tags/NET_3_6/src/main/java/org/apache/commons/net/ftp/parser/
* https://github.com/apache/commons-net/blob/master/src/main/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java
*

@@ -44,10 +47,12 @@ * Below is the regular expression used by this parser.

+ "\\s+" // separator
/*
* numeric or standard format date:
* yyyy-mm-dd (expecting hh:mm to follow)
* MMM [d]d
* [d]d MMM
* N.B. use non-space for MMM to allow for languages such as German which use
* diacritics (e.g. umlaut) in some abbreviations.
*/
/**
* numeric or standard format date:
* yyyy-mm-dd (expecting hh:mm to follow)
* MMM [d]d
* [d]d MMM
* N.B. use non-space for MMM to allow for languages such as German which use
* diacritics (e.g. umlaut) in some abbreviations.
* Japanese uses numeric day and month with suffixes to distinguish them
* [d]dXX [d]dZZ
*/
+ "(" +

@@ -57,9 +62,11 @@ "(?:\\d+[-/]\\d+[-/]\\d+)" + // yyyy-mm-dd

"|(?:\\d{1,2}\\s+\\S{3})" + // [d]d MMM
"|(?:\\d{1,2}" + JA_MONTH + "\\s+\\d{1,2}" + JA_DAY + ")" +
")"
+ "\\s+" // separator
/*
year (for non-recent standard format) - yyyy
or time (for numeric or recent standard format) [h]h:mm
*/
+ "((?:\\d+(?::\\d+)?))" // (20)
/**
* year (for non-recent standard format) - yyyy
* or time (for numeric or recent standard format) [h]h:mm
* or Japanese year - yyyyXX
*/
+ "((?:\\d+(?::\\d+)?)|(?:\\d{4}" + JA_YEAR + "))" // (20)
+ "\\s" // separator

@@ -85,3 +92,3 @@ + "(.*)"); // the rest (21)

file.hardLinkCount = parseInt(groups[15], 10);
file.date = groups[19] + " " + groups[20];
file.rawModifiedAt = groups[19] + " " + groups[20];
file.permissions = {

@@ -129,2 +136,6 @@ user: parseMode(groups[4], groups[5], groups[6]),

exports.parseLine = parseLine;
function transformList(files) {
return files;
}
exports.transformList = transformList;
function parseMode(r, w, x) {

@@ -131,0 +142,0 @@ let value = 0;

{
"name": "basic-ftp",
"version": "4.0.2",
"version": "4.1.0",
"description": "FTP client for Node.js, supports explicit FTPS over TLS, IPv6, Async/Await, and Typescript.",

@@ -39,8 +39,8 @@ "main": "dist/index",

"devDependencies": {
"@types/node": "12.7.8",
"@typescript-eslint/eslint-plugin": "2.3.1",
"@typescript-eslint/parser": "2.3.1",
"eslint": "6.4.0",
"@types/node": "12.7.12",
"@typescript-eslint/eslint-plugin": "2.3.3",
"@typescript-eslint/parser": "2.3.3",
"eslint": "6.5.1",
"js-yaml": ">=3.13.1",
"mocha": "6.2.0",
"mocha": "6.2.1",
"rimraf": "3.0.0",

@@ -47,0 +47,0 @@ "typescript": "3.6.3"

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