first commit
This commit is contained in:
332
node_modules/basic-ftp/dist/Client.d.ts
generated
vendored
Normal file
332
node_modules/basic-ftp/dist/Client.d.ts
generated
vendored
Normal file
@@ -0,0 +1,332 @@
|
||||
/// <reference types="node" />
|
||||
/// <reference types="node" />
|
||||
import { Readable, Writable } from "stream";
|
||||
import { ConnectionOptions as TLSConnectionOptions } from "tls";
|
||||
import { FileInfo } from "./FileInfo";
|
||||
import { FTPContext, FTPResponse } from "./FtpContext";
|
||||
import { ProgressHandler, ProgressTracker } from "./ProgressTracker";
|
||||
import { UploadCommand } from "./transfer";
|
||||
export interface AccessOptions {
|
||||
/** Host the client should connect to. Optional, default is "localhost". */
|
||||
readonly host?: string;
|
||||
/** Port the client should connect to. Optional, default is 21. */
|
||||
readonly port?: number;
|
||||
/** Username to use for login. Optional, default is "anonymous". */
|
||||
readonly user?: string;
|
||||
/** Password to use for login. Optional, default is "guest". */
|
||||
readonly password?: string;
|
||||
/** Use FTPS over TLS. Optional, default is false. True is preferred explicit TLS, "implicit" supports legacy, non-standardized implicit TLS. */
|
||||
readonly secure?: boolean | "implicit";
|
||||
/** TLS options as in [tls.connect(options)](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback), optional. */
|
||||
readonly secureOptions?: TLSConnectionOptions;
|
||||
}
|
||||
/** Prepares a data connection for transfer. */
|
||||
export type TransferStrategy = (ftp: FTPContext) => Promise<FTPResponse>;
|
||||
/** Parses raw directoy listing data. */
|
||||
export type RawListParser = (rawList: string) => FileInfo[];
|
||||
export interface UploadOptions {
|
||||
/** Offset in the local file to start uploading from. */
|
||||
localStart?: number;
|
||||
/** Final byte position to include in upload from the local file. */
|
||||
localEndInclusive?: number;
|
||||
}
|
||||
/**
|
||||
* High-level API to interact with an FTP server.
|
||||
*/
|
||||
export declare class Client {
|
||||
prepareTransfer: TransferStrategy;
|
||||
parseList: RawListParser;
|
||||
availableListCommands: string[];
|
||||
/** Low-level API to interact with FTP server. */
|
||||
readonly ftp: FTPContext;
|
||||
/** Tracks progress of data transfers. */
|
||||
protected _progressTracker: ProgressTracker;
|
||||
/**
|
||||
* Instantiate an FTP client.
|
||||
*
|
||||
* @param timeout Timeout in milliseconds, use 0 for no timeout. Optional, default is 30 seconds.
|
||||
*/
|
||||
constructor(timeout?: number);
|
||||
/**
|
||||
* Close the client and all open socket connections.
|
||||
*
|
||||
* Close the client and all open socket connections. The client can’t be used anymore after calling this method,
|
||||
* you have to either reconnect with `access` or `connect` or instantiate a new instance to continue any work.
|
||||
* A client is also closed automatically if any timeout or connection error occurs.
|
||||
*/
|
||||
close(): void;
|
||||
/**
|
||||
* Returns true if the client is closed and can't be used anymore.
|
||||
*/
|
||||
get closed(): boolean;
|
||||
/**
|
||||
* Connect (or reconnect) to an FTP server.
|
||||
*
|
||||
* This is an instance method and thus can be called multiple times during the lifecycle of a `Client`
|
||||
* instance. Whenever you do, the client is reset with a new control connection. This also implies that
|
||||
* you can reopen a `Client` instance that has been closed due to an error when reconnecting with this
|
||||
* method. In fact, reconnecting is the only way to continue using a closed `Client`.
|
||||
*
|
||||
* @param host Host the client should connect to. Optional, default is "localhost".
|
||||
* @param port Port the client should connect to. Optional, default is 21.
|
||||
*/
|
||||
connect(host?: string, port?: number): Promise<FTPResponse>;
|
||||
/**
|
||||
* As `connect` but using implicit TLS. Implicit TLS is not an FTP standard and has been replaced by
|
||||
* explicit TLS. There are still FTP servers that support only implicit TLS, though.
|
||||
*/
|
||||
connectImplicitTLS(host?: string, port?: number, tlsOptions?: TLSConnectionOptions): Promise<FTPResponse>;
|
||||
/**
|
||||
* Handles the first reponse by an FTP server after the socket connection has been established.
|
||||
*/
|
||||
private _handleConnectResponse;
|
||||
/**
|
||||
* Send an FTP command and handle the first response.
|
||||
*/
|
||||
send(command: string, ignoreErrorCodesDEPRECATED?: boolean): Promise<FTPResponse>;
|
||||
/**
|
||||
* Send an FTP command and ignore an FTP error response. Any other kind of error or timeout will still reject the Promise.
|
||||
*
|
||||
* @param command
|
||||
*/
|
||||
sendIgnoringError(command: string): Promise<FTPResponse>;
|
||||
/**
|
||||
* Upgrade the current socket connection to TLS.
|
||||
*
|
||||
* @param options TLS options as in `tls.connect(options)`, optional.
|
||||
* @param command Set the authentication command. Optional, default is "AUTH TLS".
|
||||
*/
|
||||
useTLS(options?: TLSConnectionOptions, command?: string): Promise<FTPResponse>;
|
||||
/**
|
||||
* Login a user with a password.
|
||||
*
|
||||
* @param user Username to use for login. Optional, default is "anonymous".
|
||||
* @param password Password to use for login. Optional, default is "guest".
|
||||
*/
|
||||
login(user?: string, password?: string): Promise<FTPResponse>;
|
||||
/**
|
||||
* Set the usual default settings.
|
||||
*
|
||||
* Settings used:
|
||||
* * Binary mode (TYPE I)
|
||||
* * File structure (STRU F)
|
||||
* * Additional settings for FTPS (PBSZ 0, PROT P)
|
||||
*/
|
||||
useDefaultSettings(): Promise<void>;
|
||||
/**
|
||||
* Convenience method that calls `connect`, `useTLS`, `login` and `useDefaultSettings`.
|
||||
*
|
||||
* This is an instance method and thus can be called multiple times during the lifecycle of a `Client`
|
||||
* instance. Whenever you do, the client is reset with a new control connection. This also implies that
|
||||
* you can reopen a `Client` instance that has been closed due to an error when reconnecting with this
|
||||
* method. In fact, reconnecting is the only way to continue using a closed `Client`.
|
||||
*/
|
||||
access(options?: AccessOptions): Promise<FTPResponse>;
|
||||
/**
|
||||
* Get the current working directory.
|
||||
*/
|
||||
pwd(): Promise<string>;
|
||||
/**
|
||||
* Get a description of supported features.
|
||||
*
|
||||
* This sends the FEAT command and parses the result into a Map where keys correspond to available commands
|
||||
* and values hold further information. Be aware that your FTP servers might not support this
|
||||
* command in which case this method will not throw an exception but just return an empty Map.
|
||||
*/
|
||||
features(): Promise<Map<string, string>>;
|
||||
/**
|
||||
* Set the working directory.
|
||||
*/
|
||||
cd(path: string): Promise<FTPResponse>;
|
||||
/**
|
||||
* Switch to the parent directory of the working directory.
|
||||
*/
|
||||
cdup(): Promise<FTPResponse>;
|
||||
/**
|
||||
* Get the last modified time of a file. This is not supported by every FTP server, in which case
|
||||
* calling this method will throw an exception.
|
||||
*/
|
||||
lastMod(path: string): Promise<Date>;
|
||||
/**
|
||||
* Get the size of a file.
|
||||
*/
|
||||
size(path: string): Promise<number>;
|
||||
/**
|
||||
* Rename a file.
|
||||
*
|
||||
* Depending on the FTP server this might also be used to move a file from one
|
||||
* directory to another by providing full paths.
|
||||
*/
|
||||
rename(srcPath: string, destPath: string): Promise<FTPResponse>;
|
||||
/**
|
||||
* Remove a file from the current working directory.
|
||||
*
|
||||
* You can ignore FTP error return codes which won't throw an exception if e.g.
|
||||
* the file doesn't exist.
|
||||
*/
|
||||
remove(path: string, ignoreErrorCodes?: boolean): Promise<FTPResponse>;
|
||||
/**
|
||||
* Report transfer progress for any upload or download to a given handler.
|
||||
*
|
||||
* This will also reset the overall transfer counter that can be used for multiple transfers. You can
|
||||
* also call the function without a handler to stop reporting to an earlier one.
|
||||
*
|
||||
* @param handler Handler function to call on transfer progress.
|
||||
*/
|
||||
trackProgress(handler?: ProgressHandler): void;
|
||||
/**
|
||||
* Upload data from a readable stream or a local file to a remote file.
|
||||
*
|
||||
* @param source Readable stream or path to a local file.
|
||||
* @param toRemotePath Path to a remote file to write to.
|
||||
*/
|
||||
uploadFrom(source: Readable | string, toRemotePath: string, options?: UploadOptions): Promise<FTPResponse>;
|
||||
/**
|
||||
* Upload data from a readable stream or a local file by appending it to an existing file. If the file doesn't
|
||||
* exist the FTP server should create it.
|
||||
*
|
||||
* @param source Readable stream or path to a local file.
|
||||
* @param toRemotePath Path to a remote file to write to.
|
||||
*/
|
||||
appendFrom(source: Readable | string, toRemotePath: string, options?: UploadOptions): Promise<FTPResponse>;
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
protected _uploadWithCommand(source: Readable | string, remotePath: string, command: UploadCommand, options: UploadOptions): Promise<FTPResponse>;
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
protected _uploadLocalFile(localPath: string, remotePath: string, command: UploadCommand, options: UploadOptions): Promise<FTPResponse>;
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
protected _uploadFromStream(source: Readable, remotePath: string, command: UploadCommand): Promise<FTPResponse>;
|
||||
/**
|
||||
* Download a remote file and pipe its data to a writable stream or to a local file.
|
||||
*
|
||||
* You can optionally define at which position of the remote file you'd like to start
|
||||
* downloading. If the destination you provide is a file, the offset will be applied
|
||||
* to it as well. For example: To resume a failed download, you'd request the size of
|
||||
* the local, partially downloaded file and use that as the offset. Assuming the size
|
||||
* is 23, you'd download the rest using `downloadTo("local.txt", "remote.txt", 23)`.
|
||||
*
|
||||
* @param destination Stream or path for a local file to write to.
|
||||
* @param fromRemotePath Path of the remote file to read from.
|
||||
* @param startAt Position within the remote file to start downloading at. If the destination is a file, this offset is also applied to it.
|
||||
*/
|
||||
downloadTo(destination: Writable | string, fromRemotePath: string, startAt?: number): Promise<FTPResponse>;
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
protected _downloadToFile(localPath: string, remotePath: string, startAt: number): Promise<FTPResponse>;
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
protected _downloadToStream(destination: Writable, remotePath: string, startAt: number): Promise<FTPResponse>;
|
||||
/**
|
||||
* List files and directories in the current working directory, or from `path` if specified.
|
||||
*
|
||||
* @param [path] Path to remote file or directory.
|
||||
*/
|
||||
list(path?: string): Promise<FileInfo[]>;
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
protected _requestListWithCommand(command: string): Promise<FileInfo[]>;
|
||||
/**
|
||||
* Remove a directory and all of its content.
|
||||
*
|
||||
* @param remoteDirPath The path of the remote directory to delete.
|
||||
* @example client.removeDir("foo") // Remove directory 'foo' using a relative path.
|
||||
* @example client.removeDir("foo/bar") // Remove directory 'bar' using a relative path.
|
||||
* @example client.removeDir("/foo/bar") // Remove directory 'bar' using an absolute path.
|
||||
* @example client.removeDir("/") // Remove everything.
|
||||
*/
|
||||
removeDir(remoteDirPath: string): Promise<void>;
|
||||
/**
|
||||
* Remove all files and directories in the working directory without removing
|
||||
* the working directory itself.
|
||||
*/
|
||||
clearWorkingDir(): Promise<void>;
|
||||
/**
|
||||
* Upload the contents of a local directory to the remote working directory.
|
||||
*
|
||||
* This will overwrite existing files with the same names and reuse existing directories.
|
||||
* Unrelated files and directories will remain untouched. You can optionally provide a `remoteDirPath`
|
||||
* to put the contents inside a directory which will be created if necessary including all
|
||||
* intermediate directories. If you did provide a remoteDirPath the working directory will stay
|
||||
* the same as before calling this method.
|
||||
*
|
||||
* @param localDirPath Local path, e.g. "foo/bar" or "../test"
|
||||
* @param [remoteDirPath] Remote path of a directory to upload to. Working directory if undefined.
|
||||
*/
|
||||
uploadFromDir(localDirPath: string, remoteDirPath?: string): Promise<void>;
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
protected _uploadToWorkingDir(localDirPath: string): Promise<void>;
|
||||
/**
|
||||
* Download all files and directories of the working directory to a local directory.
|
||||
*
|
||||
* @param localDirPath The local directory to download to.
|
||||
* @param remoteDirPath Remote directory to download. Current working directory if not specified.
|
||||
*/
|
||||
downloadToDir(localDirPath: string, remoteDirPath?: string): Promise<void>;
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
protected _downloadFromWorkingDir(localDirPath: string): Promise<void>;
|
||||
/**
|
||||
* Make sure a given remote path exists, creating all directories as necessary.
|
||||
* This function also changes the current working directory to the given path.
|
||||
*/
|
||||
ensureDir(remoteDirPath: string): Promise<void>;
|
||||
/**
|
||||
* Try to create a directory and enter it. This will not raise an exception if the directory
|
||||
* couldn't be created if for example it already exists.
|
||||
* @protected
|
||||
*/
|
||||
protected _openDir(dirName: string): Promise<void>;
|
||||
/**
|
||||
* Remove an empty directory, will fail if not empty.
|
||||
*/
|
||||
removeEmptyDir(path: string): Promise<FTPResponse>;
|
||||
/**
|
||||
* FTP servers can't handle filenames that have leading whitespace. This method transforms
|
||||
* a given path to fix that issue for most cases.
|
||||
*/
|
||||
protectWhitespace(path: string): Promise<string>;
|
||||
protected _exitAtCurrentDirectory<T>(func: () => Promise<T>): Promise<T>;
|
||||
/**
|
||||
* Try all available transfer strategies and pick the first one that works. Update `client` to
|
||||
* use the working strategy for all successive transfer requests.
|
||||
*
|
||||
* @returns a function that will try the provided strategies.
|
||||
*/
|
||||
protected _enterFirstCompatibleMode(strategies: TransferStrategy[]): TransferStrategy;
|
||||
/**
|
||||
* DEPRECATED, use `uploadFrom`.
|
||||
* @deprecated
|
||||
*/
|
||||
upload(source: Readable | string, toRemotePath: string, options?: UploadOptions): Promise<FTPResponse>;
|
||||
/**
|
||||
* DEPRECATED, use `appendFrom`.
|
||||
* @deprecated
|
||||
*/
|
||||
append(source: Readable | string, toRemotePath: string, options?: UploadOptions): Promise<FTPResponse>;
|
||||
/**
|
||||
* DEPRECATED, use `downloadTo`.
|
||||
* @deprecated
|
||||
*/
|
||||
download(destination: Writable | string, fromRemotePath: string, startAt?: number): Promise<FTPResponse>;
|
||||
/**
|
||||
* DEPRECATED, use `uploadFromDir`.
|
||||
* @deprecated
|
||||
*/
|
||||
uploadDir(localDirPath: string, remoteDirPath?: string): Promise<void>;
|
||||
/**
|
||||
* DEPRECATED, use `downloadToDir`.
|
||||
* @deprecated
|
||||
*/
|
||||
downloadDir(localDirPath: string): Promise<void>;
|
||||
}
|
||||
770
node_modules/basic-ftp/dist/Client.js
generated
vendored
Normal file
770
node_modules/basic-ftp/dist/Client.js
generated
vendored
Normal file
@@ -0,0 +1,770 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Client = void 0;
|
||||
const fs_1 = require("fs");
|
||||
const path_1 = require("path");
|
||||
const tls_1 = require("tls");
|
||||
const util_1 = require("util");
|
||||
const FtpContext_1 = require("./FtpContext");
|
||||
const parseList_1 = require("./parseList");
|
||||
const ProgressTracker_1 = require("./ProgressTracker");
|
||||
const StringWriter_1 = require("./StringWriter");
|
||||
const parseListMLSD_1 = require("./parseListMLSD");
|
||||
const netUtils_1 = require("./netUtils");
|
||||
const transfer_1 = require("./transfer");
|
||||
const parseControlResponse_1 = require("./parseControlResponse");
|
||||
// Use promisify to keep the library compatible with Node 8.
|
||||
const fsReadDir = (0, util_1.promisify)(fs_1.readdir);
|
||||
const fsMkDir = (0, util_1.promisify)(fs_1.mkdir);
|
||||
const fsStat = (0, util_1.promisify)(fs_1.stat);
|
||||
const fsOpen = (0, util_1.promisify)(fs_1.open);
|
||||
const fsClose = (0, util_1.promisify)(fs_1.close);
|
||||
const fsUnlink = (0, util_1.promisify)(fs_1.unlink);
|
||||
const LIST_COMMANDS_DEFAULT = () => ["LIST -a", "LIST"];
|
||||
const LIST_COMMANDS_MLSD = () => ["MLSD", "LIST -a", "LIST"];
|
||||
/**
|
||||
* High-level API to interact with an FTP server.
|
||||
*/
|
||||
class Client {
|
||||
/**
|
||||
* Instantiate an FTP client.
|
||||
*
|
||||
* @param timeout Timeout in milliseconds, use 0 for no timeout. Optional, default is 30 seconds.
|
||||
*/
|
||||
constructor(timeout = 30000) {
|
||||
this.availableListCommands = LIST_COMMANDS_DEFAULT();
|
||||
this.ftp = new FtpContext_1.FTPContext(timeout);
|
||||
this.prepareTransfer = this._enterFirstCompatibleMode([transfer_1.enterPassiveModeIPv6, transfer_1.enterPassiveModeIPv4]);
|
||||
this.parseList = parseList_1.parseList;
|
||||
this._progressTracker = new ProgressTracker_1.ProgressTracker();
|
||||
}
|
||||
/**
|
||||
* Close the client and all open socket connections.
|
||||
*
|
||||
* Close the client and all open socket connections. The client can’t be used anymore after calling this method,
|
||||
* you have to either reconnect with `access` or `connect` or instantiate a new instance to continue any work.
|
||||
* A client is also closed automatically if any timeout or connection error occurs.
|
||||
*/
|
||||
close() {
|
||||
this.ftp.close();
|
||||
this._progressTracker.stop();
|
||||
}
|
||||
/**
|
||||
* Returns true if the client is closed and can't be used anymore.
|
||||
*/
|
||||
get closed() {
|
||||
return this.ftp.closed;
|
||||
}
|
||||
/**
|
||||
* Connect (or reconnect) to an FTP server.
|
||||
*
|
||||
* This is an instance method and thus can be called multiple times during the lifecycle of a `Client`
|
||||
* instance. Whenever you do, the client is reset with a new control connection. This also implies that
|
||||
* you can reopen a `Client` instance that has been closed due to an error when reconnecting with this
|
||||
* method. In fact, reconnecting is the only way to continue using a closed `Client`.
|
||||
*
|
||||
* @param host Host the client should connect to. Optional, default is "localhost".
|
||||
* @param port Port the client should connect to. Optional, default is 21.
|
||||
*/
|
||||
connect(host = "localhost", port = 21) {
|
||||
this.ftp.reset();
|
||||
this.ftp.socket.connect({
|
||||
host,
|
||||
port,
|
||||
family: this.ftp.ipFamily
|
||||
}, () => this.ftp.log(`Connected to ${(0, netUtils_1.describeAddress)(this.ftp.socket)} (${(0, netUtils_1.describeTLS)(this.ftp.socket)})`));
|
||||
return this._handleConnectResponse();
|
||||
}
|
||||
/**
|
||||
* As `connect` but using implicit TLS. Implicit TLS is not an FTP standard and has been replaced by
|
||||
* explicit TLS. There are still FTP servers that support only implicit TLS, though.
|
||||
*/
|
||||
connectImplicitTLS(host = "localhost", port = 21, tlsOptions = {}) {
|
||||
this.ftp.reset();
|
||||
this.ftp.socket = (0, tls_1.connect)(port, host, tlsOptions, () => this.ftp.log(`Connected to ${(0, netUtils_1.describeAddress)(this.ftp.socket)} (${(0, netUtils_1.describeTLS)(this.ftp.socket)})`));
|
||||
this.ftp.tlsOptions = tlsOptions;
|
||||
return this._handleConnectResponse();
|
||||
}
|
||||
/**
|
||||
* Handles the first reponse by an FTP server after the socket connection has been established.
|
||||
*/
|
||||
_handleConnectResponse() {
|
||||
return this.ftp.handle(undefined, (res, task) => {
|
||||
if (res instanceof Error) {
|
||||
// The connection has been destroyed by the FTPContext at this point.
|
||||
task.reject(res);
|
||||
}
|
||||
else if ((0, parseControlResponse_1.positiveCompletion)(res.code)) {
|
||||
task.resolve(res);
|
||||
}
|
||||
// Reject all other codes, including 120 "Service ready in nnn minutes".
|
||||
else {
|
||||
// Don't stay connected but don't replace the socket yet by using reset()
|
||||
// so the user can inspect properties of this instance.
|
||||
task.reject(new FtpContext_1.FTPError(res));
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Send an FTP command and handle the first response.
|
||||
*/
|
||||
send(command, ignoreErrorCodesDEPRECATED = false) {
|
||||
if (ignoreErrorCodesDEPRECATED) { // Deprecated starting from 3.9.0
|
||||
this.ftp.log("Deprecated call using send(command, flag) with boolean flag to ignore errors. Use sendIgnoringError(command).");
|
||||
return this.sendIgnoringError(command);
|
||||
}
|
||||
return this.ftp.request(command);
|
||||
}
|
||||
/**
|
||||
* Send an FTP command and ignore an FTP error response. Any other kind of error or timeout will still reject the Promise.
|
||||
*
|
||||
* @param command
|
||||
*/
|
||||
sendIgnoringError(command) {
|
||||
return this.ftp.handle(command, (res, task) => {
|
||||
if (res instanceof FtpContext_1.FTPError) {
|
||||
task.resolve({ code: res.code, message: res.message });
|
||||
}
|
||||
else if (res instanceof Error) {
|
||||
task.reject(res);
|
||||
}
|
||||
else {
|
||||
task.resolve(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Upgrade the current socket connection to TLS.
|
||||
*
|
||||
* @param options TLS options as in `tls.connect(options)`, optional.
|
||||
* @param command Set the authentication command. Optional, default is "AUTH TLS".
|
||||
*/
|
||||
async useTLS(options = {}, command = "AUTH TLS") {
|
||||
const ret = await this.send(command);
|
||||
this.ftp.socket = await (0, netUtils_1.upgradeSocket)(this.ftp.socket, options);
|
||||
this.ftp.tlsOptions = options; // Keep the TLS options for later data connections that should use the same options.
|
||||
this.ftp.log(`Control socket is using: ${(0, netUtils_1.describeTLS)(this.ftp.socket)}`);
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
* Login a user with a password.
|
||||
*
|
||||
* @param user Username to use for login. Optional, default is "anonymous".
|
||||
* @param password Password to use for login. Optional, default is "guest".
|
||||
*/
|
||||
login(user = "anonymous", password = "guest") {
|
||||
this.ftp.log(`Login security: ${(0, netUtils_1.describeTLS)(this.ftp.socket)}`);
|
||||
return this.ftp.handle("USER " + user, (res, task) => {
|
||||
if (res instanceof Error) {
|
||||
task.reject(res);
|
||||
}
|
||||
else if ((0, parseControlResponse_1.positiveCompletion)(res.code)) { // User logged in proceed OR Command superfluous
|
||||
task.resolve(res);
|
||||
}
|
||||
else if (res.code === 331) { // User name okay, need password
|
||||
this.ftp.send("PASS " + password);
|
||||
}
|
||||
else { // Also report error on 332 (Need account)
|
||||
task.reject(new FtpContext_1.FTPError(res));
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Set the usual default settings.
|
||||
*
|
||||
* Settings used:
|
||||
* * Binary mode (TYPE I)
|
||||
* * File structure (STRU F)
|
||||
* * Additional settings for FTPS (PBSZ 0, PROT P)
|
||||
*/
|
||||
async useDefaultSettings() {
|
||||
const features = await this.features();
|
||||
// Use MLSD directory listing if possible. See https://tools.ietf.org/html/rfc3659#section-7.8:
|
||||
// "The presence of the MLST feature indicates that both MLST and MLSD are supported."
|
||||
const supportsMLSD = features.has("MLST");
|
||||
this.availableListCommands = supportsMLSD ? LIST_COMMANDS_MLSD() : LIST_COMMANDS_DEFAULT();
|
||||
await this.send("TYPE I"); // Binary mode
|
||||
await this.sendIgnoringError("STRU F"); // Use file structure
|
||||
await this.sendIgnoringError("OPTS UTF8 ON"); // Some servers expect UTF-8 to be enabled explicitly and setting before login might not have worked.
|
||||
if (supportsMLSD) {
|
||||
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) {
|
||||
await this.sendIgnoringError("PBSZ 0"); // Set to 0 for TLS
|
||||
await this.sendIgnoringError("PROT P"); // Protect channel (also for data connections)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Convenience method that calls `connect`, `useTLS`, `login` and `useDefaultSettings`.
|
||||
*
|
||||
* This is an instance method and thus can be called multiple times during the lifecycle of a `Client`
|
||||
* instance. Whenever you do, the client is reset with a new control connection. This also implies that
|
||||
* you can reopen a `Client` instance that has been closed due to an error when reconnecting with this
|
||||
* method. In fact, reconnecting is the only way to continue using a closed `Client`.
|
||||
*/
|
||||
async access(options = {}) {
|
||||
var _a, _b;
|
||||
const useExplicitTLS = options.secure === true;
|
||||
const useImplicitTLS = options.secure === "implicit";
|
||||
let welcome;
|
||||
if (useImplicitTLS) {
|
||||
welcome = await this.connectImplicitTLS(options.host, options.port, options.secureOptions);
|
||||
}
|
||||
else {
|
||||
welcome = await this.connect(options.host, options.port);
|
||||
}
|
||||
if (useExplicitTLS) {
|
||||
// Fixes https://github.com/patrickjuchli/basic-ftp/issues/166 by making sure
|
||||
// host is set for any future data connection as well.
|
||||
const secureOptions = (_a = options.secureOptions) !== null && _a !== void 0 ? _a : {};
|
||||
secureOptions.host = (_b = secureOptions.host) !== null && _b !== void 0 ? _b : options.host;
|
||||
await this.useTLS(secureOptions);
|
||||
}
|
||||
// Set UTF-8 on before login in case there are non-ascii characters in user or password.
|
||||
// Note that this might not work before login depending on server.
|
||||
await this.sendIgnoringError("OPTS UTF8 ON");
|
||||
await this.login(options.user, options.password);
|
||||
await this.useDefaultSettings();
|
||||
return welcome;
|
||||
}
|
||||
/**
|
||||
* Get the current working directory.
|
||||
*/
|
||||
async pwd() {
|
||||
const res = await this.send("PWD");
|
||||
// The directory is part of the return message, for example:
|
||||
// 257 "/this/that" is current directory.
|
||||
const parsed = res.message.match(/"(.+)"/);
|
||||
if (parsed === null || parsed[1] === undefined) {
|
||||
throw new Error(`Can't parse response to command 'PWD': ${res.message}`);
|
||||
}
|
||||
return parsed[1];
|
||||
}
|
||||
/**
|
||||
* Get a description of supported features.
|
||||
*
|
||||
* This sends the FEAT command and parses the result into a Map where keys correspond to available commands
|
||||
* and values hold further information. Be aware that your FTP servers might not support this
|
||||
* command in which case this method will not throw an exception but just return an empty Map.
|
||||
*/
|
||||
async features() {
|
||||
const res = await this.sendIgnoringError("FEAT");
|
||||
const features = new Map();
|
||||
// Not supporting any special features will be reported with a single line.
|
||||
if (res.code < 400 && (0, parseControlResponse_1.isMultiline)(res.message)) {
|
||||
// The first and last line wrap the multiline response, ignore them.
|
||||
res.message.split("\n").slice(1, -1).forEach(line => {
|
||||
// A typical lines looks like: " REST STREAM" or " MDTM".
|
||||
// Servers might not use an indentation though.
|
||||
const entry = line.trim().split(" ");
|
||||
features.set(entry[0], entry[1] || "");
|
||||
});
|
||||
}
|
||||
return features;
|
||||
}
|
||||
/**
|
||||
* Set the working directory.
|
||||
*/
|
||||
async cd(path) {
|
||||
const validPath = await this.protectWhitespace(path);
|
||||
return this.send("CWD " + validPath);
|
||||
}
|
||||
/**
|
||||
* Switch to the parent directory of the working directory.
|
||||
*/
|
||||
async cdup() {
|
||||
return this.send("CDUP");
|
||||
}
|
||||
/**
|
||||
* Get the last modified time of a file. This is not supported by every FTP server, in which case
|
||||
* calling this method will throw an exception.
|
||||
*/
|
||||
async lastMod(path) {
|
||||
const validPath = await this.protectWhitespace(path);
|
||||
const res = await this.send(`MDTM ${validPath}`);
|
||||
const date = res.message.slice(4);
|
||||
return (0, parseListMLSD_1.parseMLSxDate)(date);
|
||||
}
|
||||
/**
|
||||
* Get the size of a file.
|
||||
*/
|
||||
async size(path) {
|
||||
const validPath = await this.protectWhitespace(path);
|
||||
const command = `SIZE ${validPath}`;
|
||||
const res = await this.send(command);
|
||||
// The size is part of the response message, for example: "213 555555". It's
|
||||
// possible that there is a commmentary appended like "213 5555, some commentary".
|
||||
const size = parseInt(res.message.slice(4), 10);
|
||||
if (Number.isNaN(size)) {
|
||||
throw new Error(`Can't parse response to command '${command}' as a numerical value: ${res.message}`);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
/**
|
||||
* Rename a file.
|
||||
*
|
||||
* Depending on the FTP server this might also be used to move a file from one
|
||||
* directory to another by providing full paths.
|
||||
*/
|
||||
async rename(srcPath, destPath) {
|
||||
const validSrc = await this.protectWhitespace(srcPath);
|
||||
const validDest = await this.protectWhitespace(destPath);
|
||||
await this.send("RNFR " + validSrc);
|
||||
return this.send("RNTO " + validDest);
|
||||
}
|
||||
/**
|
||||
* Remove a file from the current working directory.
|
||||
*
|
||||
* You can ignore FTP error return codes which won't throw an exception if e.g.
|
||||
* the file doesn't exist.
|
||||
*/
|
||||
async remove(path, ignoreErrorCodes = false) {
|
||||
const validPath = await this.protectWhitespace(path);
|
||||
if (ignoreErrorCodes) {
|
||||
return this.sendIgnoringError(`DELE ${validPath}`);
|
||||
}
|
||||
return this.send(`DELE ${validPath}`);
|
||||
}
|
||||
/**
|
||||
* Report transfer progress for any upload or download to a given handler.
|
||||
*
|
||||
* This will also reset the overall transfer counter that can be used for multiple transfers. You can
|
||||
* also call the function without a handler to stop reporting to an earlier one.
|
||||
*
|
||||
* @param handler Handler function to call on transfer progress.
|
||||
*/
|
||||
trackProgress(handler) {
|
||||
this._progressTracker.bytesOverall = 0;
|
||||
this._progressTracker.reportTo(handler);
|
||||
}
|
||||
/**
|
||||
* Upload data from a readable stream or a local file to a remote file.
|
||||
*
|
||||
* @param source Readable stream or path to a local file.
|
||||
* @param toRemotePath Path to a remote file to write to.
|
||||
*/
|
||||
async uploadFrom(source, toRemotePath, options = {}) {
|
||||
return this._uploadWithCommand(source, toRemotePath, "STOR", options);
|
||||
}
|
||||
/**
|
||||
* Upload data from a readable stream or a local file by appending it to an existing file. If the file doesn't
|
||||
* exist the FTP server should create it.
|
||||
*
|
||||
* @param source Readable stream or path to a local file.
|
||||
* @param toRemotePath Path to a remote file to write to.
|
||||
*/
|
||||
async appendFrom(source, toRemotePath, options = {}) {
|
||||
return this._uploadWithCommand(source, toRemotePath, "APPE", options);
|
||||
}
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
async _uploadWithCommand(source, remotePath, command, options) {
|
||||
if (typeof source === "string") {
|
||||
return this._uploadLocalFile(source, remotePath, command, options);
|
||||
}
|
||||
return this._uploadFromStream(source, remotePath, command);
|
||||
}
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
async _uploadLocalFile(localPath, remotePath, command, options) {
|
||||
const fd = await fsOpen(localPath, "r");
|
||||
const source = (0, fs_1.createReadStream)("", {
|
||||
fd,
|
||||
start: options.localStart,
|
||||
end: options.localEndInclusive,
|
||||
autoClose: false
|
||||
});
|
||||
try {
|
||||
return await this._uploadFromStream(source, remotePath, command);
|
||||
}
|
||||
finally {
|
||||
await ignoreError(() => fsClose(fd));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
async _uploadFromStream(source, remotePath, command) {
|
||||
const onError = (err) => this.ftp.closeWithError(err);
|
||||
source.once("error", onError);
|
||||
try {
|
||||
const validPath = await this.protectWhitespace(remotePath);
|
||||
await this.prepareTransfer(this.ftp);
|
||||
// Keep the keyword `await` or the `finally` clause below runs too early
|
||||
// and removes the event listener for the source stream too early.
|
||||
return await (0, transfer_1.uploadFrom)(source, {
|
||||
ftp: this.ftp,
|
||||
tracker: this._progressTracker,
|
||||
command,
|
||||
remotePath: validPath,
|
||||
type: "upload"
|
||||
});
|
||||
}
|
||||
finally {
|
||||
source.removeListener("error", onError);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Download a remote file and pipe its data to a writable stream or to a local file.
|
||||
*
|
||||
* You can optionally define at which position of the remote file you'd like to start
|
||||
* downloading. If the destination you provide is a file, the offset will be applied
|
||||
* to it as well. For example: To resume a failed download, you'd request the size of
|
||||
* the local, partially downloaded file and use that as the offset. Assuming the size
|
||||
* is 23, you'd download the rest using `downloadTo("local.txt", "remote.txt", 23)`.
|
||||
*
|
||||
* @param destination Stream or path for a local file to write to.
|
||||
* @param fromRemotePath Path of the remote file to read from.
|
||||
* @param startAt Position within the remote file to start downloading at. If the destination is a file, this offset is also applied to it.
|
||||
*/
|
||||
async downloadTo(destination, fromRemotePath, startAt = 0) {
|
||||
if (typeof destination === "string") {
|
||||
return this._downloadToFile(destination, fromRemotePath, startAt);
|
||||
}
|
||||
return this._downloadToStream(destination, fromRemotePath, startAt);
|
||||
}
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
async _downloadToFile(localPath, remotePath, startAt) {
|
||||
const appendingToLocalFile = startAt > 0;
|
||||
const fileSystemFlags = appendingToLocalFile ? "r+" : "w";
|
||||
const fd = await fsOpen(localPath, fileSystemFlags);
|
||||
const destination = (0, fs_1.createWriteStream)("", {
|
||||
fd,
|
||||
start: startAt,
|
||||
autoClose: false
|
||||
});
|
||||
try {
|
||||
return await this._downloadToStream(destination, remotePath, startAt);
|
||||
}
|
||||
catch (err) {
|
||||
const localFileStats = await ignoreError(() => fsStat(localPath));
|
||||
const hasDownloadedData = localFileStats && localFileStats.size > 0;
|
||||
const shouldRemoveLocalFile = !appendingToLocalFile && !hasDownloadedData;
|
||||
if (shouldRemoveLocalFile) {
|
||||
await ignoreError(() => fsUnlink(localPath));
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
finally {
|
||||
await ignoreError(() => fsClose(fd));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
async _downloadToStream(destination, remotePath, startAt) {
|
||||
const onError = (err) => this.ftp.closeWithError(err);
|
||||
destination.once("error", onError);
|
||||
try {
|
||||
const validPath = await this.protectWhitespace(remotePath);
|
||||
await this.prepareTransfer(this.ftp);
|
||||
// Keep the keyword `await` or the `finally` clause below runs too early
|
||||
// and removes the event listener for the source stream too early.
|
||||
return await (0, transfer_1.downloadTo)(destination, {
|
||||
ftp: this.ftp,
|
||||
tracker: this._progressTracker,
|
||||
command: startAt > 0 ? `REST ${startAt}` : `RETR ${validPath}`,
|
||||
remotePath: validPath,
|
||||
type: "download"
|
||||
});
|
||||
}
|
||||
finally {
|
||||
destination.removeListener("error", onError);
|
||||
destination.end();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* List files and directories in the current working directory, or from `path` if specified.
|
||||
*
|
||||
* @param [path] Path to remote file or directory.
|
||||
*/
|
||||
async list(path = "") {
|
||||
const validPath = await this.protectWhitespace(path);
|
||||
let lastError;
|
||||
for (const candidate of this.availableListCommands) {
|
||||
const command = validPath === "" ? candidate : `${candidate} ${validPath}`;
|
||||
await this.prepareTransfer(this.ftp);
|
||||
try {
|
||||
const parsedList = await this._requestListWithCommand(command);
|
||||
// Use successful candidate for all subsequent requests.
|
||||
this.availableListCommands = [candidate];
|
||||
return parsedList;
|
||||
}
|
||||
catch (err) {
|
||||
const shouldTryNext = err instanceof FtpContext_1.FTPError;
|
||||
if (!shouldTryNext) {
|
||||
throw err;
|
||||
}
|
||||
lastError = err;
|
||||
}
|
||||
}
|
||||
throw lastError;
|
||||
}
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
async _requestListWithCommand(command) {
|
||||
const buffer = new StringWriter_1.StringWriter();
|
||||
await (0, transfer_1.downloadTo)(buffer, {
|
||||
ftp: this.ftp,
|
||||
tracker: this._progressTracker,
|
||||
command,
|
||||
remotePath: "",
|
||||
type: "list"
|
||||
});
|
||||
const text = buffer.getText(this.ftp.encoding);
|
||||
this.ftp.log(text);
|
||||
return this.parseList(text);
|
||||
}
|
||||
/**
|
||||
* Remove a directory and all of its content.
|
||||
*
|
||||
* @param remoteDirPath The path of the remote directory to delete.
|
||||
* @example client.removeDir("foo") // Remove directory 'foo' using a relative path.
|
||||
* @example client.removeDir("foo/bar") // Remove directory 'bar' using a relative path.
|
||||
* @example client.removeDir("/foo/bar") // Remove directory 'bar' using an absolute path.
|
||||
* @example client.removeDir("/") // Remove everything.
|
||||
*/
|
||||
async removeDir(remoteDirPath) {
|
||||
return this._exitAtCurrentDirectory(async () => {
|
||||
await this.cd(remoteDirPath);
|
||||
// Get the absolute path of the target because remoteDirPath might be a relative path, even `../` is possible.
|
||||
const absoluteDirPath = await this.pwd();
|
||||
await this.clearWorkingDir();
|
||||
const dirIsRoot = absoluteDirPath === "/";
|
||||
if (!dirIsRoot) {
|
||||
await this.cdup();
|
||||
await this.removeEmptyDir(absoluteDirPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Remove all files and directories in the working directory without removing
|
||||
* the working directory itself.
|
||||
*/
|
||||
async clearWorkingDir() {
|
||||
for (const file of await this.list()) {
|
||||
if (file.isDirectory) {
|
||||
await this.cd(file.name);
|
||||
await this.clearWorkingDir();
|
||||
await this.cdup();
|
||||
await this.removeEmptyDir(file.name);
|
||||
}
|
||||
else {
|
||||
await this.remove(file.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Upload the contents of a local directory to the remote working directory.
|
||||
*
|
||||
* This will overwrite existing files with the same names and reuse existing directories.
|
||||
* Unrelated files and directories will remain untouched. You can optionally provide a `remoteDirPath`
|
||||
* to put the contents inside a directory which will be created if necessary including all
|
||||
* intermediate directories. If you did provide a remoteDirPath the working directory will stay
|
||||
* the same as before calling this method.
|
||||
*
|
||||
* @param localDirPath Local path, e.g. "foo/bar" or "../test"
|
||||
* @param [remoteDirPath] Remote path of a directory to upload to. Working directory if undefined.
|
||||
*/
|
||||
async uploadFromDir(localDirPath, remoteDirPath) {
|
||||
return this._exitAtCurrentDirectory(async () => {
|
||||
if (remoteDirPath) {
|
||||
await this.ensureDir(remoteDirPath);
|
||||
}
|
||||
return await this._uploadToWorkingDir(localDirPath);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
async _uploadToWorkingDir(localDirPath) {
|
||||
const files = await fsReadDir(localDirPath);
|
||||
for (const file of files) {
|
||||
const fullPath = (0, path_1.join)(localDirPath, file);
|
||||
const stats = await fsStat(fullPath);
|
||||
if (stats.isFile()) {
|
||||
await this.uploadFrom(fullPath, file);
|
||||
}
|
||||
else if (stats.isDirectory()) {
|
||||
await this._openDir(file);
|
||||
await this._uploadToWorkingDir(fullPath);
|
||||
await this.cdup();
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Download all files and directories of the working directory to a local directory.
|
||||
*
|
||||
* @param localDirPath The local directory to download to.
|
||||
* @param remoteDirPath Remote directory to download. Current working directory if not specified.
|
||||
*/
|
||||
async downloadToDir(localDirPath, remoteDirPath) {
|
||||
return this._exitAtCurrentDirectory(async () => {
|
||||
if (remoteDirPath) {
|
||||
await this.cd(remoteDirPath);
|
||||
}
|
||||
return await this._downloadFromWorkingDir(localDirPath);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @protected
|
||||
*/
|
||||
async _downloadFromWorkingDir(localDirPath) {
|
||||
await ensureLocalDirectory(localDirPath);
|
||||
for (const file of await this.list()) {
|
||||
const localPath = (0, path_1.join)(localDirPath, file.name);
|
||||
if (file.isDirectory) {
|
||||
await this.cd(file.name);
|
||||
await this._downloadFromWorkingDir(localPath);
|
||||
await this.cdup();
|
||||
}
|
||||
else if (file.isFile) {
|
||||
await this.downloadTo(localPath, file.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Make sure a given remote path exists, creating all directories as necessary.
|
||||
* This function also changes the current working directory to the given path.
|
||||
*/
|
||||
async ensureDir(remoteDirPath) {
|
||||
// If the remoteDirPath was absolute go to root directory.
|
||||
if (remoteDirPath.startsWith("/")) {
|
||||
await this.cd("/");
|
||||
}
|
||||
const names = remoteDirPath.split("/").filter(name => name !== "");
|
||||
for (const name of names) {
|
||||
await this._openDir(name);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Try to create a directory and enter it. This will not raise an exception if the directory
|
||||
* couldn't be created if for example it already exists.
|
||||
* @protected
|
||||
*/
|
||||
async _openDir(dirName) {
|
||||
await this.sendIgnoringError("MKD " + dirName);
|
||||
await this.cd(dirName);
|
||||
}
|
||||
/**
|
||||
* Remove an empty directory, will fail if not empty.
|
||||
*/
|
||||
async removeEmptyDir(path) {
|
||||
const validPath = await this.protectWhitespace(path);
|
||||
return this.send(`RMD ${validPath}`);
|
||||
}
|
||||
/**
|
||||
* FTP servers can't handle filenames that have leading whitespace. This method transforms
|
||||
* a given path to fix that issue for most cases.
|
||||
*/
|
||||
async protectWhitespace(path) {
|
||||
if (!path.startsWith(" ")) {
|
||||
return path;
|
||||
}
|
||||
// Handle leading whitespace by prepending the absolute path:
|
||||
// " test.txt" while being in the root directory becomes "/ test.txt".
|
||||
const pwd = await this.pwd();
|
||||
const absolutePathPrefix = pwd.endsWith("/") ? pwd : pwd + "/";
|
||||
return absolutePathPrefix + path;
|
||||
}
|
||||
async _exitAtCurrentDirectory(func) {
|
||||
const userDir = await this.pwd();
|
||||
try {
|
||||
return await func();
|
||||
}
|
||||
finally {
|
||||
if (!this.closed) {
|
||||
await ignoreError(() => this.cd(userDir));
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Try all available transfer strategies and pick the first one that works. Update `client` to
|
||||
* use the working strategy for all successive transfer requests.
|
||||
*
|
||||
* @returns a function that will try the provided strategies.
|
||||
*/
|
||||
_enterFirstCompatibleMode(strategies) {
|
||||
return async (ftp) => {
|
||||
ftp.log("Trying to find optimal transfer strategy...");
|
||||
let lastError = undefined;
|
||||
for (const strategy of strategies) {
|
||||
try {
|
||||
const res = await strategy(ftp);
|
||||
ftp.log("Optimal transfer strategy found.");
|
||||
this.prepareTransfer = strategy; // eslint-disable-line require-atomic-updates
|
||||
return res;
|
||||
}
|
||||
catch (err) {
|
||||
// Try the next candidate no matter the exact error. It's possible that a server
|
||||
// answered incorrectly to a strategy, for example a PASV answer to an EPSV.
|
||||
lastError = err;
|
||||
}
|
||||
}
|
||||
throw new Error(`None of the available transfer strategies work. Last error response was '${lastError}'.`);
|
||||
};
|
||||
}
|
||||
/**
|
||||
* DEPRECATED, use `uploadFrom`.
|
||||
* @deprecated
|
||||
*/
|
||||
async upload(source, toRemotePath, options = {}) {
|
||||
this.ftp.log("Warning: upload() has been deprecated, use uploadFrom().");
|
||||
return this.uploadFrom(source, toRemotePath, options);
|
||||
}
|
||||
/**
|
||||
* DEPRECATED, use `appendFrom`.
|
||||
* @deprecated
|
||||
*/
|
||||
async append(source, toRemotePath, options = {}) {
|
||||
this.ftp.log("Warning: append() has been deprecated, use appendFrom().");
|
||||
return this.appendFrom(source, toRemotePath, options);
|
||||
}
|
||||
/**
|
||||
* DEPRECATED, use `downloadTo`.
|
||||
* @deprecated
|
||||
*/
|
||||
async download(destination, fromRemotePath, startAt = 0) {
|
||||
this.ftp.log("Warning: download() has been deprecated, use downloadTo().");
|
||||
return this.downloadTo(destination, fromRemotePath, startAt);
|
||||
}
|
||||
/**
|
||||
* DEPRECATED, use `uploadFromDir`.
|
||||
* @deprecated
|
||||
*/
|
||||
async uploadDir(localDirPath, remoteDirPath) {
|
||||
this.ftp.log("Warning: uploadDir() has been deprecated, use uploadFromDir().");
|
||||
return this.uploadFromDir(localDirPath, remoteDirPath);
|
||||
}
|
||||
/**
|
||||
* DEPRECATED, use `downloadToDir`.
|
||||
* @deprecated
|
||||
*/
|
||||
async downloadDir(localDirPath) {
|
||||
this.ftp.log("Warning: downloadDir() has been deprecated, use downloadToDir().");
|
||||
return this.downloadToDir(localDirPath);
|
||||
}
|
||||
}
|
||||
exports.Client = Client;
|
||||
async function ensureLocalDirectory(path) {
|
||||
try {
|
||||
await fsStat(path);
|
||||
}
|
||||
catch (err) {
|
||||
await fsMkDir(path, { recursive: true });
|
||||
}
|
||||
}
|
||||
async function ignoreError(func) {
|
||||
try {
|
||||
return await func();
|
||||
}
|
||||
catch (err) {
|
||||
// Ignore
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
80
node_modules/basic-ftp/dist/FileInfo.d.ts
generated
vendored
Normal file
80
node_modules/basic-ftp/dist/FileInfo.d.ts
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
export declare enum FileType {
|
||||
Unknown = 0,
|
||||
File = 1,
|
||||
Directory = 2,
|
||||
SymbolicLink = 3
|
||||
}
|
||||
export interface UnixPermissions {
|
||||
readonly user: number;
|
||||
readonly group: number;
|
||||
readonly world: number;
|
||||
}
|
||||
/**
|
||||
* Describes a file, directory or symbolic link.
|
||||
*/
|
||||
export declare class FileInfo {
|
||||
name: string;
|
||||
static UnixPermission: {
|
||||
Read: number;
|
||||
Write: number;
|
||||
Execute: number;
|
||||
};
|
||||
type: FileType;
|
||||
size: number;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
rawModifiedAt: string;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
modifiedAt?: Date;
|
||||
/**
|
||||
* 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`.
|
||||
*/
|
||||
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);
|
||||
get isDirectory(): boolean;
|
||||
get isSymbolicLink(): boolean;
|
||||
get isFile(): boolean;
|
||||
/**
|
||||
* Deprecated, legacy API. Use `rawModifiedAt` instead.
|
||||
* @deprecated
|
||||
*/
|
||||
get date(): string;
|
||||
set date(rawModifiedAt: string);
|
||||
}
|
||||
92
node_modules/basic-ftp/dist/FileInfo.js
generated
vendored
Normal file
92
node_modules/basic-ftp/dist/FileInfo.js
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.FileInfo = exports.FileType = void 0;
|
||||
var FileType;
|
||||
(function (FileType) {
|
||||
FileType[FileType["Unknown"] = 0] = "Unknown";
|
||||
FileType[FileType["File"] = 1] = "File";
|
||||
FileType[FileType["Directory"] = 2] = "Directory";
|
||||
FileType[FileType["SymbolicLink"] = 3] = "SymbolicLink";
|
||||
})(FileType || (exports.FileType = FileType = {}));
|
||||
/**
|
||||
* Describes a file, directory or symbolic link.
|
||||
*/
|
||||
class FileInfo {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
this.type = FileType.Unknown;
|
||||
this.size = 0;
|
||||
/**
|
||||
* 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.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;
|
||||
}
|
||||
get isDirectory() {
|
||||
return this.type === FileType.Directory;
|
||||
}
|
||||
get isSymbolicLink() {
|
||||
return this.type === FileType.SymbolicLink;
|
||||
}
|
||||
get isFile() {
|
||||
return this.type === FileType.File;
|
||||
}
|
||||
/**
|
||||
* Deprecated, legacy API. Use `rawModifiedAt` instead.
|
||||
* @deprecated
|
||||
*/
|
||||
get date() {
|
||||
return this.rawModifiedAt;
|
||||
}
|
||||
set date(rawModifiedAt) {
|
||||
this.rawModifiedAt = rawModifiedAt;
|
||||
}
|
||||
}
|
||||
exports.FileInfo = FileInfo;
|
||||
FileInfo.UnixPermission = {
|
||||
Read: 4,
|
||||
Write: 2,
|
||||
Execute: 1
|
||||
};
|
||||
178
node_modules/basic-ftp/dist/FtpContext.d.ts
generated
vendored
Normal file
178
node_modules/basic-ftp/dist/FtpContext.d.ts
generated
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
/// <reference types="node" />
|
||||
/// <reference types="node" />
|
||||
/// <reference types="node" />
|
||||
import { Socket } from "net";
|
||||
import { ConnectionOptions as TLSConnectionOptions, TLSSocket } from "tls";
|
||||
import { StringEncoding } from "./StringEncoding";
|
||||
interface Task {
|
||||
/** Handles a response for a task. */
|
||||
readonly responseHandler: ResponseHandler;
|
||||
/** Resolves or rejects a task. */
|
||||
readonly resolver: TaskResolver;
|
||||
/** Call stack when task was run. */
|
||||
readonly stack: string;
|
||||
}
|
||||
export interface TaskResolver {
|
||||
resolve(args: any): void;
|
||||
reject(err: Error): void;
|
||||
}
|
||||
export interface FTPResponse {
|
||||
/** FTP response code */
|
||||
readonly code: number;
|
||||
/** Whole response including response code */
|
||||
readonly message: string;
|
||||
}
|
||||
export type ResponseHandler = (response: Error | FTPResponse, task: TaskResolver) => void;
|
||||
/**
|
||||
* Describes an FTP server error response including the FTP response code.
|
||||
*/
|
||||
export declare class FTPError extends Error {
|
||||
/** FTP response code */
|
||||
readonly code: number;
|
||||
constructor(res: FTPResponse);
|
||||
}
|
||||
/**
|
||||
* FTPContext holds the control and data sockets of an FTP connection and provides a
|
||||
* simplified way to interact with an FTP server, handle responses, errors and timeouts.
|
||||
*
|
||||
* It doesn't implement or use any FTP commands. It's only a foundation to make writing an FTP
|
||||
* client as easy as possible. You won't usually instantiate this, but use `Client`.
|
||||
*/
|
||||
export declare class FTPContext {
|
||||
readonly timeout: number;
|
||||
/** Debug-level logging of all socket communication. */
|
||||
verbose: boolean;
|
||||
/** IP version to prefer (4: IPv4, 6: IPv6, undefined: automatic). */
|
||||
ipFamily: number | undefined;
|
||||
/** Options for TLS connections. */
|
||||
tlsOptions: TLSConnectionOptions;
|
||||
/** Current task to be resolved or rejected. */
|
||||
protected _task: Task | undefined;
|
||||
/** A multiline response might be received as multiple chunks. */
|
||||
protected _partialResponse: string;
|
||||
/** The reason why a context has been closed. */
|
||||
protected _closingError: NodeJS.ErrnoException | undefined;
|
||||
/** Encoding supported by Node applied to commands, responses and directory listing data. */
|
||||
protected _encoding: StringEncoding;
|
||||
/** FTP control connection */
|
||||
protected _socket: Socket | TLSSocket;
|
||||
/** FTP data connection */
|
||||
protected _dataSocket: Socket | TLSSocket | undefined;
|
||||
/**
|
||||
* Instantiate an FTP context.
|
||||
*
|
||||
* @param timeout - Timeout in milliseconds to apply to control and data connections. Use 0 for no timeout.
|
||||
* @param encoding - Encoding to use for control connection. UTF-8 by default. Use "latin1" for older servers.
|
||||
*/
|
||||
constructor(timeout?: number, encoding?: StringEncoding);
|
||||
/**
|
||||
* Close the context.
|
||||
*/
|
||||
close(): void;
|
||||
/**
|
||||
* Close the context with an error.
|
||||
*/
|
||||
closeWithError(err: Error): void;
|
||||
/**
|
||||
* Returns true if this context has been closed or hasn't been connected yet. You can reopen it with `access`.
|
||||
*/
|
||||
get closed(): boolean;
|
||||
/**
|
||||
* Reset this contex and all of its state.
|
||||
*/
|
||||
reset(): void;
|
||||
/**
|
||||
* Get the FTP control socket.
|
||||
*/
|
||||
get socket(): Socket | TLSSocket;
|
||||
/**
|
||||
* Set the socket for the control connection. This will only close the current control socket
|
||||
* if the new one is not an upgrade to the current one.
|
||||
*/
|
||||
set socket(socket: Socket | TLSSocket);
|
||||
/**
|
||||
* Get the current FTP data connection if present.
|
||||
*/
|
||||
get dataSocket(): Socket | TLSSocket | undefined;
|
||||
/**
|
||||
* Set the socket for the data connection. This will automatically close the former data socket.
|
||||
*/
|
||||
set dataSocket(socket: Socket | TLSSocket | undefined);
|
||||
/**
|
||||
* Get the currently used encoding.
|
||||
*/
|
||||
get encoding(): StringEncoding;
|
||||
/**
|
||||
* Set the encoding used for the control socket.
|
||||
*
|
||||
* See https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings for what encodings
|
||||
* are supported by Node.
|
||||
*/
|
||||
set encoding(encoding: StringEncoding);
|
||||
/**
|
||||
* Send an FTP command without waiting for or handling the result.
|
||||
*/
|
||||
send(command: string): void;
|
||||
/**
|
||||
* Send an FTP command and handle the first response. Use this if you have a simple
|
||||
* request-response situation.
|
||||
*/
|
||||
request(command: string): Promise<FTPResponse>;
|
||||
/**
|
||||
* Send an FTP command and handle any response until you resolve/reject. Use this if you expect multiple responses
|
||||
* to a request. This returns a Promise that will hold whatever the response handler passed on when resolving/rejecting its task.
|
||||
*/
|
||||
handle(command: string | undefined, responseHandler: ResponseHandler): Promise<any>;
|
||||
/**
|
||||
* Log message if set to be verbose.
|
||||
*/
|
||||
log(message: string): void;
|
||||
/**
|
||||
* Return true if the control socket is using TLS. This does not mean that a session
|
||||
* has already been negotiated.
|
||||
*/
|
||||
get hasTLS(): boolean;
|
||||
/**
|
||||
* Removes reference to current task and handler. This won't resolve or reject the task.
|
||||
* @protected
|
||||
*/
|
||||
protected _stopTrackingTask(): void;
|
||||
/**
|
||||
* Handle incoming data on the control socket. The chunk is going to be of type `string`
|
||||
* because we let `socket` handle encoding with `setEncoding`.
|
||||
* @protected
|
||||
*/
|
||||
protected _onControlSocketData(chunk: string): void;
|
||||
/**
|
||||
* Send the current handler a response. This is usually a control socket response
|
||||
* or a socket event, like an error or timeout.
|
||||
* @protected
|
||||
*/
|
||||
protected _passToHandler(response: Error | FTPResponse): void;
|
||||
/**
|
||||
* Setup all error handlers for a socket.
|
||||
* @protected
|
||||
*/
|
||||
protected _setupDefaultErrorHandlers(socket: Socket, identifier: string): void;
|
||||
/**
|
||||
* Close the control socket. Sends QUIT, then FIN, and ignores any response or error.
|
||||
*/
|
||||
protected _closeControlSocket(): void;
|
||||
/**
|
||||
* Close a socket, ignores any error.
|
||||
* @protected
|
||||
*/
|
||||
protected _closeSocket(socket: Socket | undefined): void;
|
||||
/**
|
||||
* Remove all default listeners for socket.
|
||||
* @protected
|
||||
*/
|
||||
protected _removeSocketListeners(socket: Socket): void;
|
||||
/**
|
||||
* Provide a new socket instance.
|
||||
*
|
||||
* Internal use only, replaced for unit tests.
|
||||
*/
|
||||
_newSocket(): Socket;
|
||||
}
|
||||
export {};
|
||||
365
node_modules/basic-ftp/dist/FtpContext.js
generated
vendored
Normal file
365
node_modules/basic-ftp/dist/FtpContext.js
generated
vendored
Normal file
@@ -0,0 +1,365 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.FTPContext = exports.FTPError = void 0;
|
||||
const net_1 = require("net");
|
||||
const parseControlResponse_1 = require("./parseControlResponse");
|
||||
/**
|
||||
* Describes an FTP server error response including the FTP response code.
|
||||
*/
|
||||
class FTPError extends Error {
|
||||
constructor(res) {
|
||||
super(res.message);
|
||||
this.name = this.constructor.name;
|
||||
this.code = res.code;
|
||||
}
|
||||
}
|
||||
exports.FTPError = FTPError;
|
||||
function doNothing() {
|
||||
/** Do nothing */
|
||||
}
|
||||
/**
|
||||
* FTPContext holds the control and data sockets of an FTP connection and provides a
|
||||
* simplified way to interact with an FTP server, handle responses, errors and timeouts.
|
||||
*
|
||||
* It doesn't implement or use any FTP commands. It's only a foundation to make writing an FTP
|
||||
* client as easy as possible. You won't usually instantiate this, but use `Client`.
|
||||
*/
|
||||
class FTPContext {
|
||||
/**
|
||||
* Instantiate an FTP context.
|
||||
*
|
||||
* @param timeout - Timeout in milliseconds to apply to control and data connections. Use 0 for no timeout.
|
||||
* @param encoding - Encoding to use for control connection. UTF-8 by default. Use "latin1" for older servers.
|
||||
*/
|
||||
constructor(timeout = 0, encoding = "utf8") {
|
||||
this.timeout = timeout;
|
||||
/** Debug-level logging of all socket communication. */
|
||||
this.verbose = false;
|
||||
/** IP version to prefer (4: IPv4, 6: IPv6, undefined: automatic). */
|
||||
this.ipFamily = undefined;
|
||||
/** Options for TLS connections. */
|
||||
this.tlsOptions = {};
|
||||
/** A multiline response might be received as multiple chunks. */
|
||||
this._partialResponse = "";
|
||||
this._encoding = encoding;
|
||||
// Help Typescript understand that we do indeed set _socket in the constructor but use the setter method to do so.
|
||||
this._socket = this.socket = this._newSocket();
|
||||
this._dataSocket = undefined;
|
||||
}
|
||||
/**
|
||||
* Close the context.
|
||||
*/
|
||||
close() {
|
||||
// Internally, closing a context is always described with an error. If there is still a task running, it will
|
||||
// abort with an exception that the user closed the client during a task. If no task is running, no exception is
|
||||
// thrown but all newly submitted tasks after that will abort the exception that the client has been closed.
|
||||
// In addition the user will get a stack trace pointing to where exactly the client has been closed. So in any
|
||||
// case use _closingError to determine whether a context is closed. This also allows us to have a single code-path
|
||||
// for closing a context making the implementation easier.
|
||||
const message = this._task ? "User closed client during task" : "User closed client";
|
||||
const err = new Error(message);
|
||||
this.closeWithError(err);
|
||||
}
|
||||
/**
|
||||
* Close the context with an error.
|
||||
*/
|
||||
closeWithError(err) {
|
||||
// If this context already has been closed, don't overwrite the reason.
|
||||
if (this._closingError) {
|
||||
return;
|
||||
}
|
||||
this._closingError = err;
|
||||
// Close the sockets but don't fully reset this context to preserve `this._closingError`.
|
||||
this._closeControlSocket();
|
||||
this._closeSocket(this._dataSocket);
|
||||
// Give the user's task a chance to react, maybe cleanup resources.
|
||||
this._passToHandler(err);
|
||||
// The task might not have been rejected by the user after receiving the error.
|
||||
this._stopTrackingTask();
|
||||
}
|
||||
/**
|
||||
* Returns true if this context has been closed or hasn't been connected yet. You can reopen it with `access`.
|
||||
*/
|
||||
get closed() {
|
||||
return this.socket.remoteAddress === undefined || this._closingError !== undefined;
|
||||
}
|
||||
/**
|
||||
* Reset this contex and all of its state.
|
||||
*/
|
||||
reset() {
|
||||
this.socket = this._newSocket();
|
||||
}
|
||||
/**
|
||||
* Get the FTP control socket.
|
||||
*/
|
||||
get socket() {
|
||||
return this._socket;
|
||||
}
|
||||
/**
|
||||
* Set the socket for the control connection. This will only close the current control socket
|
||||
* if the new one is not an upgrade to the current one.
|
||||
*/
|
||||
set socket(socket) {
|
||||
// No data socket should be open in any case where the control socket is set or upgraded.
|
||||
this.dataSocket = undefined;
|
||||
// This being a reset, reset any other state apart from the socket.
|
||||
this.tlsOptions = {};
|
||||
this._partialResponse = "";
|
||||
if (this._socket) {
|
||||
const newSocketUpgradesExisting = socket.localPort === this._socket.localPort;
|
||||
if (newSocketUpgradesExisting) {
|
||||
this._removeSocketListeners(this.socket);
|
||||
}
|
||||
else {
|
||||
this._closeControlSocket();
|
||||
}
|
||||
}
|
||||
if (socket) {
|
||||
// Setting a completely new control socket is in essence something like a reset. That's
|
||||
// why we also close any open data connection above. We can go one step further and reset
|
||||
// a possible closing error. That means that a closed FTPContext can be "reopened" by
|
||||
// setting a new control socket.
|
||||
this._closingError = undefined;
|
||||
// Don't set a timeout yet. Timeout for control sockets is only active during a task, see handle() below.
|
||||
socket.setTimeout(0);
|
||||
socket.setEncoding(this._encoding);
|
||||
socket.setKeepAlive(true);
|
||||
socket.on("data", data => this._onControlSocketData(data));
|
||||
// Server sending a FIN packet is treated as an error.
|
||||
socket.on("end", () => this.closeWithError(new Error("Server sent FIN packet unexpectedly, closing connection.")));
|
||||
// Control being closed without error by server is treated as an error.
|
||||
socket.on("close", hadError => { if (!hadError)
|
||||
this.closeWithError(new Error("Server closed connection unexpectedly.")); });
|
||||
this._setupDefaultErrorHandlers(socket, "control socket");
|
||||
}
|
||||
this._socket = socket;
|
||||
}
|
||||
/**
|
||||
* Get the current FTP data connection if present.
|
||||
*/
|
||||
get dataSocket() {
|
||||
return this._dataSocket;
|
||||
}
|
||||
/**
|
||||
* Set the socket for the data connection. This will automatically close the former data socket.
|
||||
*/
|
||||
set dataSocket(socket) {
|
||||
this._closeSocket(this._dataSocket);
|
||||
if (socket) {
|
||||
// Don't set a timeout yet. Timeout data socket should be activated when data transmission starts
|
||||
// and timeout on control socket is deactivated.
|
||||
socket.setTimeout(0);
|
||||
this._setupDefaultErrorHandlers(socket, "data socket");
|
||||
}
|
||||
this._dataSocket = socket;
|
||||
}
|
||||
/**
|
||||
* Get the currently used encoding.
|
||||
*/
|
||||
get encoding() {
|
||||
return this._encoding;
|
||||
}
|
||||
/**
|
||||
* Set the encoding used for the control socket.
|
||||
*
|
||||
* See https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings for what encodings
|
||||
* are supported by Node.
|
||||
*/
|
||||
set encoding(encoding) {
|
||||
this._encoding = encoding;
|
||||
if (this.socket) {
|
||||
this.socket.setEncoding(encoding);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Send an FTP command without waiting for or handling the result.
|
||||
*/
|
||||
send(command) {
|
||||
const containsPassword = command.startsWith("PASS");
|
||||
const message = containsPassword ? "> PASS ###" : `> ${command}`;
|
||||
this.log(message);
|
||||
this._socket.write(command + "\r\n", this.encoding);
|
||||
}
|
||||
/**
|
||||
* Send an FTP command and handle the first response. Use this if you have a simple
|
||||
* request-response situation.
|
||||
*/
|
||||
request(command) {
|
||||
return this.handle(command, (res, task) => {
|
||||
if (res instanceof Error) {
|
||||
task.reject(res);
|
||||
}
|
||||
else {
|
||||
task.resolve(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Send an FTP command and handle any response until you resolve/reject. Use this if you expect multiple responses
|
||||
* to a request. This returns a Promise that will hold whatever the response handler passed on when resolving/rejecting its task.
|
||||
*/
|
||||
handle(command, responseHandler) {
|
||||
if (this._task) {
|
||||
const err = new Error("User launched a task while another one is still running. Forgot to use 'await' or '.then()'?");
|
||||
err.stack += `\nRunning task launched at: ${this._task.stack}`;
|
||||
this.closeWithError(err);
|
||||
// Don't return here, continue with returning the Promise that will then be rejected
|
||||
// because the context closed already. That way, users will receive an exception where
|
||||
// they called this method by mistake.
|
||||
}
|
||||
return new Promise((resolveTask, rejectTask) => {
|
||||
this._task = {
|
||||
stack: new Error().stack || "Unknown call stack",
|
||||
responseHandler,
|
||||
resolver: {
|
||||
resolve: arg => {
|
||||
this._stopTrackingTask();
|
||||
resolveTask(arg);
|
||||
},
|
||||
reject: err => {
|
||||
this._stopTrackingTask();
|
||||
rejectTask(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (this._closingError) {
|
||||
// This client has been closed. Provide an error that describes this one as being caused
|
||||
// by `_closingError`, include stack traces for both.
|
||||
const err = new Error(`Client is closed because ${this._closingError.message}`); // Type 'Error' is not correctly defined, doesn't have 'code'.
|
||||
err.stack += `\nClosing reason: ${this._closingError.stack}`;
|
||||
err.code = this._closingError.code !== undefined ? this._closingError.code : "0";
|
||||
this._passToHandler(err);
|
||||
return;
|
||||
}
|
||||
// Only track control socket timeout during the lifecycle of a task. This avoids timeouts on idle sockets,
|
||||
// the default socket behaviour which is not expected by most users.
|
||||
this.socket.setTimeout(this.timeout);
|
||||
if (command) {
|
||||
this.send(command);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Log message if set to be verbose.
|
||||
*/
|
||||
log(message) {
|
||||
if (this.verbose) {
|
||||
// tslint:disable-next-line no-console
|
||||
console.log(message);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return true if the control socket is using TLS. This does not mean that a session
|
||||
* has already been negotiated.
|
||||
*/
|
||||
get hasTLS() {
|
||||
return "encrypted" in this._socket;
|
||||
}
|
||||
/**
|
||||
* Removes reference to current task and handler. This won't resolve or reject the task.
|
||||
* @protected
|
||||
*/
|
||||
_stopTrackingTask() {
|
||||
// Disable timeout on control socket if there is no task active.
|
||||
this.socket.setTimeout(0);
|
||||
this._task = undefined;
|
||||
}
|
||||
/**
|
||||
* Handle incoming data on the control socket. The chunk is going to be of type `string`
|
||||
* because we let `socket` handle encoding with `setEncoding`.
|
||||
* @protected
|
||||
*/
|
||||
_onControlSocketData(chunk) {
|
||||
this.log(`< ${chunk}`);
|
||||
// This chunk might complete an earlier partial response.
|
||||
const completeResponse = this._partialResponse + chunk;
|
||||
const parsed = (0, parseControlResponse_1.parseControlResponse)(completeResponse);
|
||||
// Remember any incomplete remainder.
|
||||
this._partialResponse = parsed.rest;
|
||||
// Each response group is passed along individually.
|
||||
for (const message of parsed.messages) {
|
||||
const code = parseInt(message.substr(0, 3), 10);
|
||||
const response = { code, message };
|
||||
const err = code >= 400 ? new FTPError(response) : undefined;
|
||||
this._passToHandler(err ? err : response);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Send the current handler a response. This is usually a control socket response
|
||||
* or a socket event, like an error or timeout.
|
||||
* @protected
|
||||
*/
|
||||
_passToHandler(response) {
|
||||
if (this._task) {
|
||||
this._task.responseHandler(response, this._task.resolver);
|
||||
}
|
||||
// Errors other than FTPError always close the client. If there isn't an active task to handle the error,
|
||||
// the next one submitted will receive it using `_closingError`.
|
||||
// There is only one edge-case: If there is an FTPError while no task is active, the error will be dropped.
|
||||
// But that means that the user sent an FTP command with no intention of handling the result. So why should the
|
||||
// error be handled? Maybe log it at least? Debug logging will already do that and the client stays useable after
|
||||
// FTPError. So maybe no need to do anything here.
|
||||
}
|
||||
/**
|
||||
* Setup all error handlers for a socket.
|
||||
* @protected
|
||||
*/
|
||||
_setupDefaultErrorHandlers(socket, identifier) {
|
||||
socket.once("error", error => {
|
||||
error.message += ` (${identifier})`;
|
||||
this.closeWithError(error);
|
||||
});
|
||||
socket.once("close", hadError => {
|
||||
if (hadError) {
|
||||
this.closeWithError(new Error(`Socket closed due to transmission error (${identifier})`));
|
||||
}
|
||||
});
|
||||
socket.once("timeout", () => {
|
||||
socket.destroy();
|
||||
this.closeWithError(new Error(`Timeout (${identifier})`));
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Close the control socket. Sends QUIT, then FIN, and ignores any response or error.
|
||||
*/
|
||||
_closeControlSocket() {
|
||||
this._removeSocketListeners(this._socket);
|
||||
this._socket.on("error", doNothing);
|
||||
this.send("QUIT");
|
||||
this._closeSocket(this._socket);
|
||||
}
|
||||
/**
|
||||
* Close a socket, ignores any error.
|
||||
* @protected
|
||||
*/
|
||||
_closeSocket(socket) {
|
||||
if (socket) {
|
||||
this._removeSocketListeners(socket);
|
||||
socket.on("error", doNothing);
|
||||
socket.destroy();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Remove all default listeners for socket.
|
||||
* @protected
|
||||
*/
|
||||
_removeSocketListeners(socket) {
|
||||
socket.removeAllListeners();
|
||||
// Before Node.js 10.3.0, using `socket.removeAllListeners()` without any name did not work: https://github.com/nodejs/node/issues/20923.
|
||||
socket.removeAllListeners("timeout");
|
||||
socket.removeAllListeners("data");
|
||||
socket.removeAllListeners("end");
|
||||
socket.removeAllListeners("error");
|
||||
socket.removeAllListeners("close");
|
||||
socket.removeAllListeners("connect");
|
||||
}
|
||||
/**
|
||||
* Provide a new socket instance.
|
||||
*
|
||||
* Internal use only, replaced for unit tests.
|
||||
*/
|
||||
_newSocket() {
|
||||
return new net_1.Socket();
|
||||
}
|
||||
}
|
||||
exports.FTPContext = FTPContext;
|
||||
46
node_modules/basic-ftp/dist/ProgressTracker.d.ts
generated
vendored
Normal file
46
node_modules/basic-ftp/dist/ProgressTracker.d.ts
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
/// <reference types="node" />
|
||||
import { Socket } from "net";
|
||||
export type ProgressType = "upload" | "download" | "list";
|
||||
/**
|
||||
* Describes progress of file transfer.
|
||||
*/
|
||||
export interface ProgressInfo {
|
||||
/** A name describing this info, e.g. the filename of the transfer. */
|
||||
readonly name: string;
|
||||
/** The type of transfer, typically "upload" or "download". */
|
||||
readonly type: ProgressType;
|
||||
/** Transferred bytes in current transfer. */
|
||||
readonly bytes: number;
|
||||
/** Transferred bytes since last counter reset. Useful for tracking multiple transfers. */
|
||||
readonly bytesOverall: number;
|
||||
}
|
||||
export type ProgressHandler = (info: ProgressInfo) => void;
|
||||
/**
|
||||
* Tracks progress of one socket data transfer at a time.
|
||||
*/
|
||||
export declare class ProgressTracker {
|
||||
bytesOverall: number;
|
||||
protected readonly intervalMs = 500;
|
||||
protected onStop: (stopWithUpdate: boolean) => void;
|
||||
protected onHandle: ProgressHandler;
|
||||
/**
|
||||
* Register a new handler for progress info. Use `undefined` to disable reporting.
|
||||
*/
|
||||
reportTo(onHandle?: ProgressHandler): void;
|
||||
/**
|
||||
* Start tracking transfer progress of a socket.
|
||||
*
|
||||
* @param socket The socket to observe.
|
||||
* @param name A name associated with this progress tracking, e.g. a filename.
|
||||
* @param type The type of the transfer, typically "upload" or "download".
|
||||
*/
|
||||
start(socket: Socket, name: string, type: ProgressType): void;
|
||||
/**
|
||||
* Stop tracking transfer progress.
|
||||
*/
|
||||
stop(): void;
|
||||
/**
|
||||
* Call the progress handler one more time, then stop tracking.
|
||||
*/
|
||||
updateAndStop(): void;
|
||||
}
|
||||
72
node_modules/basic-ftp/dist/ProgressTracker.js
generated
vendored
Normal file
72
node_modules/basic-ftp/dist/ProgressTracker.js
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ProgressTracker = void 0;
|
||||
/**
|
||||
* Tracks progress of one socket data transfer at a time.
|
||||
*/
|
||||
class ProgressTracker {
|
||||
constructor() {
|
||||
this.bytesOverall = 0;
|
||||
this.intervalMs = 500;
|
||||
this.onStop = noop;
|
||||
this.onHandle = noop;
|
||||
}
|
||||
/**
|
||||
* Register a new handler for progress info. Use `undefined` to disable reporting.
|
||||
*/
|
||||
reportTo(onHandle = noop) {
|
||||
this.onHandle = onHandle;
|
||||
}
|
||||
/**
|
||||
* Start tracking transfer progress of a socket.
|
||||
*
|
||||
* @param socket The socket to observe.
|
||||
* @param name A name associated with this progress tracking, e.g. a filename.
|
||||
* @param type The type of the transfer, typically "upload" or "download".
|
||||
*/
|
||||
start(socket, name, type) {
|
||||
let lastBytes = 0;
|
||||
this.onStop = poll(this.intervalMs, () => {
|
||||
const bytes = socket.bytesRead + socket.bytesWritten;
|
||||
this.bytesOverall += bytes - lastBytes;
|
||||
lastBytes = bytes;
|
||||
this.onHandle({
|
||||
name,
|
||||
type,
|
||||
bytes,
|
||||
bytesOverall: this.bytesOverall
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Stop tracking transfer progress.
|
||||
*/
|
||||
stop() {
|
||||
this.onStop(false);
|
||||
}
|
||||
/**
|
||||
* Call the progress handler one more time, then stop tracking.
|
||||
*/
|
||||
updateAndStop() {
|
||||
this.onStop(true);
|
||||
}
|
||||
}
|
||||
exports.ProgressTracker = ProgressTracker;
|
||||
/**
|
||||
* Starts calling a callback function at a regular interval. The first call will go out
|
||||
* immediately. The function returns a function to stop the polling.
|
||||
*/
|
||||
function poll(intervalMs, updateFunc) {
|
||||
const id = setInterval(updateFunc, intervalMs);
|
||||
const stopFunc = (stopWithUpdate) => {
|
||||
clearInterval(id);
|
||||
if (stopWithUpdate) {
|
||||
updateFunc();
|
||||
}
|
||||
// Prevent repeated calls to stop calling handler.
|
||||
updateFunc = noop;
|
||||
};
|
||||
updateFunc();
|
||||
return stopFunc;
|
||||
}
|
||||
function noop() { }
|
||||
1
node_modules/basic-ftp/dist/StringEncoding.d.ts
generated
vendored
Normal file
1
node_modules/basic-ftp/dist/StringEncoding.d.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export type StringEncoding = "utf8" | "ascii" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex" | undefined;
|
||||
2
node_modules/basic-ftp/dist/StringEncoding.js
generated
vendored
Normal file
2
node_modules/basic-ftp/dist/StringEncoding.js
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
9
node_modules/basic-ftp/dist/StringWriter.d.ts
generated
vendored
Normal file
9
node_modules/basic-ftp/dist/StringWriter.d.ts
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/// <reference types="node" />
|
||||
/// <reference types="node" />
|
||||
import { Writable } from "stream";
|
||||
import { StringEncoding } from "./StringEncoding";
|
||||
export declare class StringWriter extends Writable {
|
||||
protected buf: Buffer;
|
||||
_write(chunk: Buffer | string | any, _: string, callback: (error: Error | null) => void): void;
|
||||
getText(encoding: StringEncoding): string;
|
||||
}
|
||||
23
node_modules/basic-ftp/dist/StringWriter.js
generated
vendored
Normal file
23
node_modules/basic-ftp/dist/StringWriter.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.StringWriter = void 0;
|
||||
const stream_1 = require("stream");
|
||||
class StringWriter extends stream_1.Writable {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.buf = Buffer.alloc(0);
|
||||
}
|
||||
_write(chunk, _, callback) {
|
||||
if (chunk instanceof Buffer) {
|
||||
this.buf = Buffer.concat([this.buf, chunk]);
|
||||
callback(null);
|
||||
}
|
||||
else {
|
||||
callback(new Error("StringWriter expects chunks of type 'Buffer'."));
|
||||
}
|
||||
}
|
||||
getText(encoding) {
|
||||
return this.buf.toString(encoding);
|
||||
}
|
||||
}
|
||||
exports.StringWriter = StringWriter;
|
||||
9
node_modules/basic-ftp/dist/index.d.ts
generated
vendored
Normal file
9
node_modules/basic-ftp/dist/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Public API
|
||||
*/
|
||||
export * from "./Client";
|
||||
export * from "./FtpContext";
|
||||
export * from "./FileInfo";
|
||||
export * from "./parseList";
|
||||
export * from "./StringEncoding";
|
||||
export { enterPassiveModeIPv4, enterPassiveModeIPv6 } from "./transfer";
|
||||
28
node_modules/basic-ftp/dist/index.js
generated
vendored
Normal file
28
node_modules/basic-ftp/dist/index.js
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
||||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.enterPassiveModeIPv6 = exports.enterPassiveModeIPv4 = void 0;
|
||||
/**
|
||||
* Public API
|
||||
*/
|
||||
__exportStar(require("./Client"), exports);
|
||||
__exportStar(require("./FtpContext"), exports);
|
||||
__exportStar(require("./FileInfo"), exports);
|
||||
__exportStar(require("./parseList"), exports);
|
||||
__exportStar(require("./StringEncoding"), exports);
|
||||
var transfer_1 = require("./transfer");
|
||||
Object.defineProperty(exports, "enterPassiveModeIPv4", { enumerable: true, get: function () { return transfer_1.enterPassiveModeIPv4; } });
|
||||
Object.defineProperty(exports, "enterPassiveModeIPv6", { enumerable: true, get: function () { return transfer_1.enterPassiveModeIPv6; } });
|
||||
23
node_modules/basic-ftp/dist/netUtils.d.ts
generated
vendored
Normal file
23
node_modules/basic-ftp/dist/netUtils.d.ts
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/// <reference types="node" />
|
||||
/// <reference types="node" />
|
||||
import { Socket } from "net";
|
||||
import { ConnectionOptions, TLSSocket } from "tls";
|
||||
/**
|
||||
* Returns a string describing the encryption on a given socket instance.
|
||||
*/
|
||||
export declare function describeTLS(socket: Socket | TLSSocket): string;
|
||||
/**
|
||||
* Returns a string describing the remote address of a socket.
|
||||
*/
|
||||
export declare function describeAddress(socket: Socket): string;
|
||||
/**
|
||||
* Upgrade a socket connection with TLS.
|
||||
*/
|
||||
export declare function upgradeSocket(socket: Socket, options: ConnectionOptions): Promise<TLSSocket>;
|
||||
/**
|
||||
* Returns true if an IP is a private address according to https://tools.ietf.org/html/rfc1918#section-3.
|
||||
* This will handle IPv4-mapped IPv6 addresses correctly but return false for all other IPv6 addresses.
|
||||
*
|
||||
* @param ip The IP as a string, e.g. "192.168.0.1"
|
||||
*/
|
||||
export declare function ipIsPrivateV4Address(ip?: string): boolean;
|
||||
67
node_modules/basic-ftp/dist/netUtils.js
generated
vendored
Normal file
67
node_modules/basic-ftp/dist/netUtils.js
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ipIsPrivateV4Address = exports.upgradeSocket = exports.describeAddress = exports.describeTLS = void 0;
|
||||
const tls_1 = require("tls");
|
||||
/**
|
||||
* Returns a string describing the encryption on a given socket instance.
|
||||
*/
|
||||
function describeTLS(socket) {
|
||||
if (socket instanceof tls_1.TLSSocket) {
|
||||
const protocol = socket.getProtocol();
|
||||
return protocol ? protocol : "Server socket or disconnected client socket";
|
||||
}
|
||||
return "No encryption";
|
||||
}
|
||||
exports.describeTLS = describeTLS;
|
||||
/**
|
||||
* Returns a string describing the remote address of a socket.
|
||||
*/
|
||||
function describeAddress(socket) {
|
||||
if (socket.remoteFamily === "IPv6") {
|
||||
return `[${socket.remoteAddress}]:${socket.remotePort}`;
|
||||
}
|
||||
return `${socket.remoteAddress}:${socket.remotePort}`;
|
||||
}
|
||||
exports.describeAddress = describeAddress;
|
||||
/**
|
||||
* Upgrade a socket connection with TLS.
|
||||
*/
|
||||
function upgradeSocket(socket, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tlsOptions = Object.assign({}, options, {
|
||||
socket
|
||||
});
|
||||
const tlsSocket = (0, tls_1.connect)(tlsOptions, () => {
|
||||
const expectCertificate = tlsOptions.rejectUnauthorized !== false;
|
||||
if (expectCertificate && !tlsSocket.authorized) {
|
||||
reject(tlsSocket.authorizationError);
|
||||
}
|
||||
else {
|
||||
// Remove error listener added below.
|
||||
tlsSocket.removeAllListeners("error");
|
||||
resolve(tlsSocket);
|
||||
}
|
||||
}).once("error", error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
exports.upgradeSocket = upgradeSocket;
|
||||
/**
|
||||
* Returns true if an IP is a private address according to https://tools.ietf.org/html/rfc1918#section-3.
|
||||
* This will handle IPv4-mapped IPv6 addresses correctly but return false for all other IPv6 addresses.
|
||||
*
|
||||
* @param ip The IP as a string, e.g. "192.168.0.1"
|
||||
*/
|
||||
function ipIsPrivateV4Address(ip = "") {
|
||||
// Handle IPv4-mapped IPv6 addresses like ::ffff:192.168.0.1
|
||||
if (ip.startsWith("::ffff:")) {
|
||||
ip = ip.substr(7); // Strip ::ffff: prefix
|
||||
}
|
||||
const octets = ip.split(".").map(o => parseInt(o, 10));
|
||||
return octets[0] === 10 // 10.0.0.0 - 10.255.255.255
|
||||
|| (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) // 172.16.0.0 - 172.31.255.255
|
||||
|| (octets[0] === 192 && octets[1] === 168) // 192.168.0.0 - 192.168.255.255
|
||||
|| ip === "127.0.0.1";
|
||||
}
|
||||
exports.ipIsPrivateV4Address = ipIsPrivateV4Address;
|
||||
22
node_modules/basic-ftp/dist/parseControlResponse.d.ts
generated
vendored
Normal file
22
node_modules/basic-ftp/dist/parseControlResponse.d.ts
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
export interface ParsedResponse {
|
||||
readonly messages: string[];
|
||||
readonly rest: string;
|
||||
}
|
||||
/**
|
||||
* Parse an FTP control response as a collection of messages. A message is a complete
|
||||
* single- or multiline response. A response can also contain multiple multiline responses
|
||||
* that will each be represented by a message. A response can also be incomplete
|
||||
* and be completed on the next incoming data chunk for which case this function also
|
||||
* describes a `rest`. This function converts all CRLF to LF.
|
||||
*/
|
||||
export declare function parseControlResponse(text: string): ParsedResponse;
|
||||
export declare function isSingleLine(line: string): boolean;
|
||||
export declare function isMultiline(line: string): boolean;
|
||||
/**
|
||||
* Return true if an FTP return code describes a positive completion.
|
||||
*/
|
||||
export declare function positiveCompletion(code: number): boolean;
|
||||
/**
|
||||
* Return true if an FTP return code describes a positive intermediate response.
|
||||
*/
|
||||
export declare function positiveIntermediate(code: number): boolean;
|
||||
67
node_modules/basic-ftp/dist/parseControlResponse.js
generated
vendored
Normal file
67
node_modules/basic-ftp/dist/parseControlResponse.js
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.positiveIntermediate = exports.positiveCompletion = exports.isMultiline = exports.isSingleLine = exports.parseControlResponse = void 0;
|
||||
const LF = "\n";
|
||||
/**
|
||||
* Parse an FTP control response as a collection of messages. A message is a complete
|
||||
* single- or multiline response. A response can also contain multiple multiline responses
|
||||
* that will each be represented by a message. A response can also be incomplete
|
||||
* and be completed on the next incoming data chunk for which case this function also
|
||||
* describes a `rest`. This function converts all CRLF to LF.
|
||||
*/
|
||||
function parseControlResponse(text) {
|
||||
const lines = text.split(/\r?\n/).filter(isNotBlank);
|
||||
const messages = [];
|
||||
let startAt = 0;
|
||||
let tokenRegex;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
// No group has been opened.
|
||||
if (!tokenRegex) {
|
||||
if (isMultiline(line)) {
|
||||
// Open a group by setting an expected token.
|
||||
const token = line.substr(0, 3);
|
||||
tokenRegex = new RegExp(`^${token}(?:$| )`);
|
||||
startAt = i;
|
||||
}
|
||||
else if (isSingleLine(line)) {
|
||||
// Single lines can be grouped immediately.
|
||||
messages.push(line);
|
||||
}
|
||||
}
|
||||
// Group has been opened, expect closing token.
|
||||
else if (tokenRegex.test(line)) {
|
||||
tokenRegex = undefined;
|
||||
messages.push(lines.slice(startAt, i + 1).join(LF));
|
||||
}
|
||||
}
|
||||
// The last group might not have been closed, report it as a rest.
|
||||
const rest = tokenRegex ? lines.slice(startAt).join(LF) + LF : "";
|
||||
return { messages, rest };
|
||||
}
|
||||
exports.parseControlResponse = parseControlResponse;
|
||||
function isSingleLine(line) {
|
||||
return /^\d\d\d(?:$| )/.test(line);
|
||||
}
|
||||
exports.isSingleLine = isSingleLine;
|
||||
function isMultiline(line) {
|
||||
return /^\d\d\d-/.test(line);
|
||||
}
|
||||
exports.isMultiline = isMultiline;
|
||||
/**
|
||||
* Return true if an FTP return code describes a positive completion.
|
||||
*/
|
||||
function positiveCompletion(code) {
|
||||
return code >= 200 && code < 300;
|
||||
}
|
||||
exports.positiveCompletion = positiveCompletion;
|
||||
/**
|
||||
* Return true if an FTP return code describes a positive intermediate response.
|
||||
*/
|
||||
function positiveIntermediate(code) {
|
||||
return code >= 300 && code < 400;
|
||||
}
|
||||
exports.positiveIntermediate = positiveIntermediate;
|
||||
function isNotBlank(str) {
|
||||
return str.trim() !== "";
|
||||
}
|
||||
5
node_modules/basic-ftp/dist/parseList.d.ts
generated
vendored
Normal file
5
node_modules/basic-ftp/dist/parseList.d.ts
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import { FileInfo } from "./FileInfo";
|
||||
/**
|
||||
* Parse raw directory listing.
|
||||
*/
|
||||
export declare function parseList(rawList: string): FileInfo[];
|
||||
70
node_modules/basic-ftp/dist/parseList.js
generated
vendored
Normal file
70
node_modules/basic-ftp/dist/parseList.js
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.parseList = void 0;
|
||||
const dosParser = __importStar(require("./parseListDOS"));
|
||||
const unixParser = __importStar(require("./parseListUnix"));
|
||||
const mlsdParser = __importStar(require("./parseListMLSD"));
|
||||
/**
|
||||
* Available directory listing parsers. These are candidates that will be tested
|
||||
* in the order presented. The first candidate will be used to parse the whole list.
|
||||
*/
|
||||
const availableParsers = [
|
||||
dosParser,
|
||||
unixParser,
|
||||
mlsdParser // Keep MLSD last, may accept filename only
|
||||
];
|
||||
function firstCompatibleParser(line, parsers) {
|
||||
return parsers.find(parser => parser.testLine(line) === true);
|
||||
}
|
||||
function isNotBlank(str) {
|
||||
return str.trim() !== "";
|
||||
}
|
||||
function isNotMeta(str) {
|
||||
return !str.startsWith("total");
|
||||
}
|
||||
const REGEX_NEWLINE = /\r?\n/;
|
||||
/**
|
||||
* Parse raw directory listing.
|
||||
*/
|
||||
function parseList(rawList) {
|
||||
const lines = rawList
|
||||
.split(REGEX_NEWLINE)
|
||||
.filter(isNotBlank)
|
||||
.filter(isNotMeta);
|
||||
if (lines.length === 0) {
|
||||
return [];
|
||||
}
|
||||
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.");
|
||||
}
|
||||
const files = lines
|
||||
.map(parser.parseLine)
|
||||
.filter((info) => info !== undefined);
|
||||
return parser.transformList(files);
|
||||
}
|
||||
exports.parseList = parseList;
|
||||
12
node_modules/basic-ftp/dist/parseListDOS.d.ts
generated
vendored
Normal file
12
node_modules/basic-ftp/dist/parseListDOS.d.ts
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import { FileInfo } from "./FileInfo";
|
||||
/**
|
||||
* Returns true if a given line might be a DOS-style listing.
|
||||
*
|
||||
* - Example: `12-05-96 05:03PM <DIR> myDir`
|
||||
*/
|
||||
export declare function testLine(line: string): boolean;
|
||||
/**
|
||||
* Parse a single line of a DOS-style directory listing.
|
||||
*/
|
||||
export declare function parseLine(line: string): FileInfo | undefined;
|
||||
export declare function transformList(files: FileInfo[]): FileInfo[];
|
||||
53
node_modules/basic-ftp/dist/parseListDOS.js
generated
vendored
Normal file
53
node_modules/basic-ftp/dist/parseListDOS.js
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.transformList = exports.parseLine = exports.testLine = void 0;
|
||||
const FileInfo_1 = require("./FileInfo");
|
||||
/**
|
||||
* This parser is based on the FTP client library source code in Apache Commons Net provided
|
||||
* under the Apache 2.0 license. It has been simplified and rewritten to better fit the Javascript language.
|
||||
*
|
||||
* https://github.com/apache/commons-net/blob/master/src/main/java/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java
|
||||
*/
|
||||
const RE_LINE = new RegExp("(\\S+)\\s+(\\S+)\\s+" // MM-dd-yy whitespace hh:mma|kk:mm swallow trailing spaces
|
||||
+ "(?:(<DIR>)|([0-9]+))\\s+" // <DIR> or ddddd swallow trailing spaces
|
||||
+ "(\\S.*)" // First non-space followed by rest of line (name)
|
||||
);
|
||||
/**
|
||||
* Returns true if a given line might be a DOS-style listing.
|
||||
*
|
||||
* - Example: `12-05-96 05:03PM <DIR> myDir`
|
||||
*/
|
||||
function testLine(line) {
|
||||
return /^\d{2}/.test(line) && RE_LINE.test(line);
|
||||
}
|
||||
exports.testLine = testLine;
|
||||
/**
|
||||
* Parse a single line of a DOS-style directory listing.
|
||||
*/
|
||||
function parseLine(line) {
|
||||
const groups = line.match(RE_LINE);
|
||||
if (groups === null) {
|
||||
return undefined;
|
||||
}
|
||||
const name = groups[5];
|
||||
if (name === "." || name === "..") { // Ignore parent directory links
|
||||
return undefined;
|
||||
}
|
||||
const file = new FileInfo_1.FileInfo(name);
|
||||
const fileType = groups[3];
|
||||
if (fileType === "<DIR>") {
|
||||
file.type = FileInfo_1.FileType.Directory;
|
||||
file.size = 0;
|
||||
}
|
||||
else {
|
||||
file.type = FileInfo_1.FileType.File;
|
||||
file.size = parseInt(groups[4], 10);
|
||||
}
|
||||
file.rawModifiedAt = groups[1] + " " + groups[2];
|
||||
return file;
|
||||
}
|
||||
exports.parseLine = parseLine;
|
||||
function transformList(files) {
|
||||
return files;
|
||||
}
|
||||
exports.transformList = transformList;
|
||||
20
node_modules/basic-ftp/dist/parseListMLSD.d.ts
generated
vendored
Normal file
20
node_modules/basic-ftp/dist/parseListMLSD.d.ts
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import { FileInfo } from "./FileInfo";
|
||||
/**
|
||||
* Returns true if a given line might be part of an MLSD listing.
|
||||
*
|
||||
* - Example 1: `size=15227;type=dir;perm=el;modify=20190419065730; test one`
|
||||
* - Example 2: ` file name` (leading space)
|
||||
*/
|
||||
export declare function testLine(line: string): boolean;
|
||||
/**
|
||||
* Parse single line as MLSD listing, see specification at https://tools.ietf.org/html/rfc3659#section-7.
|
||||
*/
|
||||
export declare function parseLine(line: string): FileInfo | undefined;
|
||||
export declare function transformList(files: FileInfo[]): FileInfo[];
|
||||
/**
|
||||
* Parse date as specified in https://tools.ietf.org/html/rfc3659#section-2.3.
|
||||
*
|
||||
* Message contains response code and modified time in the format: YYYYMMDDHHMMSS[.sss]
|
||||
* For example `19991005213102` or `19980615100045.014`.
|
||||
*/
|
||||
export declare function parseMLSxDate(fact: string): Date;
|
||||
188
node_modules/basic-ftp/dist/parseListMLSD.js
generated
vendored
Normal file
188
node_modules/basic-ftp/dist/parseListMLSD.js
generated
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.parseMLSxDate = exports.transformList = exports.parseLine = exports.testLine = void 0;
|
||||
const FileInfo_1 = require("./FileInfo");
|
||||
function parseSize(value, info) {
|
||||
info.size = parseInt(value, 10);
|
||||
}
|
||||
/**
|
||||
* Parsers for MLSD facts.
|
||||
*/
|
||||
const factHandlersByName = {
|
||||
"size": parseSize, // File size
|
||||
"sizd": parseSize, // Directory size
|
||||
"unique": (value, info) => {
|
||||
info.uniqueID = value;
|
||||
},
|
||||
"modify": (value, info) => {
|
||||
info.modifiedAt = parseMLSxDate(value);
|
||||
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 1 /* FactHandlerResult.Continue */;
|
||||
}
|
||||
switch (value) {
|
||||
case "file":
|
||||
info.type = FileInfo_1.FileType.File;
|
||||
break;
|
||||
case "dir":
|
||||
info.type = FileInfo_1.FileType.Directory;
|
||||
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
|
||||
case "pdir": // Parent directory
|
||||
return 2 /* FactHandlerResult.IgnoreFile */; // Don't include these entries in the listing
|
||||
default:
|
||||
info.type = FileInfo_1.FileType.Unknown;
|
||||
}
|
||||
return 1 /* FactHandlerResult.Continue */;
|
||||
},
|
||||
"unix.mode": (value, info) => {
|
||||
const digits = value.substr(-3);
|
||||
info.permissions = {
|
||||
user: parseInt(digits[0], 10),
|
||||
group: parseInt(digits[1], 10),
|
||||
world: parseInt(digits[2], 10)
|
||||
};
|
||||
},
|
||||
"unix.ownername": (value, info) => {
|
||||
info.user = value;
|
||||
},
|
||||
"unix.owner": (value, info) => {
|
||||
if (info.user === undefined)
|
||||
info.user = value;
|
||||
},
|
||||
get "unix.uid"() {
|
||||
return this["unix.owner"];
|
||||
},
|
||||
"unix.groupname": (value, info) => {
|
||||
info.group = value;
|
||||
},
|
||||
"unix.group": (value, info) => {
|
||||
if (info.group === undefined)
|
||||
info.group = value;
|
||||
},
|
||||
get "unix.gid"() {
|
||||
return this["unix.group"];
|
||||
}
|
||||
// Regarding the fact "perm":
|
||||
// We don't handle permission information stored in "perm" because its information is conceptually
|
||||
// different from what users of FTP clients usually associate with "permissions". Those that have
|
||||
// some expectations (and probably want to edit them with a SITE command) often unknowingly expect
|
||||
// the Unix permission system. The information passed by "perm" describes what FTP commands can be
|
||||
// executed with a file/directory. But even this can be either incomplete or just meant as a "guide"
|
||||
// as the spec mentions. From https://tools.ietf.org/html/rfc3659#section-7.5.5: "The permissions are
|
||||
// described here as they apply to FTP commands. They may not map easily into particular permissions
|
||||
// available on the server's operating system." The parser by Apache Commons tries to translate these
|
||||
// to Unix permissions – this is misleading users and might not even be correct.
|
||||
};
|
||||
/**
|
||||
* Split a string once at the first position of a delimiter. For example
|
||||
* `splitStringOnce("a b c d", " ")` returns `["a", "b c d"]`.
|
||||
*/
|
||||
function splitStringOnce(str, delimiter) {
|
||||
const pos = str.indexOf(delimiter);
|
||||
const a = str.substr(0, pos);
|
||||
const b = str.substr(pos + delimiter.length);
|
||||
return [a, b];
|
||||
}
|
||||
/**
|
||||
* Returns true if a given line might be part of an MLSD listing.
|
||||
*
|
||||
* - Example 1: `size=15227;type=dir;perm=el;modify=20190419065730; test one`
|
||||
* - Example 2: ` file name` (leading space)
|
||||
*/
|
||||
function testLine(line) {
|
||||
return /^\S+=\S+;/.test(line) || line.startsWith(" ");
|
||||
}
|
||||
exports.testLine = testLine;
|
||||
/**
|
||||
* Parse single line as MLSD listing, see specification at https://tools.ietf.org/html/rfc3659#section-7.
|
||||
*/
|
||||
function parseLine(line) {
|
||||
const [packedFacts, name] = splitStringOnce(line, " ");
|
||||
if (name === "" || name === "." || name === "..") {
|
||||
return undefined;
|
||||
}
|
||||
const info = new FileInfo_1.FileInfo(name);
|
||||
const facts = packedFacts.split(";");
|
||||
for (const fact of facts) {
|
||||
const [factName, factValue] = splitStringOnce(fact, "=");
|
||||
if (!factValue) {
|
||||
continue;
|
||||
}
|
||||
const factHandler = factHandlersByName[factName.toLowerCase()];
|
||||
if (!factHandler) {
|
||||
continue;
|
||||
}
|
||||
const result = factHandler(factValue, info);
|
||||
if (result === 2 /* FactHandlerResult.IgnoreFile */) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
exports.parseLine = parseLine;
|
||||
function transformList(files) {
|
||||
// Create a map of all files that are not symbolic links by their unique ID
|
||||
const nonLinksByID = new Map();
|
||||
for (const file of files) {
|
||||
if (!file.isSymbolicLink && file.uniqueID !== undefined) {
|
||||
nonLinksByID.set(file.uniqueID, file);
|
||||
}
|
||||
}
|
||||
const resolvedFiles = [];
|
||||
for (const file of files) {
|
||||
// Try to associate unresolved symbolic links with a target file/directory.
|
||||
if (file.isSymbolicLink && file.uniqueID !== undefined && file.link === undefined) {
|
||||
const target = nonLinksByID.get(file.uniqueID);
|
||||
if (target !== undefined) {
|
||||
file.link = target.name;
|
||||
}
|
||||
}
|
||||
// The target of a symbolic link is listed as an entry 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 generally don't want these kind of entries at all.
|
||||
const isPartOfDirectory = !file.name.includes("/");
|
||||
if (isPartOfDirectory) {
|
||||
resolvedFiles.push(file);
|
||||
}
|
||||
}
|
||||
return resolvedFiles;
|
||||
}
|
||||
exports.transformList = transformList;
|
||||
/**
|
||||
* Parse date as specified in https://tools.ietf.org/html/rfc3659#section-2.3.
|
||||
*
|
||||
* Message contains response code and modified time in the format: YYYYMMDDHHMMSS[.sss]
|
||||
* For example `19991005213102` or `19980615100045.014`.
|
||||
*/
|
||||
function parseMLSxDate(fact) {
|
||||
return new Date(Date.UTC(+fact.slice(0, 4), // Year
|
||||
+fact.slice(4, 6) - 1, // Month
|
||||
+fact.slice(6, 8), // Date
|
||||
+fact.slice(8, 10), // Hours
|
||||
+fact.slice(10, 12), // Minutes
|
||||
+fact.slice(12, 14), // Seconds
|
||||
+fact.slice(15, 18) // Milliseconds
|
||||
));
|
||||
}
|
||||
exports.parseMLSxDate = parseMLSxDate;
|
||||
12
node_modules/basic-ftp/dist/parseListUnix.d.ts
generated
vendored
Normal file
12
node_modules/basic-ftp/dist/parseListUnix.d.ts
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import { FileInfo } from "./FileInfo";
|
||||
/**
|
||||
* Returns true if a given line might be a Unix-style listing.
|
||||
*
|
||||
* - Example: `-rw-r--r--+ 1 patrick staff 1057 Dec 11 14:35 test.txt`
|
||||
*/
|
||||
export declare function testLine(line: string): boolean;
|
||||
/**
|
||||
* Parse a single line of a Unix-style directory listing.
|
||||
*/
|
||||
export declare function parseLine(line: string): FileInfo | undefined;
|
||||
export declare function transformList(files: FileInfo[]): FileInfo[];
|
||||
156
node_modules/basic-ftp/dist/parseListUnix.js
generated
vendored
Normal file
156
node_modules/basic-ftp/dist/parseListUnix.js
generated
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.transformList = exports.parseLine = exports.testLine = void 0;
|
||||
const FileInfo_1 = require("./FileInfo");
|
||||
const JA_MONTH = "\u6708";
|
||||
const JA_DAY = "\u65e5";
|
||||
const JA_YEAR = "\u5e74";
|
||||
/**
|
||||
* This parser is based on the FTP client library source code in Apache Commons Net provided
|
||||
* under the Apache 2.0 license. It has been simplified and rewritten to better fit the Javascript language.
|
||||
*
|
||||
* https://github.com/apache/commons-net/blob/master/src/main/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java
|
||||
*
|
||||
* Below is the regular expression used by this parser.
|
||||
*
|
||||
* Permissions:
|
||||
* r the file is readable
|
||||
* w the file is writable
|
||||
* x the file is executable
|
||||
* - the indicated permission is not granted
|
||||
* L mandatory locking occurs during access (the set-group-ID bit is
|
||||
* on and the group execution bit is off)
|
||||
* s the set-user-ID or set-group-ID bit is on, and the corresponding
|
||||
* user or group execution bit is also on
|
||||
* S undefined bit-state (the set-user-ID bit is on and the user
|
||||
* execution bit is off)
|
||||
* t the 1000 (octal) bit, or sticky bit, is on [see chmod(1)], and
|
||||
* execution is on
|
||||
* T the 1000 bit is turned on, and execution is off (undefined bit-
|
||||
* state)
|
||||
* e z/OS external link bit
|
||||
* Final letter may be appended:
|
||||
* + file has extended security attributes (e.g. ACL)
|
||||
* Note: local listings on MacOSX also use '@'
|
||||
* this is not allowed for here as does not appear to be shown by FTP servers
|
||||
* {@code @} file has extended attributes
|
||||
*/
|
||||
const RE_LINE = new RegExp("([bcdelfmpSs-])" // file type
|
||||
+ "(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]?)))\\+?" // permissions
|
||||
+ "\\s*" // separator TODO why allow it to be omitted??
|
||||
+ "(\\d+)" // link count
|
||||
+ "\\s+" // separator
|
||||
+ "(?:(\\S+(?:\\s\\S+)*?)\\s+)?" // owner name (optional spaces)
|
||||
+ "(?:(\\S+(?:\\s\\S+)*)\\s+)?" // group name (optional spaces)
|
||||
+ "(\\d+(?:,\\s*\\d+)?)" // size or n,m
|
||||
+ "\\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.
|
||||
* Japanese uses numeric day and month with suffixes to distinguish them
|
||||
* [d]dXX [d]dZZ
|
||||
*/
|
||||
+ "(" +
|
||||
"(?:\\d+[-/]\\d+[-/]\\d+)" + // yyyy-mm-dd
|
||||
"|(?:\\S{3}\\s+\\d{1,2})" + // MMM [d]d
|
||||
"|(?:\\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
|
||||
* or Japanese year - yyyyXX
|
||||
*/
|
||||
+ "((?:\\d+(?::\\d+)?)|(?:\\d{4}" + JA_YEAR + "))" // (20)
|
||||
+ "\\s" // separator
|
||||
+ "(.*)"); // the rest (21)
|
||||
/**
|
||||
* Returns true if a given line might be a Unix-style listing.
|
||||
*
|
||||
* - Example: `-rw-r--r--+ 1 patrick staff 1057 Dec 11 14:35 test.txt`
|
||||
*/
|
||||
function testLine(line) {
|
||||
return RE_LINE.test(line);
|
||||
}
|
||||
exports.testLine = testLine;
|
||||
/**
|
||||
* Parse a single line of a Unix-style directory listing.
|
||||
*/
|
||||
function parseLine(line) {
|
||||
const groups = line.match(RE_LINE);
|
||||
if (groups === null) {
|
||||
return undefined;
|
||||
}
|
||||
const name = groups[21];
|
||||
if (name === "." || name === "..") { // Ignore parent directory links
|
||||
return undefined;
|
||||
}
|
||||
const file = new FileInfo_1.FileInfo(name);
|
||||
file.size = parseInt(groups[18], 10);
|
||||
file.user = groups[16];
|
||||
file.group = groups[17];
|
||||
file.hardLinkCount = parseInt(groups[15], 10);
|
||||
file.rawModifiedAt = groups[19] + " " + groups[20];
|
||||
file.permissions = {
|
||||
user: parseMode(groups[4], groups[5], groups[6]),
|
||||
group: parseMode(groups[8], groups[9], groups[10]),
|
||||
world: parseMode(groups[12], groups[13], groups[14]),
|
||||
};
|
||||
// Set file type
|
||||
switch (groups[1].charAt(0)) {
|
||||
case "d":
|
||||
file.type = FileInfo_1.FileType.Directory;
|
||||
break;
|
||||
case "e": // NET-39 => z/OS external link
|
||||
file.type = FileInfo_1.FileType.SymbolicLink;
|
||||
break;
|
||||
case "l":
|
||||
file.type = FileInfo_1.FileType.SymbolicLink;
|
||||
break;
|
||||
case "b":
|
||||
case "c":
|
||||
file.type = FileInfo_1.FileType.File; // TODO change this if DEVICE_TYPE implemented
|
||||
break;
|
||||
case "f":
|
||||
case "-":
|
||||
file.type = FileInfo_1.FileType.File;
|
||||
break;
|
||||
default:
|
||||
// A 'whiteout' file is an ARTIFICIAL entry in any of several types of
|
||||
// 'translucent' filesystems, of which a 'union' filesystem is one.
|
||||
file.type = FileInfo_1.FileType.Unknown;
|
||||
}
|
||||
// Separate out the link name for symbolic links
|
||||
if (file.isSymbolicLink) {
|
||||
const end = name.indexOf(" -> ");
|
||||
if (end !== -1) {
|
||||
file.name = name.substring(0, end);
|
||||
file.link = name.substring(end + 4);
|
||||
}
|
||||
}
|
||||
return file;
|
||||
}
|
||||
exports.parseLine = parseLine;
|
||||
function transformList(files) {
|
||||
return files;
|
||||
}
|
||||
exports.transformList = transformList;
|
||||
function parseMode(r, w, x) {
|
||||
let value = 0;
|
||||
if (r !== "-") {
|
||||
value += FileInfo_1.FileInfo.UnixPermission.Read;
|
||||
}
|
||||
if (w !== "-") {
|
||||
value += FileInfo_1.FileInfo.UnixPermission.Write;
|
||||
}
|
||||
const execToken = x.charAt(0);
|
||||
if (execToken !== "-" && execToken.toUpperCase() !== execToken) {
|
||||
value += FileInfo_1.FileInfo.UnixPermission.Execute;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
34
node_modules/basic-ftp/dist/transfer.d.ts
generated
vendored
Normal file
34
node_modules/basic-ftp/dist/transfer.d.ts
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/// <reference types="node" />
|
||||
import { Writable, Readable } from "stream";
|
||||
import { FTPContext, FTPResponse } from "./FtpContext";
|
||||
import { ProgressTracker, ProgressType } from "./ProgressTracker";
|
||||
export type UploadCommand = "STOR" | "APPE";
|
||||
/**
|
||||
* Prepare a data socket using passive mode over IPv6.
|
||||
*/
|
||||
export declare function enterPassiveModeIPv6(ftp: FTPContext): Promise<FTPResponse>;
|
||||
/**
|
||||
* Parse an EPSV response. Returns only the port as in EPSV the host of the control connection is used.
|
||||
*/
|
||||
export declare function parseEpsvResponse(message: string): number;
|
||||
/**
|
||||
* Prepare a data socket using passive mode over IPv4.
|
||||
*/
|
||||
export declare function enterPassiveModeIPv4(ftp: FTPContext): Promise<FTPResponse>;
|
||||
/**
|
||||
* Parse a PASV response.
|
||||
*/
|
||||
export declare function parsePasvResponse(message: string): {
|
||||
host: string;
|
||||
port: number;
|
||||
};
|
||||
export declare function connectForPassiveTransfer(host: string, port: number, ftp: FTPContext): Promise<void>;
|
||||
export interface TransferConfig {
|
||||
command: string;
|
||||
remotePath: string;
|
||||
type: ProgressType;
|
||||
ftp: FTPContext;
|
||||
tracker: ProgressTracker;
|
||||
}
|
||||
export declare function uploadFrom(source: Readable, config: TransferConfig): Promise<FTPResponse>;
|
||||
export declare function downloadTo(destination: Writable, config: TransferConfig): Promise<FTPResponse>;
|
||||
300
node_modules/basic-ftp/dist/transfer.js
generated
vendored
Normal file
300
node_modules/basic-ftp/dist/transfer.js
generated
vendored
Normal file
@@ -0,0 +1,300 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.downloadTo = exports.uploadFrom = exports.connectForPassiveTransfer = exports.parsePasvResponse = exports.enterPassiveModeIPv4 = exports.parseEpsvResponse = exports.enterPassiveModeIPv6 = void 0;
|
||||
const netUtils_1 = require("./netUtils");
|
||||
const stream_1 = require("stream");
|
||||
const tls_1 = require("tls");
|
||||
const parseControlResponse_1 = require("./parseControlResponse");
|
||||
/**
|
||||
* Prepare a data socket using passive mode over IPv6.
|
||||
*/
|
||||
async function enterPassiveModeIPv6(ftp) {
|
||||
const res = await ftp.request("EPSV");
|
||||
const port = parseEpsvResponse(res.message);
|
||||
if (!port) {
|
||||
throw new Error("Can't parse EPSV response: " + res.message);
|
||||
}
|
||||
const controlHost = ftp.socket.remoteAddress;
|
||||
if (controlHost === undefined) {
|
||||
throw new Error("Control socket is disconnected, can't get remote address.");
|
||||
}
|
||||
await connectForPassiveTransfer(controlHost, port, ftp);
|
||||
return res;
|
||||
}
|
||||
exports.enterPassiveModeIPv6 = enterPassiveModeIPv6;
|
||||
/**
|
||||
* Parse an EPSV response. Returns only the port as in EPSV the host of the control connection is used.
|
||||
*/
|
||||
function parseEpsvResponse(message) {
|
||||
// Get port from EPSV response, e.g. "229 Entering Extended Passive Mode (|||6446|)"
|
||||
// Some FTP Servers such as the one on IBM i (OS/400) use ! instead of | in their EPSV response.
|
||||
const groups = message.match(/[|!]{3}(.+)[|!]/);
|
||||
if (groups === null || groups[1] === undefined) {
|
||||
throw new Error(`Can't parse response to 'EPSV': ${message}`);
|
||||
}
|
||||
const port = parseInt(groups[1], 10);
|
||||
if (Number.isNaN(port)) {
|
||||
throw new Error(`Can't parse response to 'EPSV', port is not a number: ${message}`);
|
||||
}
|
||||
return port;
|
||||
}
|
||||
exports.parseEpsvResponse = parseEpsvResponse;
|
||||
/**
|
||||
* Prepare a data socket using passive mode over IPv4.
|
||||
*/
|
||||
async function enterPassiveModeIPv4(ftp) {
|
||||
const res = await ftp.request("PASV");
|
||||
const target = parsePasvResponse(res.message);
|
||||
if (!target) {
|
||||
throw new Error("Can't parse PASV response: " + res.message);
|
||||
}
|
||||
// If the host in the PASV response has a local address while the control connection hasn't,
|
||||
// we assume a NAT issue and use the IP of the control connection as the target for the data connection.
|
||||
// We can't always perform this replacement because it's possible (although unlikely) that the FTP server
|
||||
// indeed uses a different host for data connections.
|
||||
const controlHost = ftp.socket.remoteAddress;
|
||||
if ((0, netUtils_1.ipIsPrivateV4Address)(target.host) && controlHost && !(0, netUtils_1.ipIsPrivateV4Address)(controlHost)) {
|
||||
target.host = controlHost;
|
||||
}
|
||||
await connectForPassiveTransfer(target.host, target.port, ftp);
|
||||
return res;
|
||||
}
|
||||
exports.enterPassiveModeIPv4 = enterPassiveModeIPv4;
|
||||
/**
|
||||
* Parse a PASV response.
|
||||
*/
|
||||
function parsePasvResponse(message) {
|
||||
// Get host and port from PASV response, e.g. "227 Entering Passive Mode (192,168,1,100,10,229)"
|
||||
const groups = message.match(/([-\d]+,[-\d]+,[-\d]+,[-\d]+),([-\d]+),([-\d]+)/);
|
||||
if (groups === null || groups.length !== 4) {
|
||||
throw new Error(`Can't parse response to 'PASV': ${message}`);
|
||||
}
|
||||
return {
|
||||
host: groups[1].replace(/,/g, "."),
|
||||
port: (parseInt(groups[2], 10) & 255) * 256 + (parseInt(groups[3], 10) & 255)
|
||||
};
|
||||
}
|
||||
exports.parsePasvResponse = parsePasvResponse;
|
||||
function connectForPassiveTransfer(host, port, ftp) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let socket = ftp._newSocket();
|
||||
const handleConnErr = function (err) {
|
||||
err.message = "Can't open data connection in passive mode: " + err.message;
|
||||
reject(err);
|
||||
};
|
||||
const handleTimeout = function () {
|
||||
socket.destroy();
|
||||
reject(new Error(`Timeout when trying to open data connection to ${host}:${port}`));
|
||||
};
|
||||
socket.setTimeout(ftp.timeout);
|
||||
socket.on("error", handleConnErr);
|
||||
socket.on("timeout", handleTimeout);
|
||||
socket.connect({ port, host, family: ftp.ipFamily }, () => {
|
||||
if (ftp.socket instanceof tls_1.TLSSocket) {
|
||||
socket = (0, tls_1.connect)(Object.assign({}, ftp.tlsOptions, {
|
||||
socket,
|
||||
// Reuse the TLS session negotiated earlier when the control connection
|
||||
// was upgraded. Servers expect this because it provides additional
|
||||
// security: If a completely new session would be negotiated, a hacker
|
||||
// could guess the port and connect to the new data connection before we do
|
||||
// by just starting his/her own TLS session.
|
||||
session: ftp.socket.getSession()
|
||||
}));
|
||||
// It's the responsibility of the transfer task to wait until the
|
||||
// TLS socket issued the event 'secureConnect'. We can't do this
|
||||
// here because some servers will start upgrading after the
|
||||
// specific transfer request has been made. List and download don't
|
||||
// have to wait for this event because the server sends whenever it
|
||||
// is ready. But for upload this has to be taken into account,
|
||||
// see the details in the upload() function below.
|
||||
}
|
||||
// Let the FTPContext listen to errors from now on, remove local handler.
|
||||
socket.removeListener("error", handleConnErr);
|
||||
socket.removeListener("timeout", handleTimeout);
|
||||
ftp.dataSocket = socket;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
exports.connectForPassiveTransfer = connectForPassiveTransfer;
|
||||
/**
|
||||
* Helps resolving/rejecting transfers.
|
||||
*
|
||||
* This is used internally for all FTP transfers. For example when downloading, the server might confirm
|
||||
* with "226 Transfer complete" when in fact the download on the data connection has not finished
|
||||
* yet. With all transfers we make sure that a) the result arrived and b) has been confirmed by
|
||||
* e.g. the control connection. We just don't know in which order this will happen.
|
||||
*/
|
||||
class TransferResolver {
|
||||
/**
|
||||
* Instantiate a TransferResolver
|
||||
*/
|
||||
constructor(ftp, progress) {
|
||||
this.ftp = ftp;
|
||||
this.progress = progress;
|
||||
this.response = undefined;
|
||||
this.dataTransferDone = false;
|
||||
}
|
||||
/**
|
||||
* Mark the beginning of a transfer.
|
||||
*
|
||||
* @param name - Name of the transfer, usually the filename.
|
||||
* @param type - Type of transfer, usually "upload" or "download".
|
||||
*/
|
||||
onDataStart(name, type) {
|
||||
// Let the data socket be in charge of tracking timeouts during transfer.
|
||||
// The control socket sits idle during this time anyway and might provoke
|
||||
// a timeout unnecessarily. The control connection will take care
|
||||
// of timeouts again once data transfer is complete or failed.
|
||||
if (this.ftp.dataSocket === undefined) {
|
||||
throw new Error("Data transfer should start but there is no data connection.");
|
||||
}
|
||||
this.ftp.socket.setTimeout(0);
|
||||
this.ftp.dataSocket.setTimeout(this.ftp.timeout);
|
||||
this.progress.start(this.ftp.dataSocket, name, type);
|
||||
}
|
||||
/**
|
||||
* The data connection has finished the transfer.
|
||||
*/
|
||||
onDataDone(task) {
|
||||
this.progress.updateAndStop();
|
||||
// Hand-over timeout tracking back to the control connection. It's possible that
|
||||
// we don't receive the response over the control connection that the transfer is
|
||||
// done. In this case, we want to correctly associate the resulting timeout with
|
||||
// the control connection.
|
||||
this.ftp.socket.setTimeout(this.ftp.timeout);
|
||||
if (this.ftp.dataSocket) {
|
||||
this.ftp.dataSocket.setTimeout(0);
|
||||
}
|
||||
this.dataTransferDone = true;
|
||||
this.tryResolve(task);
|
||||
}
|
||||
/**
|
||||
* The control connection reports the transfer as finished.
|
||||
*/
|
||||
onControlDone(task, response) {
|
||||
this.response = response;
|
||||
this.tryResolve(task);
|
||||
}
|
||||
/**
|
||||
* An error has been reported and the task should be rejected.
|
||||
*/
|
||||
onError(task, err) {
|
||||
this.progress.updateAndStop();
|
||||
this.ftp.socket.setTimeout(this.ftp.timeout);
|
||||
this.ftp.dataSocket = undefined;
|
||||
task.reject(err);
|
||||
}
|
||||
/**
|
||||
* Control connection sent an unexpected request requiring a response from our part. We
|
||||
* can't provide that (because unknown) and have to close the contrext with an error because
|
||||
* the FTP server is now caught up in a state we can't resolve.
|
||||
*/
|
||||
onUnexpectedRequest(response) {
|
||||
const err = new Error(`Unexpected FTP response is requesting an answer: ${response.message}`);
|
||||
this.ftp.closeWithError(err);
|
||||
}
|
||||
tryResolve(task) {
|
||||
// To resolve, we need both control and data connection to report that the transfer is done.
|
||||
const canResolve = this.dataTransferDone && this.response !== undefined;
|
||||
if (canResolve) {
|
||||
this.ftp.dataSocket = undefined;
|
||||
task.resolve(this.response);
|
||||
}
|
||||
}
|
||||
}
|
||||
function uploadFrom(source, config) {
|
||||
const resolver = new TransferResolver(config.ftp, config.tracker);
|
||||
const fullCommand = `${config.command} ${config.remotePath}`;
|
||||
return config.ftp.handle(fullCommand, (res, task) => {
|
||||
if (res instanceof Error) {
|
||||
resolver.onError(task, res);
|
||||
}
|
||||
else if (res.code === 150 || res.code === 125) { // Ready to upload
|
||||
const dataSocket = config.ftp.dataSocket;
|
||||
if (!dataSocket) {
|
||||
resolver.onError(task, new Error("Upload should begin but no data connection is available."));
|
||||
return;
|
||||
}
|
||||
// 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 = "getCipher" in dataSocket ? dataSocket.getCipher() !== undefined : true;
|
||||
onConditionOrEvent(canUpload, dataSocket, "secureConnect", () => {
|
||||
config.ftp.log(`Uploading to ${(0, netUtils_1.describeAddress)(dataSocket)} (${(0, netUtils_1.describeTLS)(dataSocket)})`);
|
||||
resolver.onDataStart(config.remotePath, config.type);
|
||||
(0, stream_1.pipeline)(source, dataSocket, err => {
|
||||
if (err) {
|
||||
resolver.onError(task, err);
|
||||
}
|
||||
else {
|
||||
resolver.onDataDone(task);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else if ((0, parseControlResponse_1.positiveCompletion)(res.code)) { // Transfer complete
|
||||
resolver.onControlDone(task, res);
|
||||
}
|
||||
else if ((0, parseControlResponse_1.positiveIntermediate)(res.code)) {
|
||||
resolver.onUnexpectedRequest(res);
|
||||
}
|
||||
// Ignore all other positive preliminary response codes (< 200)
|
||||
});
|
||||
}
|
||||
exports.uploadFrom = uploadFrom;
|
||||
function downloadTo(destination, config) {
|
||||
if (!config.ftp.dataSocket) {
|
||||
throw new Error("Download will be initiated but no data connection is available.");
|
||||
}
|
||||
const resolver = new TransferResolver(config.ftp, config.tracker);
|
||||
return config.ftp.handle(config.command, (res, task) => {
|
||||
if (res instanceof Error) {
|
||||
resolver.onError(task, res);
|
||||
}
|
||||
else if (res.code === 150 || res.code === 125) { // Ready to download
|
||||
const dataSocket = config.ftp.dataSocket;
|
||||
if (!dataSocket) {
|
||||
resolver.onError(task, new Error("Download should begin but no data connection is available."));
|
||||
return;
|
||||
}
|
||||
config.ftp.log(`Downloading from ${(0, netUtils_1.describeAddress)(dataSocket)} (${(0, netUtils_1.describeTLS)(dataSocket)})`);
|
||||
resolver.onDataStart(config.remotePath, config.type);
|
||||
(0, stream_1.pipeline)(dataSocket, destination, err => {
|
||||
if (err) {
|
||||
resolver.onError(task, err);
|
||||
}
|
||||
else {
|
||||
resolver.onDataDone(task);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (res.code === 350) { // Restarting at startAt.
|
||||
config.ftp.send("RETR " + config.remotePath);
|
||||
}
|
||||
else if ((0, parseControlResponse_1.positiveCompletion)(res.code)) { // Transfer complete
|
||||
resolver.onControlDone(task, res);
|
||||
}
|
||||
else if ((0, parseControlResponse_1.positiveIntermediate)(res.code)) {
|
||||
resolver.onUnexpectedRequest(res);
|
||||
}
|
||||
// Ignore all other positive preliminary response codes (< 200)
|
||||
});
|
||||
}
|
||||
exports.downloadTo = downloadTo;
|
||||
/**
|
||||
* Calls a function immediately if a condition is met or subscribes to an event and calls
|
||||
* it once the event is emitted.
|
||||
*
|
||||
* @param condition The condition to test.
|
||||
* @param emitter The emitter to use if the condition is not met.
|
||||
* @param eventName The event to subscribe to if the condition is not met.
|
||||
* @param action The function to call.
|
||||
*/
|
||||
function onConditionOrEvent(condition, emitter, eventName, action) {
|
||||
if (condition === true) {
|
||||
action();
|
||||
}
|
||||
else {
|
||||
emitter.once(eventName, () => action());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user