/* AsyncJob API */
virtual void start();
+ virtual void swanSong();
void forwardReply();
void forwardError(err_type error = ERR_NONE, int xerrno = 0);
void readUserOrPassReply();
void scheduleReadControlReply();
- void finalizeDataDownload();
+
+ /// Inform Ftp::Server that we are done if originWaitInProgress
+ void stopOriginWait(int code);
static void abort(void *d); // TODO: Capitalize this and FwdState::abort().
bool forwardingCompleted; ///< completeForwarding() has been called
+ /// whether we are between Ftp::Server::startWaitingForOrigin() and
+ /// Ftp::Server::stopWaitingForOrigin() calls
+ bool originWaitInProgress;
+
struct {
wordlist *message; ///< reply message, one wordlist entry per message line
char *lastCommand; ///< the command caused the reply
AsyncJob("Ftp::Relay"),
Ftp::Client(fwdState),
thePreliminaryCb(NULL),
- forwardingCompleted(false)
+ forwardingCompleted(false),
+ originWaitInProgress(false)
{
savedReply.message = NULL;
savedReply.lastCommand = NULL;
sendCommand();
}
+void
+Ftp::Relay::swanSong()
+{
+ stopOriginWait(0);
+ Ftp::Client::swanSong();
+}
+
/// Keep control connection for future requests, after we are done with it.
/// Similar to COMPLETE_PERSISTENT_MSG handling in http.cc.
void
Ftp::Relay::serverComplete()
{
+ stopOriginWait(ctrl.replycode);
+
CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager;
if (mgr.valid()) {
if (Comm::IsConnOpen(ctrl.conn)) {
serverState() == fssConnected ? SENT_USER :
serverState() == fssHandlePass ? SENT_PASS :
SENT_COMMAND;
+
+ if (state == SENT_DATA_REQUEST) {
+ CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager;
+ if (mgr.valid()) {
+ if (Ftp::Server *srv = dynamic_cast<Ftp::Server*>(mgr.get())) {
+ typedef NullaryMemFunT<Ftp::Server> CbDialer;
+ AsyncCall::Pointer call = JobCallback(11, 3, CbDialer, srv,
+ Ftp::Server::startWaitingForOrigin);
+ ScheduleCallHere(call);
+ originWaitInProgress = true;
+ }
+ }
+ }
}
void
" after reading response data");
}
- finalizeDataDownload();
+ debugs(9, 2, "Complete data downloading");
+
+ serverComplete();
}
void
Ftp::Client::scheduleReadControlReply(0);
}
-void
-Ftp::Relay::finalizeDataDownload()
-{
- debugs(9, 2, "Complete data downloading/Uploading");
-
- updateMaster().waitForOriginData = false;
-
- CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager;
- if (mgr.valid()) {
- if (Ftp::Server *srv = dynamic_cast<Ftp::Server*>(mgr.get())) {
- typedef NullaryMemFunT<Ftp::Server> CbDialer;
- AsyncCall::Pointer call = JobCallback(11, 3, CbDialer, srv,
- Ftp::Server::originDataCompletionCheckpoint);
- ScheduleCallHere(call);
- }
- }
- serverComplete();
-}
-
bool
Ftp::Relay::abortOnData(const char *reason)
{
return !Comm::IsConnOpen(ctrl.conn);
}
+void
+Ftp::Relay::stopOriginWait(int code)
+{
+ if (originWaitInProgress) {
+ CbcPointer<ConnStateData> &mgr = fwd->request->clientConnectionManager;
+ if (mgr.valid()) {
+ if (Ftp::Server *srv = dynamic_cast<Ftp::Server*>(mgr.get())) {
+ typedef UnaryMemFunT<Ftp::Server, int> CbDialer;
+ AsyncCall::Pointer call = asyncCall(11, 3, "Ftp::Server::stopWaitingForOrigin",
+ CbDialer(srv, &Ftp::Server::stopWaitingForOrigin, code));
+ ScheduleCallHere(call);
+ }
+ }
+ originWaitInProgress = false;
+ }
+}
+
void
Ftp::Relay::abort(void *d)
{
uploadAvailSize(0),
listener(),
connector(),
- reader()
+ reader(),
+ waitingForOrigin(false),
+ originDataDownloadAbortedOnError(false)
{
flags.readMore = false; // we need to announce ourselves first
*uploadBuf = 0;
{
Must(reply);
+ if (waitingForOrigin) {
+ Must(delayedReply == NULL);
+ delayedReply = reply;
+ return;
+ }
+
const HttpHeader &header = reply->header;
// adaptation and forwarding errors lack Http::HdrType::FTP_STATUS
if (!header.has(Http::HdrType::FTP_STATUS)) {
if (!checkDataConnPre())
return false;
- master->waitForOriginData = true;
master->userDataDone = 0;
+ originDataDownloadAbortedOnError = false;
changeState(fssHandleDataRequest, "handleDataRequest");
if (!checkDataConnPre())
return false;
- master->waitForOriginData = true;
- master->userDataDone = 0;
-
if (Config.accessList.forceRequestBodyContinuation) {
ClientHttpRequest *http = pipeline.front()->http;
HttpRequest *request = http->request;
}
void
-Ftp::Server::originDataCompletionCheckpoint()
+Ftp::Server::startWaitingForOrigin()
{
- if (!master->userDataDone) {
- debugs(33, 5, "Transfering from/to client not finished yet");
- return;
+ debugs(33, 5, "waiting for Ftp::Client data transfer to end");
+ waitingForOrigin = true;
+}
+
+void
+Ftp::Server::stopWaitingForOrigin(int originStatus)
+{
+ Must(waitingForOrigin);
+ waitingForOrigin = false;
+
+ // if we have already decided how to respond, respond now
+ if (delayedReply) {
+ HttpReply::Pointer reply = delayedReply;
+ delayedReply = nullptr;
+ writeForwardedReply(reply.getRaw());
+ return; // do not completeDataDownload() after an earlier response
}
- completeDataExchange();
+ if (master->serverState != fssHandleDataRequest)
+ return;
+
+ // completeDataDownload() could be waitingForOrigin in fssHandleDataRequest
+ // Depending on which side has finished downloading first, either trust
+ // master->userDataDone status or set originDataDownloadAbortedOnError:
+ if (master->userDataDone) {
+ // We finished downloading before Ftp::Client. Most likely, the
+ // adaptation shortened the origin response or we hit an error.
+ // Our status (stored in master->userDataDone) is more informative.
+ // Use master->userDataDone; avoid originDataDownloadAbortedOnError.
+ completeDataDownload();
+ } else {
+ debugs(33, 5, "too early to write the response");
+ // Ftp::Client naturally finished downloading before us. Set
+ // originDataDownloadAbortedOnError to overwrite future
+ // master->userDataDone and relay Ftp::Client error, if there was
+ // any, to the user.
+ originDataDownloadAbortedOnError = (originStatus >= 400);
+ }
}
void Ftp::Server::userDataCompletionCheckpoint(int finalStatusCode)
if (bodyParser)
finishDechunkingRequest(false);
- // The origin control connection is gone, nothing to wait for
- if (!Comm::IsConnOpen(pinning.serverConnection))
- master->waitForOriginData = false;
-
- if (master->waitForOriginData) {
- // The completeDataExchange() is not called here unconditionally
+ if (waitingForOrigin) {
+ // The completeDataDownload() is not called here unconditionally
// because we want to signal the FTP user that we are not fully
// done processing its data stream, even though all data bytes
// have been sent or received already.
- debugs(33, 5, "Transfering from/to FTP server is not complete");
+ debugs(33, 5, "Transfering from FTP server is not complete");
return;
}
- completeDataExchange();
+ // Adjust our reply if the server aborted with an error before we are done.
+ if (master->userDataDone == 226 && originDataDownloadAbortedOnError) {
+ debugs(33, 5, "Transfering from FTP server terminated with an error, adjust status code");
+ master->userDataDone = 451;
+ }
+ completeDataDownload();
}
-void Ftp::Server::completeDataExchange()
+void Ftp::Server::completeDataDownload()
{
writeCustomReply(master->userDataDone, master->userDataDone == 226 ? "Transfer complete" : "Server error; transfer aborted");
closeDataConnection();
public:
typedef RefCount<MasterState> Pointer;
- MasterState(): serverState(fssBegin), clientReadGreeting(false), userDataDone(0), waitForOriginData(false) {}
+MasterState(): serverState(fssBegin), clientReadGreeting(false), userDataDone(0) {}
Ip::Address clientDataAddr; ///< address of our FTP client data connection
SBuf workingDir; ///< estimated current working directory for URI formation
bool clientReadGreeting; ///< whether our FTP client read their FTP server greeting
/// Squid will send or has sent this final status code to the FTP client
int userDataDone;
- /// whether the transfer on the Squid-origin data connection is not over yet
- bool waitForOriginData;
};
/// Manages a control connection from an FTP client.
/* AsyncJob API */
virtual void callException(const std::exception &e) override;
+ /// Called by Ftp::Client class when it is start receiving or
+ /// sending data.
+ void startWaitingForOrigin();
+
/// Called by Ftp::Client class when it is done receiving or
/// sending data. Waits for both agents to be done before
/// responding to the FTP client and closing the data connection.
- void originDataCompletionCheckpoint();
+ void stopWaitingForOrigin(int status);
// This is a pointer in hope to minimize future changes when MasterState
// becomes a part of MasterXaction. Guaranteed not to be nil.
/// Writes the data-transfer status reply to the FTP client and
/// closes the data connection.
- void completeDataExchange();
+ void completeDataDownload();
void calcUri(const SBuf *file);
void changeState(const Ftp::ServerState newState, const char *reason);
AsyncCall::Pointer listener; ///< set when we are passively listening
AsyncCall::Pointer connector; ///< set when we are actively connecting
AsyncCall::Pointer reader; ///< set when we are reading FTP data
+
+ /// whether we wait for the origin data transfer to end
+ bool waitingForOrigin;
+ /// whether the origin data transfer aborted
+ bool originDataDownloadAbortedOnError;
+
+ /// a response which writing was postponed until stopWaitingForOrigin()
+ HttpReply::Pointer delayedReply;
};
} // namespace Ftp