]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Converted some of the new FTP code to use SBuf and Tokenizer
authorAlex Rousskov <rousskov@measurement-factory.com>
Thu, 7 Aug 2014 17:31:05 +0000 (11:31 -0600)
committerAlex Rousskov <rousskov@measurement-factory.com>
Thu, 7 Aug 2014 17:31:05 +0000 (11:31 -0600)
instead of MemBuf, String, and c-string manipulations.

src/Makefile.am
src/clients/FtpRelay.cc
src/ftp/Elements.cc [new file with mode: 0644]
src/ftp/Elements.h [new file with mode: 0644]
src/ftp/Makefile.am
src/parser/testTokenizer.cc
src/servers/FtpServer.cc
src/servers/FtpServer.h

index 0fa15d2f078e2ebb1c01ba107d4a9a19d2f2ecf0..f42403cd44cb2343d6df058570942486c0619b6a 100644 (file)
@@ -673,15 +673,15 @@ squid_DEPENDENCIES = \
        $(AUTH_LIBS) \
        acl/libapi.la \
        base/libbase.la \
-       ftp/libftp.la \
        clients/libclients.la \
-       servers/libservers.la \
+       ftp/libftp.la \
        libsquid.la \
        ip/libip.la \
        fs/libfs.la \
        format/libformat.la \
        ipc/libipc.la \
-       mgr/libmgr.la
+       mgr/libmgr.la \
+       servers/libservers.la
 
 if ENABLE_LOADABLE_MODULES
 squid_SOURCES += $(LOADABLE_MODULES_SOURCES)
@@ -1615,6 +1615,7 @@ tests_testCacheManager_LDADD = \
        acl/libacls.la \
        acl/libstate.la \
        acl/libapi.la \
+       parser/libsquid-parser.la \
        base/libbase.la \
        libsquid.la \
        ip/libip.la \
@@ -2050,6 +2051,7 @@ tests_testEvent_LDADD = \
        acl/libacls.la \
        acl/libstate.la \
        acl/libapi.la \
+       parser/libsquid-parser.la \
        base/libbase.la \
        libsquid.la \
        ip/libip.la \
@@ -2301,6 +2303,7 @@ tests_testEventLoop_LDADD = \
        acl/libacls.la \
        acl/libstate.la \
        acl/libapi.la \
+       parser/libsquid-parser.la \
        base/libbase.la \
        libsquid.la \
        ip/libip.la \
@@ -2545,6 +2548,7 @@ tests_test_http_range_LDADD = \
        acl/libacls.la \
        acl/libstate.la \
        acl/libapi.la \
+       parser/libsquid-parser.la \
        libsquid.la \
        ip/libip.la \
        fs/libfs.la \
@@ -2837,6 +2841,7 @@ tests_testHttpRequest_LDADD = \
        acl/libacls.la \
        acl/libstate.la \
        acl/libapi.la \
+       parser/libsquid-parser.la \
        libsquid.la \
        ip/libip.la \
        fs/libfs.la \
@@ -3682,6 +3687,7 @@ tests_testURL_LDADD = \
        eui/libeui.la \
        acl/libstate.la \
        acl/libapi.la \
+       parser/libsquid-parser.la \
        base/libbase.la \
        libsquid.la \
        ip/libip.la \
index 41636868706e3beecda10043e1b2677d5424335a..e4f7b7b75734637917cd6bd90b982eb505fbed8a 100644 (file)
@@ -12,6 +12,7 @@
 #include "ftp/Parsing.h"
 #include "HttpHdrCc.h"
 #include "HttpRequest.h"
+#include "SBuf.h"
 #include "servers/FtpServer.h"
 #include "Server.h"
 #include "SquidTime.h"
@@ -508,14 +509,13 @@ Ftp::Relay::sendCommand()
         return;
     }
 
-    static MemBuf mb;
-    mb.reset();
+    SBuf buf;
     if (params.size() > 0)
-        mb.Printf("%s %s%s", cmd.termedBuf(), params.termedBuf(), Ftp::crlf);
+        buf.Printf("%s %s%s", cmd.termedBuf(), params.termedBuf(), Ftp::crlf);
     else
-        mb.Printf("%s%s", cmd.termedBuf(), Ftp::crlf);
+        buf.Printf("%s%s", cmd.termedBuf(), Ftp::crlf);
 
-    writeCommand(mb.content());
+    writeCommand(buf.c_str());
 
     state =
         serverState() == fssHandleCdup ? SENT_CDUP :
diff --git a/src/ftp/Elements.cc b/src/ftp/Elements.cc
new file mode 100644 (file)
index 0000000..6e05370
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * DEBUG: section 09    File Transfer Protocol (FTP)
+ */
+
+#include "squid.h"
+#include "ftp/Elements.h"
+#include "SBuf.h"
+
+const SBuf &
+Ftp::cmdAppe()
+{
+    static const SBuf cmd("APPE");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdAuth()
+{
+    static const SBuf cmd("AUTH");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdCwd()
+{
+    static const SBuf cmd("CWD");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdDele()
+{
+    static const SBuf cmd("DELE");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdEprt()
+{
+    static const SBuf cmd("EPRT");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdEpsv()
+{
+    static const SBuf cmd("EPSV");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdList()
+{
+    static const SBuf cmd("LIST");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdMkd()
+{
+    static const SBuf cmd("MKD");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdMlsd()
+{
+    static const SBuf cmd("MLSD");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdMlst()
+{
+    static const SBuf cmd("MLST");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdNlst()
+{
+    static const SBuf cmd("NLST");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdRetr()
+{
+    static const SBuf cmd("RETR");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdRmd()
+{
+    static const SBuf cmd("RMD");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdRnfr()
+{
+    static const SBuf cmd("RNFR");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdRnto()
+{
+    static const SBuf cmd("RNTO");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdSmnt()
+{
+    static const SBuf cmd("SMNT");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdStat()
+{
+    static const SBuf cmd("STAT");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdStor()
+{
+    static const SBuf cmd("STOR");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdStou()
+{
+    static const SBuf cmd("STOU");
+    return cmd;
+}
+
+const SBuf &
+Ftp::cmdUser()
+{
+    static const SBuf cmd("USER");
+    return cmd;
+}
diff --git a/src/ftp/Elements.h b/src/ftp/Elements.h
new file mode 100644 (file)
index 0000000..6d77e86
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef SQUID_FTP_ELEMENTS_H
+#define SQUID_FTP_ELEMENTS_H
+
+class SBuf;
+
+namespace Ftp {
+
+/* FTP Commands used by Squid. ALLCAPS case. Safe for static initializaton. */
+const SBuf &cmdAppe();
+const SBuf &cmdAuth();
+const SBuf &cmdCwd();
+const SBuf &cmdDele();
+const SBuf &cmdEprt();
+const SBuf &cmdEpsv();
+const SBuf &cmdList();
+const SBuf &cmdMkd();
+const SBuf &cmdMlsd();
+const SBuf &cmdMlst();
+const SBuf &cmdNlst();
+const SBuf &cmdRetr();
+const SBuf &cmdRmd();
+const SBuf &cmdRnfr();
+const SBuf &cmdRnto();
+const SBuf &cmdSmnt();
+const SBuf &cmdStat();
+const SBuf &cmdStor();
+const SBuf &cmdStou();
+const SBuf &cmdUser();
+
+} // namespace Ftp
+
+#endif /* SQUID_FTP_ELEMENTS_H */
index 4c4d083c0f4c134ad14e305359972c1ed42a6e5f..6117116d9c65e9865bcb8f3374ce5835042661ab 100644 (file)
@@ -3,5 +3,7 @@ include $(top_srcdir)/src/Common.am
 noinst_LTLIBRARIES = libftp.la
 
 libftp_la_SOURCES = \
+       Elements.cc \
+       Elements.h \
        Parsing.cc \
        Parsing.h
index ac4dec4bf088a8af65f0bfe049fd206a00ef214a..7443e07eeb46dd3b5e1204f4f262a2ac57d89d8c 100644 (file)
@@ -60,8 +60,8 @@ testTokenizer::testTokenizerSkip()
     CPPUNIT_ASSERT(t.prefix(s,alpha));
     CPPUNIT_ASSERT_EQUAL(SBuf("GET"),s);
 
-    // test skip testing character set
-    CPPUNIT_ASSERT(t.skip(whitespace));
+    // test skipping one character from a character set
+    CPPUNIT_ASSERT(t.skipOne(whitespace));
     // check that skip was right
     CPPUNIT_ASSERT(t.prefix(s,alpha));
     CPPUNIT_ASSERT_EQUAL(SBuf("http"),s);
@@ -73,10 +73,14 @@ testTokenizer::testTokenizerSkip()
     CPPUNIT_ASSERT_EQUAL(SBuf("resource"),s);
 
     // no skip
-    CPPUNIT_ASSERT(!t.skip(alpha));
+    CPPUNIT_ASSERT(!t.skipOne(alpha));
     CPPUNIT_ASSERT(!t.skip(SBuf("://")));
     CPPUNIT_ASSERT(!t.skip('a'));
 
+    // test skipping all characters from a character set while looking at .com
+    CPPUNIT_ASSERT(t.skip('.'));
+    CPPUNIT_ASSERT_EQUAL(static_cast<SBuf::size_type>(3), t.skipAll(alpha));
+    CPPUNIT_ASSERT(t.remaining().startsWith(SBuf("/path")));
 }
 
 void
index effd90e8cdaa812abde490a388344de21b2049a9..37130c2a1c57d65898220a2e2a476b07b014420f 100644 (file)
@@ -3,6 +3,7 @@
  */
 
 #include "squid.h"
+#include "base/CharacterSet.h"
 #include "base/Subscription.h"
 #include "clientStream.h"
 #include "comm/ConnOpener.h"
 #include "client_side_request.h"
 #include "errorpage.h"
 #include "fd.h"
+#include "ftp/Elements.h"
 #include "ftp/Parsing.h"
 #include "globals.h"
 #include "HttpHdrCc.h"
 #include "ip/tools.h"
 #include "ipc/FdNotes.h"
+#include "parser/Tokenizer.h"
 #include "servers/forward.h"
 #include "servers/FtpServer.h"
 #include "SquidConfig.h"
 #include "StatCounters.h"
 #include "tools.h"
 
+#include <set>
+#include <map>
+
 CBDATA_NAMESPACED_CLASS_INIT(Ftp, Server);
 
 namespace Ftp {
 static void PrintReply(MemBuf &mb, const HttpReply *reply, const char *const prefix = "");
-static bool SupportedCommand(const String &name);
-static bool CommandHasPathParameter(const String &cmd);
+static bool SupportedCommand(const SBuf &name);
+static bool CommandHasPathParameter(const SBuf &cmd);
 };
 
 Ftp::Server::Server(const MasterXaction::Pointer &xact):
@@ -76,7 +82,7 @@ Ftp::Server::start()
         char buf[MAX_IPSTRLEN];
         clientConnection->local.toUrl(buf, MAX_IPSTRLEN);
         host = buf;
-        calcUri();
+        calcUri(NULL);
         debugs(33, 5, "FTP transparent URL: " << uri);
     }
 
@@ -315,24 +321,24 @@ Ftp::Server::resetLogin(const char *reason)
 
 /// computes uri member from host and, if tracked, working dir with file name
 void
-Ftp::Server::calcUri(const char *file)
+Ftp::Server::calcUri(const SBuf *file)
 {
     uri = "ftp://";
     uri.append(host);
-    if (port->ftp_track_dirs && master.workingDir.size()) {
+    if (port->ftp_track_dirs && master.workingDir.length()) {
         if (master.workingDir[0] != '/')
             uri.append("/");
         uri.append(master.workingDir);
     }
 
-    if (uri[uri.size() - 1] != '/')
+    if (uri[uri.length() - 1] != '/')
         uri.append("/");
 
     if (port->ftp_track_dirs && file) {
-        // remove any '/' from the beginning of path
-        while (*file == '/')
-            ++file;
-        uri.append(file);
+        static const CharacterSet Slash("/", "/");
+        Parser::Tokenizer tok(*file);
+        tok.skipAll(Slash);
+        uri.append(tok.remaining());
     }
 }
 
@@ -347,7 +353,7 @@ Ftp::Server::listenForDataConnection()
     conn->flags = COMM_NONBLOCKING;
     conn->local = transparent() ? port->s : clientConnection->local;
     conn->local.port(0);
-    const char *const note = uri.termedBuf();
+    const char *const note = uri.c_str();
     comm_open_listener(SOCK_STREAM, IPPROTO_TCP, conn, note);
     if (!Comm::IsConnOpen(conn)) {
         debugs(5, DBG_CRITICAL, "comm_open_listener failed for FTP data: " <<
@@ -514,13 +520,28 @@ Ftp::Server::changeState(const ServerState newState, const char *reason)
 
 /// whether the given FTP command has a pathname parameter
 static bool
-Ftp::CommandHasPathParameter(const String &cmd)
-{
-    static const char *pathCommandsStr[]= {"CWD","SMNT", "RETR", "STOR", "APPE",
-                                           "RNFR", "RNTO", "DELE", "RMD", "MKD",
-                                           "LIST", "NLST", "STAT", "MLSD", "MLST"};
-    static const std::set<String> pathCommands(pathCommandsStr, pathCommandsStr + sizeof(pathCommandsStr)/sizeof(pathCommandsStr[0]));
-    return pathCommands.find(cmd) != pathCommands.end();
+Ftp::CommandHasPathParameter(const SBuf &cmd)
+{
+    static std::set<SBuf> PathedCommands;
+    if (!PathedCommands.size()) {
+        PathedCommands.insert(cmdMlst());
+        PathedCommands.insert(cmdMlsd());
+        PathedCommands.insert(cmdStat());
+        PathedCommands.insert(cmdNlst());
+        PathedCommands.insert(cmdList());
+        PathedCommands.insert(cmdMkd());
+        PathedCommands.insert(cmdRmd());
+        PathedCommands.insert(cmdDele());
+        PathedCommands.insert(cmdRnto());
+        PathedCommands.insert(cmdRnfr());
+        PathedCommands.insert(cmdAppe());
+        PathedCommands.insert(cmdStor());
+        PathedCommands.insert(cmdRetr());
+        PathedCommands.insert(cmdSmnt());
+        PathedCommands.insert(cmdCwd());
+    }
+
+    return PathedCommands.find(cmd) != PathedCommands.end();
 }
 
 /// Parses a single FTP request on the control connection.
@@ -528,73 +549,65 @@ Ftp::CommandHasPathParameter(const String &cmd)
 ClientSocketContext *
 Ftp::Server::parseOneRequest(Http::ProtocolVersion &ver)
 {
-    ver = Http::ProtocolVersion(1, 1);
-
-    // TODO: Use tokenizer for parsing instead of raw pointer manipulation.
-    const char *inBuf = in.buf.rawContent();
+    // OWS <command> [ RWS <parameter> ] OWS LF
+    static const CharacterSet WhiteSpace = CharacterSet("Ftp::WS", " \f\r\t\v");
+    static const CharacterSet BlackSpace = WhiteSpace.complement("!Ftp::WS");
+    static const CharacterSet NewLine = CharacterSet("NL", "\n");
+    static const CharacterSet OldLine = NewLine.complement("!NL");
 
-    const char *const eor =
-        static_cast<const char *>(memchr(inBuf, '\n',
-            min(static_cast<size_t>(in.buf.length()), Config.maxRequestHeaderSize)));
+    // This set is used to ignore empty commands without allowing an attacker
+    // to keep us endlessly busy by feeding us whitespace or empty commands.
+    static const CharacterSet LeadingSpace = (WhiteSpace + NewLine).rename("Ftp::LeadingSpace");
 
-    if (eor == NULL && in.buf.length() >= Config.maxRequestHeaderSize) {
-        changeState(fssError, "huge req");
-        writeEarlyReply(421, "Too large request");
-        return NULL;
-    }
+    SBuf cmd;
+    SBuf params;
 
-    if (eor == NULL) {
-        debugs(33, 5, "Incomplete request, waiting for end of request");
-        return NULL;
-    }
+    Parser::Tokenizer tok(in.buf);
 
-    const size_t req_sz = eor + 1 - inBuf;
+    (void)tok.skipAll(LeadingSpace); // leading OWS and empty commands
+    const bool parsed = tok.prefix(cmd, BlackSpace); // required command
 
-    // skip leading whitespaces
-    const char *boc = inBuf; // beginning of command
-    while (boc < eor && isspace(*boc)) ++boc;
-    if (boc >= eor) {
-        debugs(33, 5, "Empty request, ignoring");
-        consumeInput(req_sz);
-        return NULL;
+    // note that the condition below will eat either RWS or trailing OWS
+    if (parsed && tok.skipAll(WhiteSpace) && tok.prefix(params, OldLine)) {
+        // now params may include trailing OWS
+        // TODO: Support right-trimming using CharacterSet in Tokenizer instead
+        static const SBuf bufWhiteSpace("\r\t ");
+        params.trim(bufWhiteSpace, false, true);
     }
 
-    const char *eoc = boc; // end of command
-    while (eoc < eor && !isspace(*eoc)) ++eoc;
-    in.buf.setAt(eoc - inBuf, '\0');
-
-    const char *bop = eoc + 1; // beginning of parameter
-    while (bop < eor && isspace(*bop)) ++bop;
-    if (bop < eor) {
-        const char *eop = eor - 1;
-        while (isspace(*eop)) --eop;
-        assert(eop >= bop);
-        in.buf.setAt(eop + 1 - inBuf, '\0');
-    } else
-        bop = NULL;
+    // technically, we may skip multiple NLs below, but that is OK
+    if (!parsed || !tok.skipAll(NewLine)) { // did not find terminating LF yet
+        // we need more data, but can we buffer more?
+        if (in.buf.length() >= Config.maxRequestHeaderSize) {
+            changeState(fssError, "huge req");
+            writeEarlyReply(421, "Huge request");
+            return NULL;
+        } else {
+            debugs(33, 5, "Waiting for more, up to " <<
+                   (Config.maxRequestHeaderSize - in.buf.length()));
+            return NULL;
+        }
+    }
 
-    debugs(33, 7, "Parsed FTP command " << boc << " with " <<
-           (bop == NULL ? "no " : "") << "parameters" <<
-           (bop != NULL ? ": " : "") << bop);
+    Must(parsed && cmd.length());
+    consumeInput(tok.parsedSize()); // TODO: Would delaying optimize copying?
 
-    // TODO: Use SBuf instead of String
-    const String cmd = boc;
-    String params = bop;
+    debugs(33, 2, ">>ftp " << cmd << (params.isEmpty() ? "" : " ") << params);
 
-    consumeInput(req_sz);
+    cmd.toUpper(); // this should speed up and simplify future comparisons
 
     // interception cases do not need USER to calculate the uri
     if (!transparent()) {
         if (!master.clientReadGreeting) {
             // the first command must be USER
-            if (!pinning.pinned && cmd.caseCmp("USER") != 0) {
+            if (!pinning.pinned && cmd != cmdUser()) {
                 writeEarlyReply(530, "Must login first");
                 return NULL;
             }
         }
 
         // process USER request now because it sets FTP peer host name
-        if (cmd.caseCmp("USER") == 0 && !handleUserRequest(cmd, params))
+        if (cmd == cmdUser() && !handleUserRequest(cmd, params))
             return NULL;
     }
 
@@ -604,22 +617,23 @@ Ftp::Server::parseOneRequest(Http::ProtocolVersion &ver)
     }
 
     const HttpRequestMethod method =
-        !cmd.caseCmp("APPE") || !cmd.caseCmp("STOR") || !cmd.caseCmp("STOU") ?
+        cmd == cmdAppe() || cmd == cmdStor() || cmd == cmdStou() ?
         Http::METHOD_PUT : Http::METHOD_GET;
 
-    const char *aPath = params.size() > 0 && CommandHasPathParameter(cmd) ?
-        params.termedBuf() : NULL;
-    calcUri(aPath);
-    char *newUri = xstrdup(uri.termedBuf());
+    const SBuf *path = params.length() && CommandHasPathParameter(cmd) ?
+        &params : NULL;
+    calcUri(path);
+    char *newUri = xstrdup(uri.c_str());
     HttpRequest *const request = HttpRequest::CreateFromUrlAndMethod(newUri, method);
     if (!request) {
         debugs(33, 5, "Invalid FTP URL: " << uri);
         writeEarlyReply(501, "Invalid host");
-        uri.clean();
+        uri.clear();
         safe_free(newUri);
         return NULL;
     }
 
+    ver = Http::ProtocolVersion(1, 1);
     request->flags.ftpNative = true;
     request->http_ver = ver;
 
@@ -627,9 +641,8 @@ Ftp::Server::parseOneRequest(Http::ProtocolVersion &ver)
     request->flags.cachable = false; // XXX: reset later by maybeCacheable()
     request->flags.noCache = true;
 
-    request->header.putStr(HDR_FTP_COMMAND, cmd.termedBuf());
-    request->header.putStr(HDR_FTP_ARGUMENTS, params.termedBuf() != NULL ?
-                           params.termedBuf() : "");
+    request->header.putStr(HDR_FTP_COMMAND, cmd.c_str());
+    request->header.putStr(HDR_FTP_ARGUMENTS, params.c_str()); // may be ""
     if (method == Http::METHOD_PUT) {
         request->header.putStr(HDR_EXPECT, "100-continue");
         request->header.putStr(HDR_TRANSFER_ENCODING, "chunked");
@@ -638,7 +651,7 @@ Ftp::Server::parseOneRequest(Http::ProtocolVersion &ver)
     ClientHttpRequest *const http = new ClientHttpRequest(this);
     http->request = request;
     HTTPMSGLOCK(http->request);
-    http->req_sz = req_sz;
+    http->req_sz = tok.parsedSize();
     http->uri = newUri;
 
     ClientSocketContext *const result =
@@ -718,29 +731,29 @@ Ftp::Server::handleFeatReply(const HttpReply *reply, StoreIOBuffer data)
             // assume RFC 2389 FEAT response format, quoted by Squid:
             // <"> SP NAME [SP PARAMS] <">
             // but accommodate MS servers sending four SPs before NAME
-            if (e->value.size() < 4)
-                continue;
-            const char *raw = e->value.termedBuf();
-            if (raw[0] != '"' || raw[1] != ' ')
-                continue;
-            const char *beg = raw + 1 + strspn(raw + 1, " "); // after quote and spaces
+
             // command name ends with (SP parameter) or quote
-            const char *end = beg + strcspn(beg, " \"");
+            static const CharacterSet AfterFeatNameChars("AfterFeatName", " \"");
+            static const CharacterSet FeatNameChars = AfterFeatNameChars.complement("FeatName");
 
-            if (end <= beg)
+            Parser::Tokenizer tok(SBuf(e->value.termedBuf()));
+            if (!tok.skip('"') && !tok.skip(' '))
                 continue;
 
-            // compute the number of spaces before the command
-            prependSpaces = beg - raw - 1;
+            // optional spaces; remember their number to accomodate MS servers
+            prependSpaces = 1 + tok.skipAll(CharacterSet::SP);
 
-            const String cmd = e->value.substr(beg-raw, end-raw);
+            SBuf cmd;
+            if (!tok.prefix(cmd, FeatNameChars))
+                continue;
+            cmd.toUpper();
 
             if (!Ftp::SupportedCommand(cmd))
                 filteredHeader.delAt(pos, deletedCount);
 
-            if (cmd == "EPRT")
+            if (cmd == cmdEprt())
                 hasEPRT = true;
-            else if (cmd == "EPSV")
+            else if (cmd == cmdEpsv())
                 hasEPSV = true;
         }
     }
@@ -823,7 +836,7 @@ void
 Ftp::Server::handleErrorReply(const HttpReply *reply, StoreIOBuffer data)
 {
     if (!pinning.pinned) // we failed to connect to server
-        uri.clean();
+        uri.clear();
     // 421: we will close due to fssError
     writeErrorReply(reply, 421);
 }
@@ -1193,76 +1206,80 @@ Ftp::Server::handleRequest(String &cmd, String &params) {
                "\n----------");
     }
 
-    // TODO: optimize using a static map with case-insensitive lookup
-    static std::pair<const char*, RequestHandler> handlers[] = {
-        std::make_pair("LIST", &Ftp::Server::handleDataRequest),
-        std::make_pair("NLST", &Ftp::Server::handleDataRequest),
-        std::make_pair("MLSD", &Ftp::Server::handleDataRequest),
-        std::make_pair("FEAT", &Ftp::Server::handleFeatRequest),
-        std::make_pair("PASV", &Ftp::Server::handlePasvRequest),
-        std::make_pair("PORT", &Ftp::Server::handlePortRequest),
-        std::make_pair("RETR", &Ftp::Server::handleDataRequest),
-        std::make_pair("EPRT", &Ftp::Server::handleEprtRequest),
-        std::make_pair("EPSV", &Ftp::Server::handleEpsvRequest),
-        std::make_pair("CWD", &Ftp::Server::handleCwdRequest),
-        std::make_pair("PASS", &Ftp::Server::handlePassRequest),
-        std::make_pair("CDUP", &Ftp::Server::handleCdupRequest)
-    };
+    // TODO: When HttpHeader uses SBuf, change keys to SBuf
+    typedef std::map<const std::string, RequestHandler> RequestHandlers;
+    static RequestHandlers handlers;
+    if (!handlers.size()) {
+        handlers["LIST"] = &Ftp::Server::handleDataRequest;
+        handlers["NLST"] = &Ftp::Server::handleDataRequest;
+        handlers["MLSD"] = &Ftp::Server::handleDataRequest;
+        handlers["FEAT"] = &Ftp::Server::handleFeatRequest;
+        handlers["PASV"] = &Ftp::Server::handlePasvRequest;
+        handlers["PORT"] = &Ftp::Server::handlePortRequest;
+        handlers["RETR"] = &Ftp::Server::handleDataRequest;
+        handlers["EPRT"] = &Ftp::Server::handleEprtRequest;
+        handlers["EPSV"] = &Ftp::Server::handleEpsvRequest;
+        handlers["CWD"] = &Ftp::Server::handleCwdRequest;
+        handlers["PASS"] = &Ftp::Server::handlePassRequest;
+        handlers["CDUP"] = &Ftp::Server::handleCdupRequest;
+    }
 
     RequestHandler handler = NULL;
     if (request->method == Http::METHOD_PUT)
         handler = &Ftp::Server::handleUploadRequest;
     else {
-        for (size_t i = 0; i < sizeof(handlers) / sizeof(*handlers); ++i) {
-            if (cmd.caseCmp(handlers[i].first) == 0) {
-                handler = handlers[i].second;
-                break;
-            }
-        }
+        const RequestHandlers::const_iterator hi = handlers.find(cmd.termedBuf());
+        if (hi != handlers.end())
+            handler = hi->second;
     }
 
-    // TODO: complain about unknown commands
-    return handler != NULL ? (this->*handler)(cmd, params) : true;
+    if (!handler) {
+        debugs(11, 7, "forwarding " << cmd << " as is, no post-processing");
+        return true;
+    }
+
+    return (this->*handler)(cmd, params);
 }
 
 /// Called to parse USER command, which is required to create an HTTP request
 /// wrapper. Thus, errors are handled with writeEarlyReply() here.
 bool
-Ftp::Server::handleUserRequest(const String &cmd, String &params)
+Ftp::Server::handleUserRequest(const SBuf &cmd, SBuf &params)
 {
-    if (params.size() == 0) {
+    if (params.isEmpty()) {
         writeEarlyReply(501, "Missing username");
         return false;
     }
 
     // find the [end of] user name
-    const String::size_type eou = params.rfind('@');
-    if (eou == String::npos || eou + 1 >= params.size()) {
+    const SBuf::size_type eou = params.rfind('@');
+    if (eou == SBuf::npos || eou + 1 >= params.length()) {
         writeEarlyReply(501, "Missing host");
         return false;
     }
-    // const String login = params.substr(0, eou);
 
     // Determine the intended destination.
-    host = params.substr(eou + 1, params.size());
+    host = params.substr(eou + 1, params.length());
     // If we can parse it as raw IPv6 address, then surround with "[]".
     // Otherwise (domain, IPv4, [bracketed] IPv6, garbage, etc), use as is.
-    if (host.pos(":")) {
-        char ipBuf[MAX_IPSTRLEN];
-        Ip::Address ipa;
-        ipa = host.termedBuf();
+    if (host.find(':') != SBuf::npos) {
+        const Ip::Address ipa(host.c_str());
         if (!ipa.isAnyAddr()) {
+            char ipBuf[MAX_IPSTRLEN];
             ipa.toHostStr(ipBuf, MAX_IPSTRLEN);
             host = ipBuf;
         }
     }
 
-    String oldUri;
+    // const SBuf login = params.substr(0, eou);
+    params.chop(0, eou); // leave just the login part for the peer
+
+    SBuf oldUri;
     if (master.clientReadGreeting)
         oldUri = uri;
 
     master.workingDir = NULL;
-    calcUri();
+    calcUri(NULL);
 
     if (!master.clientReadGreeting) {
         debugs(11, 3, "set URI to " << uri);
@@ -1275,8 +1292,6 @@ Ftp::Server::handleUserRequest(const String &cmd, String &params)
         resetLogin("URI reset");
     }
 
-    params.cut(eou);
-
     return true;
 }
 
@@ -1589,18 +1604,18 @@ Ftp::Server::setReply(const int code, const char *msg)
 
 /// Whether Squid FTP gateway supports a given feature (e.g., a command).
 static bool
-Ftp::SupportedCommand(const String &name)
+Ftp::SupportedCommand(const SBuf &name)
 {
-    static std::set<std::string> BlackList;
+    static std::set<SBuf> BlackList;
     if (BlackList.empty()) {
         /* Add FTP commands that Squid cannot gateway correctly */
 
         // we probably do not support AUTH TLS.* and AUTH SSL,
         // but let's disclaim all AUTH support to KISS, for now
-        BlackList.insert("AUTH");
+        BlackList.insert(cmdAuth());
     }
 
     // we claim support for all commands that we do not know about
-    return BlackList.find(name.termedBuf()) == BlackList.end();
+    return BlackList.find(name) == BlackList.end();
 }
 
index 9550c045174dae19d38deb5d5fd288c42f3f54b5..635321ba534b9d2ba61ef78c638e7e99b0d9213f 100644 (file)
@@ -32,7 +32,7 @@ class MasterState
 {
 public:
     Ip::Address clientDataAddr; ///< address of our FTP client data connection
-    String workingDir;
+    SBuf workingDir;
     ServerState serverState; ///< what our FTP server is doing
     bool clientReadGreeting; ///< whether our FTP client read their FTP server greeting
 
@@ -81,9 +81,9 @@ protected:
     bool createDataConnection(Ip::Address cltAddr);
     void closeDataConnection();
 
-    void calcUri(const char *file = NULL);
+    void calcUri(const SBuf *file);
     void changeState(const Ftp::ServerState newState, const char *reason);
-    bool handleUserRequest(const String &cmd, String &params);
+    bool handleUserRequest(const SBuf &cmd, SBuf &params);
     bool checkDataConnPost() const;
     void replyDataWritingCheckpoint();
     void maybeReadUploadData();
@@ -130,8 +130,8 @@ private:
     void shovelUploadData();
     void resetLogin(const char *reason);
 
-    String uri; ///< a URI reconstructed from various FTP message details
-    String host; ///< intended dest. of a transparently intercepted FTP conn
+    SBuf uri; ///< a URI reconstructed from various FTP message details
+    SBuf host; ///< intended dest. of a transparently intercepted FTP conn
     bool gotEpsvAll; ///< restrict data conn setup commands to just EPSV
     AsyncCall::Pointer onDataAcceptCall; ///< who to call upon data conn acceptance
     Comm::ConnectionPointer dataListenConn; ///< data connection listening socket