first commit
This commit is contained in:
19
node_modules/basic-ftp/LICENSE.txt
generated
vendored
Normal file
19
node_modules/basic-ftp/LICENSE.txt
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2019 Patrick Juchli
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
242
node_modules/basic-ftp/README.md
generated
vendored
Normal file
242
node_modules/basic-ftp/README.md
generated
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
# Basic FTP
|
||||
|
||||
[](https://www.npmjs.com/package/basic-ftp)
|
||||
[](https://www.npmjs.com/package/basic-ftp)
|
||||
[](https://github.com/patrickjuchli/basic-ftp/actions/workflows/nodejs.yml)
|
||||
|
||||
This is an FTP client library for Node.js. It supports FTPS over TLS, Passive Mode over IPv6, has a Promise-based API, and offers methods to operate on whole directories. Active Mode is not supported.
|
||||
|
||||
## Advisory
|
||||
|
||||
Prefer alternative transfer protocols like HTTPS or SFTP (SSH). FTP is a an old protocol with some reliability issues. Use this library when you have no choice and need to use FTP. Try to use FTPS (FTP over TLS) whenever possible, FTP alone does not provide any security.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Node 10.0 or later is the only dependency.
|
||||
|
||||
## Installation
|
||||
|
||||
`npm install basic-ftp`
|
||||
|
||||
## Usage
|
||||
|
||||
The first example will connect to an FTP server using TLS (FTPS), get a directory listing, upload a file and download it as a copy. Note that the FTP protocol doesn't allow multiple requests running in parallel.
|
||||
|
||||
```js
|
||||
const { Client } = require("basic-ftp")
|
||||
// ESM: import { Client } from "basic-ftp"
|
||||
|
||||
example()
|
||||
|
||||
async function example() {
|
||||
const client = new Client()
|
||||
client.ftp.verbose = true
|
||||
try {
|
||||
await client.access({
|
||||
host: "myftpserver.com",
|
||||
user: "very",
|
||||
password: "password",
|
||||
secure: true
|
||||
})
|
||||
console.log(await client.list())
|
||||
await client.uploadFrom("README.md", "README_FTP.md")
|
||||
await client.downloadTo("README_COPY.md", "README_FTP.md")
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err)
|
||||
}
|
||||
client.close()
|
||||
}
|
||||
```
|
||||
|
||||
The next example deals with directories and their content. First, we make sure a remote path exists, creating all directories as necessary. Then, we make sure it's empty and upload the contents of a local directory.
|
||||
|
||||
```js
|
||||
await client.ensureDir("my/remote/directory")
|
||||
await client.clearWorkingDir()
|
||||
await client.uploadFromDir("my/local/directory")
|
||||
```
|
||||
|
||||
If you encounter a problem, it may help to log out all communication with the FTP server.
|
||||
|
||||
```js
|
||||
client.ftp.verbose = true
|
||||
```
|
||||
|
||||
## Client API
|
||||
|
||||
`new Client(timeout = 30000)`
|
||||
|
||||
Create a client instance. Configure it with a timeout in milliseconds that will be used for any connection made. Use 0 to disable timeouts, default is 30 seconds.
|
||||
|
||||
`close()`
|
||||
|
||||
Close the client and any open connection. The client can’t be used anymore after calling this method, you'll have to reconnect with `access` to continue any work. A client is also closed automatically if any timeout or connection error occurs. See the section on [Error Handling](#error-handling) below.
|
||||
|
||||
`closed`
|
||||
|
||||
True if the client is not connected to a server. You can reconnect with `access`.
|
||||
|
||||
`access(options): Promise<FTPResponse>`
|
||||
|
||||
Get access to an FTP server. This method will connect to a server, optionally secure the connection with TLS, login a user and apply some default settings (TYPE I, STRU F, PBSZ 0, PROT P). It returns the response of the initial connect command. 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 connection. This also implies that you can reopen a `Client` instance that has been closed due to an error when reconnecting with this method. The available options are:
|
||||
|
||||
- `host (string)` Server host, default: localhost
|
||||
- `port (number)` Server port, default: 21
|
||||
- `user (string)` Username, default: anonymous
|
||||
- `password (string)` Password, default: guest
|
||||
- `secure (boolean | "implicit")` Explicit FTPS over TLS, default: false. Use "implicit" if you need support for legacy implicit FTPS.
|
||||
- `secureOptions` Options for TLS, same as for [tls.connect()](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback) in Node.js.
|
||||
|
||||
---
|
||||
|
||||
`features(): Promise<Map<string, string>>`
|
||||
|
||||
Get a description of supported features. This will return a Map where keys correspond to FTP commands and values contain further details. If the FTP server doesn't support this request you'll still get an empty Map instead of an error response.
|
||||
|
||||
`send(command): Promise<FTPResponse>`
|
||||
|
||||
Send an FTP command and return the first response.
|
||||
|
||||
`sendIgnoringError(command): Promise<FTPResponse>`
|
||||
|
||||
Send an FTP command, return the first response, and ignore an FTP error response. Any other error or timeout will still reject the Promise.
|
||||
|
||||
`cd(path): Promise<FTPResponse>`
|
||||
|
||||
Change the current working directory.
|
||||
|
||||
`pwd(): Promise<string>`
|
||||
|
||||
Get the path of the current working directory.
|
||||
|
||||
`list([path]): Promise<FileInfo[]>`
|
||||
|
||||
List files and directories in the current working directory, or at `path` if specified. Currently, this library only supports MLSD, Unix and DOS directory listings. See [FileInfo](src/FileInfo.ts) for more details.
|
||||
|
||||
`lastMod(path): Promise<Date>`
|
||||
|
||||
Get the last modification time of a file. This command might not be supported by your FTP server and throw an exception.
|
||||
|
||||
`size(path): Promise<number>`
|
||||
|
||||
Get the size of a file in bytes.
|
||||
|
||||
`rename(path, newPath): Promise<FTPResponse>`
|
||||
|
||||
Rename a file. Depending on the server you may also use this to move a file to another directory by providing full paths.
|
||||
|
||||
`remove(path): Promise<FTPResponse>`
|
||||
|
||||
Remove a file.
|
||||
|
||||
`uploadFrom(readableStream | localPath, remotePath, [options]): Promise<FTPResponse>`
|
||||
|
||||
Upload data from a readable stream or a local file to a remote file. If such a file already exists it will be overwritten. If a file is being uploaded, additional options offer `localStart` and `localEndInclusive` to only upload parts of it.
|
||||
|
||||
`appendFrom(readableStream | localPath, remotePath, [options]): 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. If a file is being uploaded, additional options offer `localStart` and `localEndInclusive` to only upload parts of it. For example: To resume a failed upload, request the size of the remote, partially uploaded file using `size()` and use it as `localStart`.
|
||||
|
||||
`downloadTo(writableStream | localPath, remotePath, startAt = 0): 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, request the size of the local, partially downloaded file and use that as `startAt`.
|
||||
|
||||
---
|
||||
|
||||
`ensureDir(remoteDirPath): Promise<void>`
|
||||
|
||||
Make sure that the given `remoteDirPath` exists on the server, creating all directories as necessary. The working directory is at `remoteDirPath` after calling this method.
|
||||
|
||||
`clearWorkingDir(): Promise<void>`
|
||||
|
||||
Remove all files and directories from the working directory.
|
||||
|
||||
`removeDir(remoteDirPath): Promise<void>`
|
||||
|
||||
Remove all files and directories from a given directory, including the directory itself. The working directory stays the same unless it is part of the deleted directories.
|
||||
|
||||
`uploadFromDir(localDirPath, [remoteDirPath]): Promise<void>`
|
||||
|
||||
Upload the contents of a local directory to the current 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 any remote directory which will be created if necessary including all intermediate directories. The working directory stays the same after calling this method.
|
||||
|
||||
`downloadToDir(localDirPath, [remoteDirPath]): Promise<void>`
|
||||
|
||||
Download all files and directories of the current working directory to a given local directory. You can optionally set a specific remote directory. The working directory stays the same after calling this method.
|
||||
|
||||
---
|
||||
|
||||
`trackProgress(handler)`
|
||||
|
||||
Report any transfer progress using the given handler function. See the next section for more details.
|
||||
|
||||
## Transfer Progress
|
||||
|
||||
Set a callback function with `client.trackProgress` to track the progress of any transfer. Transfers are uploads, downloads or directory listings. To disable progress reporting, call `trackProgress` without a handler.
|
||||
|
||||
```js
|
||||
// Log progress for any transfer from now on.
|
||||
client.trackProgress(info => {
|
||||
console.log("File", info.name)
|
||||
console.log("Type", info.type)
|
||||
console.log("Transferred", info.bytes)
|
||||
console.log("Transferred Overall", info.bytesOverall)
|
||||
})
|
||||
|
||||
// Transfer some data
|
||||
await client.uploadFrom(someStream, "test.txt")
|
||||
await client.uploadFrom("somefile.txt", "test2.txt")
|
||||
|
||||
// Set a new callback function which also resets the overall counter
|
||||
client.trackProgress(info => console.log(info.bytesOverall))
|
||||
await client.downloadToDir("local/path", "remote/path")
|
||||
|
||||
// Stop logging
|
||||
client.trackProgress()
|
||||
```
|
||||
|
||||
For each transfer, the callback function will receive the filename, transfer type (`upload`, `download` or `list`) and number of bytes transferred. The function will be called at a regular interval during a transfer.
|
||||
|
||||
There is also a counter for all bytes transferred since the last time `trackProgress` was called. This is useful when downloading a directory with multiple files where you want to show the total bytes downloaded so far.
|
||||
|
||||
## Error Handling
|
||||
|
||||
Any error reported by the FTP server will be thrown as `FTPError`. The connection to the FTP server stays intact and you can continue to use your `Client` instance.
|
||||
|
||||
This is different with a timeout or connection error: In addition to an `Error` being thrown, any connection to the FTP server will be closed. You’ll have to reconnect with `client.access()`, if you want to continue any work.
|
||||
|
||||
## Logging
|
||||
|
||||
Using `client.ftp.verbose = true` will log debug-level information to the console. You can use your own logging library by overriding `client.ftp.log`. This method is called regardless of what `client.ftp.verbose` is set to. For example:
|
||||
|
||||
```
|
||||
myClient.ftp.log = myLogger.debug
|
||||
```
|
||||
|
||||
## Static Types
|
||||
|
||||
In addition to unit tests and linting, the source code is written in Typescript using rigorous [compiler settings](tsconfig.json) like `strict` and `noImplicitAny`. When building the project, the source is transpiled to Javascript and type declaration files. This makes the library useable for both Javascript and Typescript projects.
|
||||
|
||||
## Extending the library
|
||||
|
||||
### Client
|
||||
|
||||
`get/set client.parseList`
|
||||
|
||||
Provide a function to parse directory listing data. This library supports MLSD, Unix and DOS formats. Parsing these list responses is one of the more challenging parts of FTP because there is no standard that all servers adhere to. The signature of the function is `(rawList: string) => FileInfo[]`.
|
||||
|
||||
### FTPContext
|
||||
|
||||
The Client API described so far is implemented using an FTPContext. An FTPContext provides the foundation to write an FTP client. It holds the socket connections and provides an API to handle responses and events in a simplified way. Through `client.ftp` you get access to this context.
|
||||
|
||||
`get/set verbose`
|
||||
|
||||
Set the verbosity level to optionally log out all communication between the client and the server.
|
||||
|
||||
`get/set encoding`
|
||||
|
||||
Set the encoding applied to all incoming and outgoing messages of the control connection. This encoding is also used when parsing a list response from a data connection. See https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings for what encodings are supported by Node.js. Default is `utf8` because most modern servers support it, some of them without mentioning it when requesting features.
|
||||
|
||||
## Acknowledgment
|
||||
|
||||
This library uses parts of the [directory listing parsers](https://github.com/apache/commons-net/tree/master/src/main/java/org/apache/commons/net/ftp/parser) written by The Apache Software Foundation. They've been made available under the Apache 2.0 license. See the [included notice](NOTICE.txt) and headers in the respective files containing the original copyright texts and a description of changes.
|
||||
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());
|
||||
}
|
||||
}
|
||||
49
node_modules/basic-ftp/package.json
generated
vendored
Normal file
49
node_modules/basic-ftp/package.json
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "basic-ftp",
|
||||
"version": "5.0.5",
|
||||
"description": "FTP client for Node.js, supports FTPS over TLS, IPv6, Async/Await, and Typescript.",
|
||||
"main": "dist/index",
|
||||
"types": "dist/index",
|
||||
"files": [
|
||||
"dist/**/*"
|
||||
],
|
||||
"scripts": {
|
||||
"prepublishOnly": "npm run clean && npm run lint && tsc && mocha",
|
||||
"prepare": "tsc",
|
||||
"test": "npm run prepublishOnly",
|
||||
"clean": "rm -rf dist",
|
||||
"lint": "eslint \"./src/**/*.ts\"",
|
||||
"lint-fix": "eslint --fix \"./src/**/*.ts\"",
|
||||
"dev": "npm run clean && tsc --watch",
|
||||
"tdd": "mocha --watch",
|
||||
"buildOnly": "tsc"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/patrickjuchli/basic-ftp.git"
|
||||
},
|
||||
"author": "Patrick Juchli <patrickjuchli@gmail.com>",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"ftp",
|
||||
"ftps",
|
||||
"promise",
|
||||
"async",
|
||||
"await",
|
||||
"tls",
|
||||
"ipv6",
|
||||
"typescript"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "10.0.6",
|
||||
"@types/node": "20.10.4",
|
||||
"@typescript-eslint/eslint-plugin": "6.14.0",
|
||||
"@typescript-eslint/parser": "6.14.0",
|
||||
"eslint": "8.55.0",
|
||||
"mocha": "10.2.0",
|
||||
"typescript": "5.3.3"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user