add hw2
This commit is contained in:
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;
|
||||
Reference in New Issue
Block a user