Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Diff: FTPRequestHandler.cpp
- Revision:
- 0:61dbbee3f903
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/FTPRequestHandler.cpp Sun Jun 19 13:59:16 2011 +0000
@@ -0,0 +1,590 @@
+
+/*
+Copyright (c) 2011 Robert Ellis (holistic [at] robellis [dot] org [dot] uk)
+
+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.
+*/
+
+#include "core/netservice.h"
+#include "FTPRequestHandler.h"
+#include "stringUtils.h"
+#include <string.h>
+#include <sstream>
+
+//#define __DEBUG
+#include "dbg/dbg.h"
+
+FTPRequestHandler::FTPRequestHandler(FTPServer* pSvr, Host* pCommandClient, TCPSocket* pCommandSocket) :
+ NetService(), m_pSvr(pSvr), m_pCommandClient(pCommandClient), m_closed(false), m_authenticated(false),
+ fileSystem()
+{
+ m_pDataSocket = NULL;
+ m_pDataClient = NULL;
+ m_command = NOOP;
+ acceptCommandSocket(pCommandSocket);
+}
+
+FTPRequestHandler::~FTPRequestHandler()
+{
+ close();
+}
+
+
+void FTPRequestHandler::acceptCommandSocket(TCPSocket* pCommandSocket)
+{
+ m_pCommandSocket = pCommandSocket;
+ m_pCommandSocket->setOnEvent(this, &FTPRequestHandler::onCommandSocketEvent);
+ onCommandSocketEvent(TCPSOCKET_ACCEPT);
+}
+
+void FTPRequestHandler::acceptDataSocket(TCPSocket* pDataSocket)
+{
+ m_pDataSocket = pDataSocket;
+ m_pDataSocket->setOnEvent(this, &FTPRequestHandler::onDataSocketEvent);
+ onDataSocketEvent(TCPSOCKET_ACCEPT);
+}
+
+bool FTPRequestHandler::openDataSocket()
+{
+ if(m_pDataSocket != NULL)
+ {
+ sendReply(125, "Data connection already open; transfer starting.");
+ return true;
+ }
+
+ m_pDataSocket = new TCPSocket;
+ Host h(IpAddr(127,0,0,1), 20, "localhost");
+ m_pDataSocket->bind(h);
+ sendReply(150, "About to open data connection.");
+ TCPSocketErr bindErr = m_pDataSocket->connect(*m_pDataClient);
+ if(bindErr == TCPSOCKET_OK)
+ {
+ m_pDataSocket->setOnEvent(this, &FTPRequestHandler::onDataSocketEvent);
+ return true;
+ }
+ else
+ {
+ closeDataSocket();
+ sendReply(425, "Can't open data connection.");
+ DBG("Error binding data socket: %d\r\n", bindErr);
+ return false;
+ }
+}
+
+
+void FTPRequestHandler::onCommandSocketEvent(TCPSocketEvent e)
+{
+ DBG("Command Event %d\r\n", e);
+
+ if(m_closed)
+ {
+ DBG("\r\nWARN: Discarded\r\n");
+ return;
+ }
+
+ switch(e)
+ {
+ case TCPSOCKET_ACCEPT:
+ sendReply(220, m_pSvr->getWelcomeMessage().c_str());
+ break;
+
+ case TCPSOCKET_READABLE:
+ DBG("Event TCPSOCKET_READABLE\r\n");
+ handleRequest();
+ break;
+
+ case TCPSOCKET_WRITEABLE:
+ DBG("Event TCPSOCKET_WRITEABLE\r\n");
+ break;
+
+ case TCPSOCKET_CONTIMEOUT:
+ case TCPSOCKET_CONRST:
+ case TCPSOCKET_CONABRT:
+ case TCPSOCKET_ERROR:
+ case TCPSOCKET_DISCONNECTED:
+ close();
+ break;
+ }
+}
+
+void FTPRequestHandler::onDataSocketEvent(TCPSocketEvent e)
+{
+ DBG("Data Event %d\r\n", e);
+
+ if(m_closed)
+ {
+ DBG("\r\nWARN: Discarded\r\n");
+ return;
+ }
+
+ switch(e)
+ {
+ case TCPSOCKET_ACCEPT:
+ sendReply(150, "Accepted data connection.");
+ handleResponse();
+ break;
+
+ case TCPSOCKET_READABLE:
+ DBG("Event TCPSOCKET_READABLE\r\n");
+ break;
+
+ case TCPSOCKET_WRITEABLE:
+ DBG("Event TCPSOCKET_WRITEABLE\r\n");
+ handleResponse();
+ break;
+
+ case TCPSOCKET_CONTIMEOUT:
+ case TCPSOCKET_CONRST:
+ case TCPSOCKET_CONABRT:
+ case TCPSOCKET_ERROR:
+ case TCPSOCKET_DISCONNECTED:
+ closeDataSocket();
+ break;
+ }
+}
+
+
+void FTPRequestHandler::close() //Close socket and destroy data
+{
+ if(m_closed)
+ {
+ return;
+ }
+
+ closeDataSocket();
+
+ m_closed = true; //Prevent recursive calling or calling on an object being destructed by someone else
+
+ if(m_pCommandSocket) //m_pTCPSocket Should only be destroyed if ownership not passed to an handler
+ {
+ m_pCommandSocket->resetOnEvent();
+ m_pCommandSocket->close();
+ delete m_pCommandSocket; //This fn might have been called by this socket (through an event), so DO NOT DESTROY IT HERE
+ }
+
+ if(m_pCommandClient)
+ {
+ delete m_pCommandClient;
+ }
+
+ if(m_pDataClient)
+ {
+ delete m_pDataClient;
+ }
+
+ NetService::close();
+}
+
+void FTPRequestHandler::closeDataSocket()
+{
+ if(m_closed)
+ {
+ return;
+ }
+
+ if(m_pDataSocket)
+ {
+ sendReply(226, "Closing data connection.");
+ m_pDataSocket->resetOnEvent();
+ m_pDataSocket->close();
+ delete m_pDataSocket;
+ m_pDataSocket = NULL;
+ }
+}
+
+
+int FTPRequestHandler::getRequest()
+{
+ char c[2];
+ request = "";
+
+ while(1)
+ {
+ int ret = m_pCommandSocket->recv(c, 1);
+
+ if(!ret)
+ {
+ break;
+ }
+
+ if(request.length() > 0 && c[0] == '\n')
+ {
+ break;
+ }
+ else
+ {
+ request += c[0];
+ }
+ }
+
+ request = trim(request);
+ return request.length();
+}
+
+
+void FTPRequestHandler::sendReply(int replyCode, const char* format, ...)
+{
+ char buf[256], fmt[256];
+ va_list args;
+ int i;
+
+ snprintf(fmt, 256, "%03u %s\r\n", replyCode, format);
+
+ va_start(args, format);
+ i = vsnprintf(buf, 256, fmt, args);
+ va_end(args);
+
+ buf[i+1] = 0;
+ DBG("Response: %s", buf);
+
+ m_pCommandSocket->send(buf, i);
+}
+
+void FTPRequestHandler::sendReply(const char* format, ...)
+{
+ char buf[256], fmt[256];
+ va_list args;
+ int i;
+
+ snprintf(fmt, 256, "%s\r\n", format);
+
+ va_start(args, format);
+ i = vsnprintf(buf, 256, fmt, args);
+ va_end(args);
+
+ buf[i+1] = 0;
+ DBG("Response: %s", buf);
+
+ m_pCommandSocket->send(buf, i);
+}
+
+void FTPRequestHandler::sendData(const char* data, int length)
+{
+ if(m_pDataSocket)
+ {
+ m_pDataSocket->send(data, length);
+ }
+}
+
+
+bool FTPRequestHandler::isAuthenticated()
+{
+ if(!m_authenticated)
+ {
+ sendReply(530, "Not logged in.");
+ }
+
+ return m_authenticated;
+}
+
+void FTPRequestHandler::sendSyntaxError()
+{
+ sendReply(501, "Syntax error in parameters");
+}
+
+
+void FTPRequestHandler::handleRequest()
+{
+ getRequest();
+ DBG("Request: %s\r\n", request.c_str());
+
+ vector<string> tokens = tokenize(request, " ");
+
+ string& command = tokens[0];
+
+ if(command.compare("USER") == 0)
+ {
+ if(tokens.size() == 2)
+ {
+ cmdUser(tokens[1]);
+ }
+ else
+ {
+ sendSyntaxError();
+ }
+ }
+ else if(command.compare("PASS") == 0)
+ {
+ if(tokens.size() == 2)
+ {
+ cmdPass(tokens[1]);
+ }
+ else
+ {
+ sendSyntaxError();
+ }
+ }
+ else if(command.compare("QUIT") == 0)
+ {
+ cmdQuit();
+ }
+ else if(command.compare("FEAT") == 0)
+ {
+ cmdFeat();
+ }
+ else if(command.compare("PORT") == 0)
+ {
+ if(tokens.size() == 2)
+ {
+ cmdPort(tokens[1]);
+ }
+ else
+ {
+ sendSyntaxError();
+ }
+ }
+ else if(command.compare("PASV") == 0)
+ {
+ cmdPasv();
+ }
+ else if(command.compare("TYPE") == 0)
+ {
+ if(tokens.size() == 2)
+ {
+ cmdType(tokens[1], "");
+ }
+ else if(tokens.size() == 3)
+ {
+ cmdType(tokens[1], tokens[2]);
+ }
+ else
+ {
+ sendSyntaxError();
+ }
+
+ }
+ else if(command.compare("PWD") == 0)
+ {
+ cmdPwd();
+ }
+ else if(command.compare("CWD") == 0)
+ {
+ if(tokens.size() == 2)
+ {
+ cmdCwd(tokens[1]);
+ }
+ else
+ {
+ sendSyntaxError();
+ }
+ }
+ else if(command.compare("CDUP") == 0)
+ {
+ cmdCdup();
+ }
+ else if(command.compare("LIST") == 0)
+ {
+ cmdList();
+ }
+ else if(command.compare("MLSD") == 0)
+ {
+ cmdMlsd();
+ }
+ else
+ {
+ sendReply(202, "Command not implemented.");
+ }
+}
+
+
+void FTPRequestHandler::handleResponse()
+{
+ if(m_command == MLSD)
+ {
+ sendData(m_data_mlsd.c_str(), m_data_mlsd.length());
+ closeDataSocket();
+ m_data_mlsd = "";
+ }
+ else
+ {
+
+ }
+}
+
+
+void FTPRequestHandler::cmdUser(const string& username)
+{
+ m_user.setUsername(username);
+ sendReply(331, "User name okay, need password.");
+ //sendReply(530, "Not logged in.");
+ //sendReply(230, "User logged in, proceed.");
+}
+
+void FTPRequestHandler::cmdPass(const string& password)
+{
+ FTPUser* user = m_pSvr->checkUser(m_user.getUsername(), password);
+
+ if(user != NULL)
+ {
+ m_user = *user;
+ m_authenticated = true;
+ sendReply(230, "User logged in, proceed.");
+ }
+ else
+ {
+ sendReply(530, "Not logged in.");
+ close();
+ }
+}
+
+void FTPRequestHandler::cmdQuit()
+{
+ sendReply(221, "Service closing control connection.");
+ close();
+}
+
+void FTPRequestHandler::cmdFeat()
+{
+ if(isAuthenticated())
+ {
+ //List extended features
+ sendReply("211-Extensions supported:");
+ sendReply(" MLSD");
+ sendReply(211, "End");
+ }
+}
+
+void FTPRequestHandler::cmdPort(const string& ip_port)
+{
+ if(isAuthenticated())
+ {
+ vector<string> ip_port_vector = tokenize(ip_port, ",");
+ if(ip_port_vector.size() != 6)
+ {
+ sendSyntaxError();
+ return;
+ }
+
+ if(m_pDataSocket)
+ {
+ sendReply(500, "Sorry, only one transfer at a time.");
+ return;
+ }
+
+ uint8_t ip1 = atoi(ip_port_vector[0].c_str());
+ uint8_t ip2 = atoi(ip_port_vector[1].c_str());
+ uint8_t ip3 = atoi(ip_port_vector[2].c_str());
+ uint8_t ip4 = atoi(ip_port_vector[3].c_str());
+ uint8_t port1 = atoi(ip_port_vector[4].c_str());
+ uint8_t port2 = atoi(ip_port_vector[5].c_str());
+ int port = (port1 << 8) | port2;
+
+ m_pDataClient = new Host(IpAddr(ip1, ip2, ip3, ip4), port);
+
+ sendReply(200, "PORT Command okay.");
+ }
+}
+
+void FTPRequestHandler::cmdPasv()
+{
+ if(isAuthenticated())
+ {
+ if(m_pDataSocket)
+ {
+ sendReply(500, "Sorry, only one transfer at a time.");
+ return;
+ }
+
+ int port = m_pSvr->getDataPort();
+ const IpAddr& ip = m_pSvr->getIp();
+ uint8_t port2 = (port >> 0) & 0xFF;
+ uint8_t port1 = (port >> 8) & 0xFF;
+ sendReply(227, "Entering passive mode (%u,%u,%u,%u,%u,%u)", ip[0], ip[1], ip[2], ip[3], port1, port2);
+ }
+}
+
+void FTPRequestHandler::cmdType(const string& type, const string& arg)
+{
+ if(isAuthenticated())
+ {
+ sendReply(200, "TYPE ignored (always I)");
+ }
+}
+
+
+
+void FTPRequestHandler::cmdPwd()
+{
+ if(isAuthenticated())
+ {
+ string workDir = fileSystem.getWorkingDirectory();
+ sendReply(257, "\"%s\"", workDir.c_str());
+ }
+}
+
+void FTPRequestHandler::cmdCwd(const string& dir)
+{
+ if(isAuthenticated())
+ {
+ if(fileSystem.changeWorkingDirectory(dir))
+ {
+ sendReply(250, "CWD successful.");
+ }
+ else
+ {
+ sendReply(550, "Directory not found.");
+ }
+ }
+}
+
+void FTPRequestHandler::cmdCdup()
+{
+ cmdCwd("..");
+}
+
+void FTPRequestHandler::cmdList()
+{
+ cmdMlsd();
+}
+
+
+void FTPRequestHandler::cmdMlsd()
+{
+ if(isAuthenticated())
+ {
+ m_command = MLSD;
+ m_data_mlsd = "";
+
+ m_data_mlsd += "Type=cdir; ";
+ m_data_mlsd += fileSystem.getWorkingDirectory();
+ m_data_mlsd += "\r\n";
+
+ list<File> fileList = fileSystem.getFileList();
+ for(list<File>::iterator it = fileList.begin(); it != fileList.end(); it++)
+ {
+ File& file = *it;
+
+ if(file.getType() == TYPE_FILE)
+ {
+ m_data_mlsd += "Type=file;";
+ }
+ else if(file.getType() == TYPE_DIR)
+ {
+ m_data_mlsd += "Type=dir;";
+ }
+
+ m_data_mlsd += " ";
+ m_data_mlsd += file.getName();
+ m_data_mlsd += "\r\n";
+ }
+
+ DBG("\r\n%s", m_data_mlsd.c_str());
+ }
+}
+