Hi,
here's a merge proposal for Parser::Tokenizer, an implementation of
the API suggested by Alex.
The feature branch is available at lp:~squid/squid/sbuf-tokenizer
--
/kinkie
# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: [email protected]
# target_branch: file:///home/kinkie/squid/workspace/trunk/
# testament_sha1: 202894c2b71d744c260b7b8798102f295e879d8e
# timestamp: 2014-01-06 22:21:02 +0100
# base_revision_id: [email protected]\
# gqkbrnvpk1feg3bm
#
# Begin patch
=== modified file 'configure.ac'
--- configure.ac 2013-12-12 09:41:39 +0000
+++ configure.ac 2013-12-19 16:00:30 +0000
@@ -3429,6 +3429,7 @@
src/ipc/Makefile
src/ssl/Makefile
src/mgr/Makefile
+ src/parser/Makefile
src/snmp/Makefile
contrib/Makefile
icons/Makefile
=== modified file 'src/Makefile.am'
--- src/Makefile.am 2014-01-03 10:32:53 +0000
+++ src/Makefile.am 2014-01-05 16:57:44 +0000
@@ -46,8 +46,8 @@
LoadableModules.h \
LoadableModules.cc
-SUBDIRS = base anyp comm eui acl format fs repl
-DIST_SUBDIRS = base anyp comm eui acl format fs repl
+SUBDIRS = base anyp parser comm eui acl format fs repl
+DIST_SUBDIRS = base anyp parser comm eui acl format fs repl
if ENABLE_AUTH
SUBDIRS += auth
@@ -646,6 +646,7 @@
$(ESI_LIBS) \
$(SSL_LIBS) \
$(SNMP_LIBS) \
+ parser/libsquid-parser.la \
$(top_builddir)/lib/libmisccontainers.la \
$(top_builddir)/lib/libmiscencoding.la \
$(top_builddir)/lib/libmiscutil.la \
=== added directory 'src/parser'
=== added file 'src/parser/Makefile.am'
--- src/parser/Makefile.am 1970-01-01 00:00:00 +0000
+++ src/parser/Makefile.am 2013-12-31 10:42:11 +0000
@@ -0,0 +1,49 @@
+include $(top_srcdir)/src/Common.am
+include $(top_srcdir)/src/TestHeaders.am
+
+EXTRA_PROGRAMS = \
+ testTokenizer
+
+check_PROGRAMS += testTokenizer
+TESTS += testTokenizer
+
+noinst_LTLIBRARIES = libsquid-parser.la
+
+libsquid_parser_la_SOURCES = \
+ Tokenizer.h \
+ Tokenizer.cc
+
+SBUF_SOURCE= \
+ $(top_srcdir)/src/base/CharacterSet.h \
+ $(top_srcdir)/src/SBuf.h \
+ $(top_srcdir)/src/SBuf.cc \
+ $(top_srcdir)/src/MemBlob.h \
+ $(top_srcdir)/src/MemBlob.cc \
+ $(top_srcdir)/src/OutOfBoundsException.h \
+ $(top_srcdir)/src/SBufExceptions.h \
+ $(top_srcdir)/src/SBufExceptions.cc \
+ $(top_srcdir)/src/String.cc \
+ $(top_srcdir)/src/SquidString.h \
+ $(top_srcdir)/src/base/TextException.h \
+ $(top_srcdir)/src/base/TextException.cc
+
+testTokenizer_SOURCES = \
+ $(SBUF_SOURCE) \
+ testTokenizer.h \
+ testTokenizer.cc \
+ Tokenizer.h
+nodist_testTokenizer_SOURCES = \
+ $(top_srcdir)/src/tests/testMain.cc \
+ $(top_srcdir)/src/tests/stub_mem.cc \
+ $(top_srcdir)/src/tests/stub_debug.cc \
+ $(top_srcdir)/src/tests/stub_time.cc \
+ $(top_srcdir)/src/tests/stub_SBufDetailedStats.cc
+testTokenizer_LDFLAGS = $(LIBADD_DL)
+testTokenizer_LDADD = \
+ libsquid-parser.la \
+ $(top_builddir)/lib/libmiscutil.la \
+ $(top_builddir)/src/base/libbase.la \
+ $(SQUID_CPPUNIT_LIBS) \
+ $(SQUID_CPPUNIT_LA) \
+ $(COMPAT_LIB)
+testTokenizer_DEPENDENCIES = $(SQUID_CPPUNIT_LA)
=== added file 'src/parser/Tokenizer.cc'
--- src/parser/Tokenizer.cc 1970-01-01 00:00:00 +0000
+++ src/parser/Tokenizer.cc 2014-01-06 21:20:57 +0000
@@ -0,0 +1,60 @@
+#include "squid.h"
+#include "Tokenizer.h"
+
+namespace Parser {
+
+bool
+Tokenizer::token(SBuf &returnedToken, const CharacterSet &whitespace)
+{
+ SBuf savebuf(buf_);
+ SBuf saveRetVal(returnedToken);
+ skip(whitespace);
+ if (!(prefix(returnedToken,whitespace))) {
+ buf_=savebuf;
+ returnedToken=saveRetVal;
+ return false;
+ }
+ skip(whitespace);
+ return true;
+}
+
+bool
+Tokenizer::prefix(SBuf &returnedToken, const CharacterSet &tokenChars)
+{
+ SBuf::size_type prefixLen = buf_.findFirstNotOf(tokenChars);
+ if (prefixLen == 0)
+ return false;
+ returnedToken = buf_.consume(prefixLen);
+ return true;
+}
+
+bool
+Tokenizer::skip(const CharacterSet &tokenChars)
+{
+ SBuf::size_type prefixLen = buf_.findFirstNotOf(tokenChars);
+ if (prefixLen == 0)
+ return false;
+ buf_.consume(prefixLen);
+ return true;
+}
+
+bool
+Tokenizer::skip(const SBuf &tokenToSkip)
+{
+ if (buf_.startsWith(tokenToSkip)) {
+ buf_.consume(tokenToSkip.length());
+ return true;
+ }
+ return false;
+}
+
+bool
+Tokenizer::skip(const char tokenChar)
+{
+ if (buf_[0] == tokenChar) {
+ buf_.consume(1);
+ return true;
+ }
+ return false;
+}
+} /* namespace Parser */
=== added file 'src/parser/Tokenizer.h'
--- src/parser/Tokenizer.h 1970-01-01 00:00:00 +0000
+++ src/parser/Tokenizer.h 2013-12-19 16:00:30 +0000
@@ -0,0 +1,47 @@
+#ifndef SQUID_PARSER_TOKENIZER_H_
+#define SQUID_PARSER_TOKENIZER_H_
+
+#include "base/CharacterSet.h"
+#include "SBuf.h"
+
+namespace Parser {
+
+class Tokenizer {
+public:
+ explicit Tokenizer(const SBuf &inBuf) : buf_(inBuf) {}
+
+ bool atEnd() const { return !buf_.length(); }
+ const SBuf& remaining() const { return buf_; }
+ void reset(const SBuf &newBuf) { buf_ = newBuf; }
+
+ /* The following methods start from the beginning of the input buffer.
+ * They return true and consume parsed chars if a non-empty token is found.
+ * Otherwise, they return false without any side-effects. */
+
+ /** Basic strtok(3):
+ * Skips all leading delimiters (if any),
+ * accumulates all characters up to the first delimiter (a token), and
+ * skips all trailing delimiters (if any).
+ * Want to extract delimiters? Use three prefix() calls instead.
+ */
+ bool token(SBuf &returnedToken, const CharacterSet &whitespace);
+
+ /// Accumulates all sequential permitted characters (a token).
+ bool prefix(SBuf &returnedToken, const CharacterSet &tokenChars);
+
+ /// Skips all sequential permitted characters (a token).
+ bool skip(const CharacterSet &tokenChars);
+
+ /// Skips a given token.
+ bool skip(const SBuf &tokenToSkip);
+
+ /// Skips a given character (a token).
+ bool skip(const char tokenChar);
+
+private:
+ SBuf buf_; ///< yet unparsed input
+};
+
+
+} /* namespace Parser */
+#endif /* SQUID_PARSER_TOKENIZER_H_ */
=== added file 'src/parser/testTokenizer.cc'
--- src/parser/testTokenizer.cc 1970-01-01 00:00:00 +0000
+++ src/parser/testTokenizer.cc 2014-01-05 16:57:44 +0000
@@ -0,0 +1,109 @@
+#include "squid.h"
+
+#include "testTokenizer.h"
+#include "base/CharacterSet.h"
+#include "Tokenizer.h"
+
+CPPUNIT_TEST_SUITE_REGISTRATION( testTokenizer );
+
+SBuf text("GET http://resource.com/path HTTP/1.1\r\n"
+ "Host: resource.com\r\n"
+ "Cookie: laijkpk3422r j1noin \r\n"
+ "\r\n");
+const CharacterSet alpha("alpha","abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+const CharacterSet whitespace("whitespace"," \r\n");
+const CharacterSet crlf("crlf","\r\n");
+const CharacterSet tab("tab","\t");
+const CharacterSet numbers("numbers","0123456789");
+
+void
+testTokenizer::testTokenizerPrefix()
+{
+ Parser::Tokenizer t(text);
+ SBuf s;
+
+ // successful prefix tokenization
+ CPPUNIT_ASSERT(t.prefix(s,alpha));
+ CPPUNIT_ASSERT_EQUAL(SBuf("GET"),s);
+ CPPUNIT_ASSERT(t.prefix(s,whitespace));
+ CPPUNIT_ASSERT_EQUAL(SBuf(" "),s);
+
+ //no match (first char is not in the prefix set)
+ CPPUNIT_ASSERT(!t.prefix(s,whitespace));
+ CPPUNIT_ASSERT_EQUAL(SBuf(" "),s);
+
+ // one more match to set S to something meaningful
+ CPPUNIT_ASSERT(t.prefix(s,alpha));
+ CPPUNIT_ASSERT_EQUAL(SBuf("http"),s);
+
+ //no match (no characters from the character set in the prefix)
+ CPPUNIT_ASSERT(!t.prefix(s,tab));
+ CPPUNIT_ASSERT_EQUAL(SBuf("http"),s); //output SBuf left untouched
+
+ // match until the end of the sample
+ CharacterSet all(whitespace);
+ all += alpha;
+ all += crlf;
+ all += numbers;
+ all.add(':').add('.').add('/');
+ CPPUNIT_ASSERT(t.prefix(s,all));
+ CPPUNIT_ASSERT_EQUAL(SBuf(),t.remaining());
+}
+
+void
+testTokenizer::testTokenizerSkip()
+{
+ Parser::Tokenizer t(text);
+ SBuf s;
+
+ // first scenario: patterns match
+ // prep for test
+ CPPUNIT_ASSERT(t.prefix(s,alpha));
+ CPPUNIT_ASSERT_EQUAL(SBuf("GET"),s);
+
+ // test skip testing character set
+ CPPUNIT_ASSERT(t.skip(whitespace));
+ // check that skip was right
+ CPPUNIT_ASSERT(t.prefix(s,alpha));
+ CPPUNIT_ASSERT_EQUAL(SBuf("http"),s);
+
+ //check skip prefix
+ CPPUNIT_ASSERT(t.skip(SBuf("://")));
+ // verify
+ CPPUNIT_ASSERT(t.prefix(s,alpha));
+ CPPUNIT_ASSERT_EQUAL(SBuf("resource"),s);
+
+ // no skip
+ CPPUNIT_ASSERT(!t.skip(alpha));
+ CPPUNIT_ASSERT(!t.skip(SBuf("://")));
+ CPPUNIT_ASSERT(!t.skip('a'));
+
+}
+
+void
+testTokenizer::testTokenizerToken()
+{
+ Parser::Tokenizer t(text);
+ SBuf s;
+
+ // first scenario: patterns match
+ CPPUNIT_ASSERT(t.token(s,whitespace));
+ CPPUNIT_ASSERT_EQUAL(SBuf("GET"),s);
+ CPPUNIT_ASSERT(t.token(s,whitespace));
+ CPPUNIT_ASSERT_EQUAL(SBuf("http://resource.com/path"),s);
+ CPPUNIT_ASSERT(t.token(s,whitespace));
+ CPPUNIT_ASSERT_EQUAL(SBuf("HTTP/1.1"),s);
+ CPPUNIT_ASSERT(t.token(s,whitespace));
+ CPPUNIT_ASSERT_EQUAL(SBuf("Host:"),s);
+
+ SBuf s2(s);
+ //no separator found
+ CPPUNIT_ASSERT(!t.token(s,tab));
+ CPPUNIT_ASSERT_EQUAL(s2,s); // check that the output parameter was untouched
+}
+
+void
+testTokenizer::testCharacterSet()
+{
+
+}
=== added file 'src/parser/testTokenizer.h'
--- src/parser/testTokenizer.h 1970-01-01 00:00:00 +0000
+++ src/parser/testTokenizer.h 2013-12-19 16:00:30 +0000
@@ -0,0 +1,22 @@
+#ifndef SQUID_TESTTOKENIZER_H_
+#define SQUID_TESTTOKENIZER_H_
+
+#include <cppunit/extensions/HelperMacros.h>
+
+class testTokenizer : public CPPUNIT_NS::TestFixture
+{
+ CPPUNIT_TEST_SUITE( testTokenizer );
+ CPPUNIT_TEST ( testCharacterSet );
+ CPPUNIT_TEST ( testTokenizerPrefix );
+ CPPUNIT_TEST ( testTokenizerSkip );
+ CPPUNIT_TEST ( testTokenizerToken );
+ CPPUNIT_TEST_SUITE_END();
+
+protected:
+ void testTokenizerPrefix();
+ void testTokenizerSkip();
+ void testTokenizerToken();
+ void testCharacterSet();
+};
+
+#endif /* SQUID_TESTTOKENIZER_H_ */
# Begin bundle
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
/JJ4mmUIj/xdyRThQkMxtpas