
First working release of new WiFi code Axes moving other than X (possibly mapped), Y and Z are not taken to be part of the feed rate Introduced SafeStrncpy and SafeStrncat
890 lines
21 KiB
C++
890 lines
21 KiB
C++
/*
|
|
* FtpResponder.cpp
|
|
*
|
|
* Created on: 15 Apr 2017
|
|
* Authors: David and Christian
|
|
*/
|
|
|
|
#include "FtpResponder.h"
|
|
#include "Network.h"
|
|
#include "Platform.h"
|
|
|
|
FtpResponder::FtpResponder(NetworkResponder *n) : NetworkResponder(n), dataSocket(nullptr),
|
|
passivePort(0), passivePortOpenTime(0), dataBuf(nullptr)
|
|
{
|
|
strcpy(fileToMove, "");
|
|
}
|
|
|
|
// Ask the responder to accept this connection, returns true if it did
|
|
bool FtpResponder::Accept(Socket *s, Protocol protocol)
|
|
{
|
|
if (responderState == ResponderState::free && protocol == FtpProtocol)
|
|
{
|
|
// Make sure we can get an output buffer before we accept the connection, or we won't be able to reply
|
|
if (outBuf != nullptr || OutputBuffer::Allocate(outBuf))
|
|
{
|
|
skt = s;
|
|
if (reprap.Debug(moduleWebserver))
|
|
{
|
|
debugPrintf("FTP connection accepted\n");
|
|
}
|
|
|
|
outBuf->copy("220 RepRapFirmware FTP server\r\n");
|
|
Commit(ResponderState::authenticating);
|
|
return true;
|
|
}
|
|
}
|
|
else if (responderState == ResponderState::waitingForPasvPort && protocol == FtpDataProtocol && s->GetLocalPort() == passivePort)
|
|
{
|
|
dataSocket = s;
|
|
responderState = ResponderState::pasvPortOpened;
|
|
|
|
if (reprap.Debug(moduleWebserver))
|
|
{
|
|
debugPrintf("FTP data connection accepted\n");
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// This is called to force termination if we implement the specified protocol
|
|
void FtpResponder::Terminate(Protocol protocol)
|
|
{
|
|
if (protocol == FtpProtocol || protocol == AnyProtocol)
|
|
{
|
|
ConnectionLost();
|
|
}
|
|
}
|
|
|
|
// Do some work, returning true if we did anything significant
|
|
bool FtpResponder::Spin()
|
|
{
|
|
switch (responderState)
|
|
{
|
|
case ResponderState::free:
|
|
return false;
|
|
|
|
case ResponderState::authenticating:
|
|
case ResponderState::reading:
|
|
return ReadData();
|
|
|
|
case ResponderState::sending:
|
|
SendData();
|
|
return true;
|
|
|
|
case ResponderState::waitingForPasvPort:
|
|
if (millis() - passivePortOpenTime > ftpPasvPortTimeout && (outBuf != nullptr || OutputBuffer::Allocate(outBuf)))
|
|
{
|
|
outBuf->copy("425 Failed to establish connection.\r\n");
|
|
Commit(ResponderState::reading);
|
|
|
|
CloseDataPort();
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case ResponderState::pasvPortOpened:
|
|
if (dataBuf != nullptr || OutputBuffer::Allocate(dataBuf))
|
|
{
|
|
return ReadData();
|
|
}
|
|
return false;
|
|
|
|
case ResponderState::uploading:
|
|
DoUpload();
|
|
|
|
if (!uploadError && skt->CanRead())
|
|
{
|
|
ReadData(); // check for incoming ABOR requests
|
|
}
|
|
return true;
|
|
|
|
case ResponderState::sendingPasvData:
|
|
SendPassiveData();
|
|
|
|
if (!sendError && skt->CanRead())
|
|
{
|
|
ReadData(); // check for incoming ABOR requests
|
|
}
|
|
return true;
|
|
|
|
case ResponderState::pasvTransferComplete:
|
|
if (outBuf != nullptr || OutputBuffer::Allocate(outBuf))
|
|
{
|
|
// Is the main FTP connection still available?
|
|
if (skt->CanSend())
|
|
{
|
|
// Yes - send a response
|
|
if (uploadError || sendError)
|
|
{
|
|
outBuf->copy("526 Transfer failed!\r\n");
|
|
}
|
|
else
|
|
{
|
|
outBuf->copy("226 Transfer complete.\r\n");
|
|
}
|
|
Commit(ResponderState::reading);
|
|
|
|
// Close the data port again
|
|
CloseDataPort();
|
|
}
|
|
else
|
|
{
|
|
// No - reset our state
|
|
ConnectionLost();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
default: // should not happen
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void FtpResponder::Diagnostics(MessageType mt)
|
|
{
|
|
GetPlatform()->MessageF(mt, "FTP state %d\n", (int)responderState);
|
|
}
|
|
|
|
// This must be called only for the main FTP port
|
|
void FtpResponder::ConnectionLost()
|
|
{
|
|
CloseDataPort();
|
|
NetworkResponder::ConnectionLost();
|
|
}
|
|
|
|
// Send our data over the main FTP port.
|
|
// We send outBuf first and then outStack. fileBeingSent is reserved for the data port.
|
|
void FtpResponder::SendData()
|
|
{
|
|
// Send our output buffer and output stack
|
|
for(;;)
|
|
{
|
|
if (outBuf == nullptr)
|
|
{
|
|
outBuf = outStack->Pop();
|
|
if (outBuf == nullptr)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
const size_t bytesLeft = outBuf->BytesLeft();
|
|
if (bytesLeft == 0)
|
|
{
|
|
outBuf = OutputBuffer::Release(outBuf);
|
|
}
|
|
else
|
|
{
|
|
const size_t sent = skt->Send(reinterpret_cast<const uint8_t *>(outBuf->UnreadData()), bytesLeft);
|
|
if (sent == 0)
|
|
{
|
|
// Check whether the connection has been closed
|
|
if (!skt->CanSend())
|
|
{
|
|
// The connection has been lost or the other end has closed it
|
|
if (reprap.Debug(moduleWebserver))
|
|
{
|
|
debugPrintf("Can't send anymore\n");
|
|
}
|
|
ConnectionLost();
|
|
}
|
|
return;
|
|
}
|
|
|
|
outBuf->Taken(sent); // tell the output buffer how much data we have taken
|
|
if (sent < bytesLeft)
|
|
{
|
|
return;
|
|
}
|
|
outBuf = OutputBuffer::Release(outBuf);
|
|
}
|
|
}
|
|
|
|
// If we get here then there is nothing left to send
|
|
skt->Send(); // tell the socket there is no more data
|
|
|
|
// If we are going to free up this responder after sending, then we must close the connection
|
|
if (stateAfterSending == ResponderState::free)
|
|
{
|
|
skt->Close();
|
|
skt = nullptr;
|
|
}
|
|
responderState = stateAfterSending;
|
|
}
|
|
|
|
// Send our data over the passive FTP data port.
|
|
// We send dataBuf first and then fileBeingSent.
|
|
void FtpResponder::SendPassiveData()
|
|
{
|
|
// Send our output buffers
|
|
while (dataBuf != nullptr)
|
|
{
|
|
const size_t bytesLeft = dataBuf->BytesLeft();
|
|
if (bytesLeft == 0)
|
|
{
|
|
dataBuf = OutputBuffer::Release(dataBuf);
|
|
}
|
|
else
|
|
{
|
|
const size_t sent = dataSocket->Send(reinterpret_cast<const uint8_t *>(dataBuf->UnreadData()), bytesLeft);
|
|
if (sent == 0)
|
|
{
|
|
// Check whether the connection has been closed
|
|
if (!dataSocket->CanSend())
|
|
{
|
|
if (reprap.Debug(moduleWebserver))
|
|
{
|
|
debugPrintf("Can't send anymore over the data port\n");
|
|
}
|
|
|
|
sendError = true;
|
|
dataSocket = nullptr;
|
|
if (fileBeingSent != nullptr)
|
|
{
|
|
fileBeingSent->Close();
|
|
fileBeingSent = nullptr;
|
|
}
|
|
|
|
responderState = ResponderState::pasvTransferComplete;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Tell the output buffer how much data we have taken
|
|
dataBuf->Taken(sent);
|
|
if (sent < bytesLeft)
|
|
{
|
|
return;
|
|
}
|
|
dataBuf = OutputBuffer::Release(dataBuf);
|
|
}
|
|
}
|
|
|
|
// If we get here then there are no output buffers left to send
|
|
// If we have a file to send, send it
|
|
if (fileBeingSent != nullptr && fileBuffer == nullptr)
|
|
{
|
|
fileBuffer = NetworkBuffer::Allocate();
|
|
if (fileBuffer == nullptr)
|
|
{
|
|
return; // no buffer available, try again later
|
|
}
|
|
}
|
|
|
|
// If we have a file buffer here, we must be in the process of sending a file
|
|
while (fileBuffer != nullptr)
|
|
{
|
|
if (fileBuffer->IsEmpty() && fileBeingSent != nullptr)
|
|
{
|
|
const int bytesRead = fileBuffer->ReadFromFile(fileBeingSent);
|
|
if (bytesRead != (int)NetworkBuffer::bufferSize)
|
|
{
|
|
// We had a read error or we reached the end of the file
|
|
fileBeingSent->Close();
|
|
fileBeingSent = nullptr;
|
|
}
|
|
}
|
|
|
|
if (fileBuffer->IsEmpty())
|
|
{
|
|
// Must have sent the whole file
|
|
fileBuffer->Release();
|
|
fileBuffer = nullptr;
|
|
}
|
|
else
|
|
{
|
|
const size_t remaining = fileBuffer->Remaining();
|
|
const size_t sent = dataSocket->Send(fileBuffer->UnreadData(), remaining);
|
|
if (sent == 0)
|
|
{
|
|
// Check whether the connection has been closed
|
|
if (!dataSocket->CanSend())
|
|
{
|
|
if (reprap.Debug(moduleWebserver))
|
|
{
|
|
debugPrintf("Can't send anymore\n");
|
|
}
|
|
|
|
sendError = true;
|
|
dataSocket = nullptr;
|
|
if (fileBeingSent != nullptr)
|
|
{
|
|
fileBeingSent->Close();
|
|
fileBeingSent = nullptr;
|
|
}
|
|
fileBuffer->Release();
|
|
fileBuffer = nullptr;
|
|
|
|
responderState = ResponderState::pasvTransferComplete;
|
|
}
|
|
return;
|
|
}
|
|
|
|
fileBuffer->Taken(sent);
|
|
if (sent < remaining)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we get here then there is nothing left to send. Close it as well
|
|
dataSocket->Send(); // tell the socket there is no more data
|
|
dataSocket->Close();
|
|
dataSocket = nullptr;
|
|
|
|
responderState = ResponderState::pasvTransferComplete;
|
|
}
|
|
|
|
// Write some more upload data
|
|
void FtpResponder::DoUpload()
|
|
{
|
|
// Write incoming data to the file
|
|
const uint8_t *buffer;
|
|
size_t len;
|
|
if (dataSocket->ReadBuffer(buffer, len))
|
|
{
|
|
if (reprap.Debug(moduleWebserver))
|
|
{
|
|
GetPlatform()->MessageF(HOST_MESSAGE, "Writing %u bytes of upload data\n", len);
|
|
}
|
|
|
|
const bool success = fileBeingUploaded.Write(reinterpret_cast<const char*>(buffer), len);
|
|
dataSocket->Taken(len);
|
|
if (!success)
|
|
{
|
|
uploadError = true;
|
|
GetPlatform()->Message(GENERIC_MESSAGE, "Error: Could not write upload data!\n");
|
|
CancelUpload();
|
|
|
|
responderState = ResponderState::pasvTransferComplete;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Upload has finished if the connection is closed
|
|
if (!dataSocket->CanRead())
|
|
{
|
|
dataSocket = nullptr;
|
|
responderState = ResponderState::pasvTransferComplete;
|
|
|
|
FinishUpload(0, 0);
|
|
}
|
|
}
|
|
|
|
// Try to read some data from the main FTP port and return true
|
|
// if anything significant could be done
|
|
bool FtpResponder::ReadData()
|
|
{
|
|
bool readSomething = false;
|
|
char c;
|
|
while (!haveCompleteLine && skt->ReadChar(c))
|
|
{
|
|
CharFromClient(c);
|
|
readSomething = true;
|
|
}
|
|
|
|
if (!readSomething && !skt->CanRead())
|
|
{
|
|
ConnectionLost();
|
|
return true;
|
|
}
|
|
|
|
if (haveCompleteLine && (outBuf != nullptr || OutputBuffer::Allocate(outBuf)))
|
|
{
|
|
ProcessLine();
|
|
return true;
|
|
}
|
|
return readSomething;
|
|
}
|
|
|
|
// Keep track of incoming characters
|
|
void FtpResponder::CharFromClient(char c)
|
|
{
|
|
switch (c)
|
|
{
|
|
case 0:
|
|
break;
|
|
|
|
case '\r':
|
|
case '\n':
|
|
if (clientPointer != 0)
|
|
{
|
|
// This line is complete
|
|
clientMessage[clientPointer] = 0;
|
|
haveCompleteLine = true;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
clientMessage[clientPointer++] = c;
|
|
|
|
// Make sure we don't overflow the line buffer
|
|
if (clientPointer > ARRAY_UPB(clientMessage))
|
|
{
|
|
clientPointer = 0;
|
|
GetPlatform()->Message(HOST_MESSAGE, "Webserver: Buffer overflow in FTP server!\n");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Process the next FTP command
|
|
void FtpResponder::ProcessLine()
|
|
{
|
|
if (reprap.Debug(moduleWebserver))
|
|
{
|
|
debugPrintf("FTP request '%s' (state %d)\n", clientMessage, (int)responderState);
|
|
}
|
|
|
|
haveCompleteLine = false;
|
|
clientPointer = 0;
|
|
|
|
switch (responderState)
|
|
{
|
|
case ResponderState::authenticating:
|
|
// don't check the user name
|
|
if (StringStartsWith(clientMessage, "USER"))
|
|
{
|
|
outBuf->copy("331 Please specify the password.\r\n");
|
|
Commit(ResponderState::authenticating);
|
|
}
|
|
// but check the password (if set)
|
|
else if (StringStartsWith(clientMessage, "PASS"))
|
|
{
|
|
const char *password = GetParameter("PASS");
|
|
if (reprap.NoPasswordSet() || reprap.CheckPassword(password))
|
|
{
|
|
strcpy(currentDirectory, "/");
|
|
|
|
outBuf->copy("230 Login successful.\r\n");
|
|
Commit(ResponderState::reading);
|
|
}
|
|
else
|
|
{
|
|
outBuf->copy("530 Login incorrect.\r\n");
|
|
Commit();
|
|
}
|
|
}
|
|
// end connection
|
|
else if (StringEquals(clientMessage, "QUIT"))
|
|
{
|
|
outBuf->copy("221 Goodbye.\r\n");
|
|
Commit();
|
|
}
|
|
// if it's different, send response 500 to indicate we don't know the code (might be AUTH or so)
|
|
else
|
|
{
|
|
outBuf->copy("500 Unknown login command.\r\n");
|
|
Commit(ResponderState::authenticating);
|
|
}
|
|
break;
|
|
|
|
case ResponderState::reading:
|
|
// get system type
|
|
if (StringEquals(clientMessage, "SYST"))
|
|
{
|
|
outBuf->copy("215 UNIX Type: L8\r\n");
|
|
Commit(ResponderState::reading);
|
|
}
|
|
// get features
|
|
else if (StringEquals(clientMessage, "FEAT"))
|
|
{
|
|
outBuf->copy( "211-Features:\r\n"
|
|
"PASV\r\n" // support PASV mode
|
|
"211 End\r\n"
|
|
);
|
|
Commit(ResponderState::reading);
|
|
}
|
|
// get current dir
|
|
else if (StringEquals(clientMessage, "PWD"))
|
|
{
|
|
outBuf->printf("257 \"%s\"\r\n", currentDirectory);
|
|
Commit(ResponderState::reading);
|
|
}
|
|
// set current dir
|
|
else if (StringStartsWith(clientMessage, "CWD"))
|
|
{
|
|
const char *directory = GetParameter("CWD");
|
|
ChangeDirectory(directory);
|
|
}
|
|
// change to parent of current directory
|
|
else if (StringEquals(clientMessage, "CDUP"))
|
|
{
|
|
ChangeDirectory("..");
|
|
}
|
|
// switch transfer mode (sends response, but doesn't have any effects)
|
|
else if (StringStartsWith(clientMessage, "TYPE"))
|
|
{
|
|
const char *type = GetParameter("TYPE");
|
|
if (StringEquals(type, "I"))
|
|
{
|
|
outBuf->copy("200 Switching to Binary mode.\r\n");
|
|
Commit(ResponderState::reading);
|
|
}
|
|
else if (StringEquals(type, "A"))
|
|
{
|
|
outBuf->copy("200 Switching to ASCII mode.\r\n");
|
|
Commit(ResponderState::reading);
|
|
}
|
|
else
|
|
{
|
|
outBuf->copy("500 Unknown command.\r\n");
|
|
Commit(ResponderState::reading);
|
|
}
|
|
}
|
|
// enter passive mode mode
|
|
else if (StringEquals(clientMessage, "PASV"))
|
|
{
|
|
// reset error conditions
|
|
uploadError = sendError = false;
|
|
|
|
// open random port > 1023
|
|
passivePort = random(1024, 65535);
|
|
passivePortOpenTime = millis();
|
|
|
|
GetNetwork()->OpenDataPort(passivePort);
|
|
if (reprap.Debug(moduleWebserver))
|
|
{
|
|
debugPrintf("FTP data port open at port %u\n", passivePort);
|
|
}
|
|
|
|
// send FTP response
|
|
const uint8_t * const ipAddress = GetNetwork()->GetIPAddress();
|
|
outBuf->printf("227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n",
|
|
ipAddress[0], ipAddress[1], ipAddress[2], ipAddress[3],
|
|
passivePort / 256, passivePort % 256);
|
|
Commit(ResponderState::waitingForPasvPort);
|
|
}
|
|
// PASV commands are not supported in this state
|
|
else if (StringEquals(clientMessage, "LIST") || StringStartsWith(clientMessage, "RETR") || StringStartsWith(clientMessage, "STOR"))
|
|
{
|
|
outBuf->copy("425 Use PASV first.\r\n");
|
|
Commit(ResponderState::reading);
|
|
}
|
|
// delete file
|
|
else if (StringStartsWith(clientMessage, "DELE"))
|
|
{
|
|
const char *filename = GetParameter("DELE");
|
|
if (GetPlatform()->GetMassStorage()->Delete(currentDirectory, filename))
|
|
{
|
|
outBuf->copy("250 Delete operation successful.\r\n");
|
|
}
|
|
else
|
|
{
|
|
outBuf->copy("550 Delete operation failed.\r\n");
|
|
}
|
|
Commit(ResponderState::reading);
|
|
}
|
|
// delete directory
|
|
else if (StringStartsWith(clientMessage, "RMD"))
|
|
{
|
|
const char *filename = GetParameter("RMD");
|
|
if (GetPlatform()->GetMassStorage()->Delete(currentDirectory, filename))
|
|
{
|
|
outBuf->copy("250 Remove directory operation successful.\r\n");
|
|
}
|
|
else
|
|
{
|
|
outBuf->copy("550 Remove directory operation failed.\r\n");
|
|
}
|
|
Commit(ResponderState::reading);
|
|
}
|
|
// make new directory
|
|
else if (StringStartsWith(clientMessage, "MKD"))
|
|
{
|
|
const char *filename = GetParameter("MKD");
|
|
const char *location = (filename[0] == '/')
|
|
? filename
|
|
: GetPlatform()->GetMassStorage()->CombineName(currentDirectory, filename);
|
|
|
|
if (GetPlatform()->GetMassStorage()->MakeDirectory(location))
|
|
{
|
|
outBuf->printf("257 \"%s\" created\r\n", location);
|
|
}
|
|
else
|
|
{
|
|
outBuf->copy("550 Create directory operation failed.\r\n");
|
|
}
|
|
Commit(ResponderState::reading);
|
|
}
|
|
// rename file or directory
|
|
else if (StringStartsWith(clientMessage, "RNFR"))
|
|
{
|
|
const char *filename = GetParameter("RNFR");
|
|
if (filename[0] != '/')
|
|
{
|
|
filename = GetPlatform()->GetMassStorage()->CombineName(currentDirectory, filename);
|
|
}
|
|
SafeStrncpy(fileToMove, filename, ARRAY_SIZE(fileToMove));
|
|
|
|
if (GetPlatform()->GetMassStorage()->FileExists(fileToMove))
|
|
{
|
|
outBuf->copy("350 Ready to RNTO.\r\n");
|
|
}
|
|
else
|
|
{
|
|
outBuf->copy("550 Invalid file or directory.\r\n");
|
|
}
|
|
Commit(ResponderState::reading);
|
|
}
|
|
else if (StringStartsWith(clientMessage, "RNTO"))
|
|
{
|
|
const char *filename = GetParameter("RNTO");
|
|
if (filename[0] != '/')
|
|
{
|
|
filename = GetPlatform()->GetMassStorage()->CombineName(currentDirectory, filename);
|
|
}
|
|
|
|
if (GetPlatform()->GetMassStorage()->Rename(fileToMove, filename))
|
|
{
|
|
outBuf->copy("250 Rename successful.\r\n");
|
|
}
|
|
else
|
|
{
|
|
outBuf->copy("500 Could not rename file or directory\r\n");
|
|
}
|
|
Commit(ResponderState::reading);
|
|
}
|
|
// no op
|
|
else if (StringEquals(clientMessage, "NOOP"))
|
|
{
|
|
outBuf->copy("200 NOOP okay.\r\n");
|
|
Commit(ResponderState::reading);
|
|
}
|
|
// end connection
|
|
else if (StringEquals(clientMessage, "QUIT"))
|
|
{
|
|
outBuf->copy("221 Goodbye.\r\n");
|
|
Commit();
|
|
}
|
|
// unknown
|
|
else
|
|
{
|
|
outBuf->copy("500 Unknown command.\r\n");
|
|
Commit(ResponderState::reading);
|
|
}
|
|
break;
|
|
|
|
case ResponderState::pasvPortOpened:
|
|
// list directory entries
|
|
if (StringEquals(clientMessage, "LIST"))
|
|
{
|
|
// send announcement via ftp main port
|
|
outBuf->copy("150 Here comes the directory listing.\r\n");
|
|
Commit(ResponderState::sendingPasvData);
|
|
|
|
// build directory listing, dataBuf is sent later in the Spin loop
|
|
MassStorage * const massStorage = GetPlatform()->GetMassStorage();
|
|
FileInfo fileInfo;
|
|
if (massStorage->FindFirst(currentDirectory, fileInfo))
|
|
{
|
|
do {
|
|
// Example for a typical UNIX-like file list:
|
|
// "drwxr-xr-x 2 ftp ftp 0 Apr 11 2013 bin\r\n"
|
|
const char dirChar = (fileInfo.isDirectory) ? 'd' : '-';
|
|
const struct tm * const timeInfo = gmtime(&fileInfo.lastModified);
|
|
dataBuf->catf("%crw-rw-rw- 1 ftp ftp %13lu %s %02d %04d %s\r\n",
|
|
dirChar, fileInfo.size, massStorage->GetMonthName(timeInfo->tm_mon + 1),
|
|
timeInfo->tm_mday, timeInfo->tm_year + 1900, fileInfo.fileName);
|
|
} while (massStorage->FindNext(fileInfo));
|
|
}
|
|
}
|
|
// upload a file
|
|
else if (StringStartsWith(clientMessage, "STOR"))
|
|
{
|
|
const char *filename = GetParameter("STOR");
|
|
FileStore *file = GetPlatform()->GetFileStore(currentDirectory, filename, true);
|
|
if (file != nullptr)
|
|
{
|
|
StartUpload(file, filename);
|
|
|
|
outBuf->copy("150 OK to send data.\r\n");
|
|
Commit(ResponderState::uploading);
|
|
}
|
|
else
|
|
{
|
|
outBuf->copy("550 Failed to open file.\r\n");
|
|
Commit(ResponderState::reading);
|
|
}
|
|
}
|
|
// download a file
|
|
else if (StringStartsWith(clientMessage, "RETR"))
|
|
{
|
|
const char *filename = GetParameter("RETR");
|
|
fileBeingSent = GetPlatform()->GetFileStore(currentDirectory, filename, false);
|
|
if (fileBeingSent != nullptr)
|
|
{
|
|
outBuf->printf("150 Opening data connection for %s (%lu bytes).\r\n", filename, fileBeingSent->Length());
|
|
Commit(ResponderState::sendingPasvData);
|
|
}
|
|
else
|
|
{
|
|
outBuf->copy("550 Failed to open file.\r\n");
|
|
Commit(ResponderState::reading);
|
|
}
|
|
}
|
|
// abort current operation
|
|
else if (StringEquals(clientMessage, "ABOR"))
|
|
{
|
|
CloseDataPort();
|
|
|
|
outBuf->copy("226 ABOR successful.\r\n");
|
|
Commit(ResponderState::reading);
|
|
}
|
|
// end connection
|
|
else if (StringEquals(clientMessage, "QUIT"))
|
|
{
|
|
CloseDataPort();
|
|
|
|
outBuf->copy("221 Goodbye.\r\n");
|
|
Commit();
|
|
}
|
|
// unknown command
|
|
else
|
|
{
|
|
outBuf->copy("500 Unknown command.\r\n");
|
|
Commit(ResponderState::pasvPortOpened);
|
|
}
|
|
break;
|
|
|
|
case ResponderState::uploading:
|
|
case ResponderState::sendingPasvData:
|
|
// abort current transfer
|
|
if (StringEquals(clientMessage, "ABOR"))
|
|
{
|
|
CancelUpload();
|
|
CloseDataPort();
|
|
|
|
outBuf->copy("226 ABOR successful.\r\n");
|
|
Commit(ResponderState::reading);
|
|
}
|
|
// in theory we could add support for QUIT here but it's not worth the effort
|
|
// unknown command
|
|
else
|
|
{
|
|
outBuf->copy("500 Unknown command.\r\n");
|
|
Commit(responderState);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// do nothing
|
|
break;
|
|
}
|
|
}
|
|
|
|
const char *FtpResponder::GetParameter(const char *after) const
|
|
{
|
|
const size_t commandLength = strlen(after);
|
|
if (commandLength >= ftpMessageLength)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
const char *result = clientMessage + strlen(after) + 1;
|
|
while ((*result == '\t' || *result == ' ') && *result != 0)
|
|
{
|
|
++result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void FtpResponder::ChangeDirectory(const char *newDirectory)
|
|
{
|
|
char combinedPath[FILENAME_LENGTH];
|
|
if (newDirectory[0] != 0)
|
|
{
|
|
// Prepare the new directory path
|
|
if (newDirectory[0] == '/') // Absolute path
|
|
{
|
|
SafeStrncpy(combinedPath, newDirectory, ARRAY_SIZE(combinedPath));
|
|
}
|
|
else // Relative path
|
|
{
|
|
if (StringEquals(newDirectory, "..")) // Go up
|
|
{
|
|
// Check if we're already at the root directory
|
|
if (StringEquals(currentDirectory, "/"))
|
|
{
|
|
outBuf->copy("550 Failed to change directory.\r\n");
|
|
Commit(responderState);
|
|
return;
|
|
}
|
|
|
|
// No - find the parent directory
|
|
SafeStrncpy(combinedPath, currentDirectory, ARRAY_SIZE(combinedPath));
|
|
for(int i = strlen(combinedPath) - 2; i >= 0; i--)
|
|
{
|
|
if (combinedPath[i] == '/')
|
|
{
|
|
combinedPath[i + 1] = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else // Go to child directory
|
|
{
|
|
SafeStrncpy(combinedPath, currentDirectory, ARRAY_SIZE(combinedPath));
|
|
if (!StringEndsWith(combinedPath, "/") && strlen(combinedPath) > 1)
|
|
{
|
|
SafeStrncat(combinedPath, "/", ARRAY_SIZE(combinedPath));
|
|
}
|
|
SafeStrncat(combinedPath, newDirectory, ARRAY_SIZE(combinedPath));
|
|
}
|
|
}
|
|
|
|
// Make sure the new path does not end with a slash, else FatFs won't be able to see the directory
|
|
if (StringEndsWith(combinedPath, "/") && strlen(combinedPath) > 1)
|
|
{
|
|
combinedPath[strlen(combinedPath) - 1] = 0;
|
|
}
|
|
|
|
// Verify the final path and change it if possible
|
|
if (GetPlatform()->GetMassStorage()->DirectoryExists(combinedPath))
|
|
{
|
|
SafeStrncpy(currentDirectory, combinedPath, ARRAY_SIZE(currentDirectory));
|
|
outBuf->copy("250 Directory successfully changed.\r\n");
|
|
Commit(responderState);
|
|
}
|
|
else
|
|
{
|
|
outBuf->copy("550 Failed to change directory.\r\n");
|
|
Commit(responderState);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
outBuf->copy("550 Failed to change directory.\r\n");
|
|
Commit(responderState);
|
|
}
|
|
}
|
|
|
|
void FtpResponder::CloseDataPort()
|
|
{
|
|
if (reprap.Debug(moduleWebserver))
|
|
{
|
|
debugPrintf("FTP data port is being closed\n");
|
|
}
|
|
|
|
if (dataSocket != nullptr)
|
|
{
|
|
dataSocket->Close();
|
|
dataSocket = nullptr;
|
|
}
|
|
GetNetwork()->CloseDataPort();
|
|
|
|
OutputBuffer::ReleaseAll(dataBuf);
|
|
dataBuf = nullptr;
|
|
|
|
if (fileBeingSent != nullptr)
|
|
{
|
|
fileBeingSent->Close();
|
|
fileBeingSent = nullptr;
|
|
}
|
|
}
|
|
|
|
// End
|