]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Added SBuf-related files
authorFrancesco Chemolli <kinkie@squid-cache.org>
Fri, 29 Mar 2013 16:50:25 +0000 (17:50 +0100)
committerFrancesco Chemolli <kinkie@squid-cache.org>
Fri, 29 Mar 2013 16:50:25 +0000 (17:50 +0100)
13 files changed:
src/MemBlob.cc
src/MemBlob.h
src/OutOfBoundsException.h [new file with mode: 0644]
src/SBuf.cc [new file with mode: 0644]
src/SBuf.cci [new file with mode: 0644]
src/SBuf.h [new file with mode: 0644]
src/SBufExceptions.cc [new file with mode: 0644]
src/SBufExceptions.h [new file with mode: 0644]
src/SBufStream.h [new file with mode: 0644]
src/tests/SBufFindTest.cc [new file with mode: 0644]
src/tests/SBufFindTest.h [new file with mode: 0644]
src/tests/testSBuf.cc [new file with mode: 0644]
src/tests/testSBuf.h [new file with mode: 0644]

index 8cb0c8044efef6fa27acc3d2a4946789f5f71c15..4acdb569a8fe9453d167e027d69cec65a1ab9baa 100644 (file)
@@ -46,6 +46,17 @@ InstanceIdDefinitions(MemBlob, "blob");
 MemBlobStats::MemBlobStats(): alloc(0), live(0), append(0), liveBytes(0)
 {}
 
+MemBlobStats&
+MemBlobStats::operator += (const MemBlobStats& s)
+{
+    alloc+=s.alloc;
+    live+=s.live;
+    append+=s.append;
+    liveBytes+=s.liveBytes;
+
+    return *this;
+}
+
 std::ostream&
 MemBlobStats::dump(std::ostream &os) const
 {
index 7fe3b99990500a7e9ec3aab97303564796484909..0dcca1952393f3dc09ea4a40a9b4f448a65cda5d 100644 (file)
@@ -46,6 +46,8 @@ public:
     /// dumps class-wide statistics
     std::ostream& dump(std::ostream& os) const;
 
+    MemBlobStats& operator += (const MemBlobStats&);
+
 public:
     uint64_t alloc;     ///< number of MemBlob instances created so far
     uint64_t live;      ///< number of MemBlob instances currently alive
diff --git a/src/OutOfBoundsException.h b/src/OutOfBoundsException.h
new file mode 100644 (file)
index 0000000..44fd4b0
--- /dev/null
@@ -0,0 +1,22 @@
+#ifndef _SQUID_SRC_OUTOFBOUNDSEXCEPTION_H
+#define _SQUID_SRC_OUTOFBOUNDSEXCEPTION_H
+
+#include "base/TextException.h"
+#include "SBuf.h"
+
+/**
+ * Exception raised when the user is going out of bounds when accessing
+ * a char within the SBuf
+ */
+class OutOfBoundsException : public TextException
+{
+public:
+    OutOfBoundsException(const SBuf &buf, SBuf::size_type &pos, const char *aFileName = 0, int aLineNo = -1);
+    virtual ~OutOfBoundsException() throw();
+
+protected:
+    SBuf _buf;
+    SBuf::size_type _pos;
+};
+
+#endif /* _SQUID_SRC_OUTOFBOUNDSEXCEPTION_H */
diff --git a/src/SBuf.cc b/src/SBuf.cc
new file mode 100644 (file)
index 0000000..7ed1341
--- /dev/null
@@ -0,0 +1,850 @@
+/*
+ * SBuf.cc (C) 2008 Francesco Chemolli <kinkie@squid-cache.org>
+ *
+ * SQUID Web Proxy Cache          http://www.squid-cache.org/
+ * ----------------------------------------------------------
+ *
+ *  Squid is the result of efforts by numerous individuals from
+ *  the Internet community; see the CONTRIBUTORS file for full
+ *  details.   Many organizations have provided support for Squid's
+ *  development; see the SPONSORS file for full details.  Squid is
+ *  Copyrighted (C) 2001 by the Regents of the University of
+ *  California; see the COPYRIGHT file for full details.  Squid
+ *  incorporates software developed and/or copyrighted by other
+ *  sources; see the CREDITS file for full details.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+ */
+
+#include "squid.h"
+#include "base/RefCount.h"
+#include "Debug.h"
+#include "OutOfBoundsException.h"
+#include "SBuf.h"
+#include "SBufExceptions.h"
+#include "util.h"
+
+#if HAVE_STRING_H
+#include <string.h>
+#endif
+
+#if HAVE_SSTREAM
+#include <sstream>
+#endif
+
+#if HAVE_IOSTREAM
+#include <iostream>
+#endif
+
+#ifdef VA_COPY
+#undef VA_COPY
+#endif
+#if defined HAVE_VA_COPY
+#define VA_COPY va_copy
+#elif defined HAVE___VA_COPY
+#define VA_COPY __va_copy
+#endif
+
+InstanceIdDefinitions(SBuf, "SBuf");
+
+SBufStats SBuf::stats;
+
+SBufStats::SBufStats()
+        : alloc(0), allocCopy(0), allocFromString(0), allocFromCString(0),
+        assignFast(0), clear(0), append(0), toStream(0), setChar(0),
+        getChar(0), compareSlow(0), compareFast(0), copyOut(0),
+        rawAccess(0), chop(0), trim(0), find(0), scanf(0),
+        caseChange(0), cowFast(0), cowSlow(0), live(0)
+{}
+
+SBufStats&
+SBufStats::operator +=(const SBufStats& ss)
+{
+    alloc += ss.alloc;
+    allocCopy += ss.allocCopy;
+    allocFromString += ss.allocFromString;
+    allocFromCString += ss.allocFromCString;
+    assignFast += ss.assignFast;
+    clear += ss.clear;
+    append += ss.append;
+    toStream += ss.toStream;
+    setChar += ss.setChar;
+    getChar += ss.getChar;
+    compareSlow += ss.compareSlow;
+    compareFast += ss.compareFast;
+    copyOut += ss.copyOut;
+    rawAccess += ss.rawAccess;
+    chop += ss.chop;
+    trim += ss.trim;
+    find += ss.find;
+    scanf += ss.scanf;
+    caseChange += ss.caseChange;
+    cowFast += ss.cowFast;
+    cowSlow += ss.cowSlow;
+    live += ss.live;
+
+    return *this;
+}
+
+SBuf::SBuf()
+        : store_(GetStorePrototype()), off_(0), len_(0)
+{
+    debugs(24, 8, id << " created");
+    ++stats.alloc;
+    ++stats.live;
+}
+
+SBuf::SBuf(const SBuf &S)
+        : store_(S.store_), off_(S.off_), len_(S.len_)
+{
+    debugs(24, 8, id << " created from id " << S.id);
+    ++stats.alloc;
+    ++stats.allocCopy;
+    ++stats.live;
+}
+
+SBuf::SBuf(const String &S)
+        : store_(GetStorePrototype()), off_(0), len_(0)
+{
+    debugs(24, 8, id << " created from string");
+    assign(S.rawBuf(), 0, S.size());
+    ++stats.alloc;
+    ++stats.allocFromString;
+    ++stats.live;
+}
+
+SBuf::SBuf(const char *S, size_type pos, size_type n)
+        : store_(GetStorePrototype()), off_(0), len_(0)
+{
+    append(S,pos,n); //bounds checked in append()
+    ++stats.alloc;
+    ++stats.allocFromCString;
+    ++stats.live;
+}
+
+SBuf::~SBuf()
+{
+    debugs(24, 8, id << " destructed");
+    --stats.live;
+}
+
+SBuf&
+SBuf::assign(const SBuf &S)
+{
+    debugs(24, 7, "assigning " << id << " from " <<  S.id);
+    if (&S == this) //assignment to self. Noop.
+        return *this;
+    ++stats.assignFast;
+    store_ = S.store_;
+    off_ = S.off_;
+    len_ = S.len_;
+    return *this;
+}
+
+SBuf&
+SBuf::assign(const char *S, size_type pos, size_type n)
+{
+    debugs(24, 6, id << " from c-string, pos=" << pos << "n=" << n << ")");
+    clear();
+    return append(S, pos, n); //bounds checked in append()
+}
+
+void
+SBuf::reserveCapacity(size_type minCapacity)
+{
+    Must(0 <= minCapacity && minCapacity <= maxSize);
+    reserveSpace(minCapacity-length());
+}
+
+void
+SBuf::reserveSpace(size_type minSpace)
+{
+    Must(0 <= minSpace && minSpace <= maxSize);
+    debugs(24, 7, "reserving " << minSpace << " for " << id);
+    // we're not concerned about RefCounts here,
+    // the store knows the last-used portion. If
+    // it's available, we're effectively claiming ownership
+    // of it. If it's not, we need to go away (realloc)
+    if (store_->canAppend(off_+len_, minSpace)) {
+        debugs(24, 7, "not growing");
+        return;
+    }
+    // TODO: we may try to memmove before realloc'ing in order to avoid
+    //   one allocation operation, if we're the sole owners of a MemBlob.
+    //   Maybe some heuristic on off_ and length()?
+    reAlloc(estimateCapacity(minSpace+length()));
+}
+
+void
+SBuf::clear()
+{
+#if 0
+    //enabling this code path, the store will be freed and reinitialized
+    store_ = GetStorePrototype(); //uncomment to actually free storage upon clear()
+#else
+    //enabling this code path, we try to release the store without deallocating it.
+    // will be lazily reallocated if needed.
+    if (store_->LockCount() == 1)
+        store_->clear();
+#endif
+    len_ = 0;
+    off_ = 0;
+    ++stats.clear;
+}
+
+SBuf&
+SBuf::append(const SBuf &S)
+{
+    return append(S.buf(), 0, S.length());
+}
+
+SBuf&
+SBuf::append(const char * S, size_type pos, size_type n)
+{
+    Must(pos == npos || pos >= 0);
+    Must(n == npos || n >= 0);
+
+    if (S == NULL)
+        return *this;
+    if (n == npos)
+        n = strlen(S)-pos;
+
+    debugs(24, 7, "from c-string to id " << id);
+
+    reserveSpace(n); //called method also checks n <= maxSize()
+    const char *actual_start = S+pos;
+    store_->append(actual_start, n);
+    len_ += n;
+    ++stats.append;
+    return *this;
+}
+
+SBuf&
+SBuf::append(const std::string &str, SBuf::size_type pos, SBuf::size_type n)
+{
+    return append(str.data(), pos, n); //bounds checked in append()
+}
+
+SBuf&
+SBuf::assign(const std::string &str, size_type pos, size_type n)
+{
+    clear();
+    return append(str, pos, n); //bounds checked in append()
+}
+
+SBuf&
+SBuf::Printf(const char *fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    clear();
+    vappendf(fmt, args);
+    va_end(args);
+    return *this;
+}
+
+SBuf&
+SBuf::appendf(const char *fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    vappendf(fmt, args);
+    va_end(args);
+    return *this;
+}
+
+SBuf&
+SBuf::vappendf(const char *fmt, va_list vargs)
+{
+#ifdef VA_COPY
+    va_list ap;
+#endif
+    int sz = 0;
+
+    Must(fmt != NULL);
+
+    //we can assume that we'll need to append at least strlen(fmt) bytes,
+    //times 1.2 for instance...
+    reserveSpace(strlen(fmt)*2);
+
+    while (length() <= maxSize) {
+#ifdef VA_COPY
+        /* Fix of bug 753r. The value of vargs is undefined
+         * after vsnprintf() returns. Make a copy of vargs
+         * in case we loop around and call vsnprintf() again.
+         */
+        VA_COPY(ap, vargs);
+        sz = vsnprintf(bufEnd(), store_->spaceSize(), fmt, ap);
+        va_end(ap);
+#else /* VA_COPY */
+        sz = vsnprintf(bufEnd(), store_->spaceSize(), fmt, vargs);
+#endif /* VA_COPY*/
+        /* check for possible overflow */
+        /* snprintf on Linux returns -1 on overflows */
+        /* snprintf on FreeBSD returns at least free_space on overflows */
+
+        if (sz < 0 || sz >= (int)store_->spaceSize())
+            reserveSpace(sz*2); // TODO: tune heuristics
+        else
+            break;
+    }
+
+    len_ += sz;
+    // TODO: this does NOT belong here, but to class-init or autoconf
+    /* on Linux and FreeBSD, '\0' is not counted in return value */
+    /* on XXX it might be counted */
+    /* check that '\0' is appended and not counted */
+
+    if (operator[](len_-1) == 0) {
+        --sz;
+        --len_;
+    }
+
+    store_->size += sz;
+    ++stats.append;
+
+    return *this;
+}
+
+std::ostream&
+SBuf::print(std::ostream &os) const
+{
+    os.write(buf(), length());
+    ++stats.toStream;
+    return os;
+}
+
+std::ostream&
+SBuf::dump(std::ostream &os) const
+{
+    os << id
+    << ": ";
+    store_->dump(os);
+    os << ",offset:" << off_
+    << ",len:" << len_
+    << ") : '";
+    print(os);
+    os << std::endl;
+    return os;
+}
+
+void
+SBuf::setAt(SBuf::size_type pos, char toset)
+{
+    checkAccessBounds(pos);
+    cow();
+    store_->mem[off_+pos] = toset;
+    ++stats.setChar;
+}
+
+int
+SBuf::compare(const SBuf &S, SBufCaseSensitive isCaseSensitive, size_type n) const
+{
+    Must(n == npos || n >= 0);
+    size_type sz = min(S.length(), length());
+    if (n != npos)
+        sz = min(n, sz);
+    ++stats.compareSlow;
+    int rv;
+    if (isCaseSensitive == caseSensitive)
+        rv = strncmp(buf(), S.buf(), sz);
+    else
+        rv = strncasecmp(buf(), S.buf(), sz);
+    if (rv != 0)
+        return rv;
+    //if max-length was specified, it was hit, and the tail is ignored.
+    if (n != npos)
+        return 0;
+    //first sz bytes equal. longest string "wins"
+    return commonCompareChecksPost(S);
+}
+
+bool
+SBuf::startsWith(const SBuf &S, SBufCaseSensitive isCaseSensitive) const
+{
+    if (length() < S.length()) {
+        ++stats.compareFast;
+        return false;
+    }
+    return (compare(S, isCaseSensitive, S.length()) == 0);
+}
+
+bool
+SBuf::operator ==(const SBuf & S) const
+{
+    debugs(24, 8, id << " == " << S.id);
+    if (length() != S.length()) {
+        debugs(24, 8, "no, different lengths");
+        ++stats.compareFast;
+        return false; //shortcut: must be equal length
+    }
+    if (store_ == S.store_ && off_ == S.off_) {
+        debugs(24, 8, "yes, same length and backing store");
+        ++stats.compareFast;
+        return true;  //shortcut: same store, offset and length
+    }
+    ++stats.compareSlow;
+    const bool rv = (0 == strncmp(buf(), S.buf(), length()));
+    debugs(24, 8, "returning " << rv);
+    return rv;
+}
+
+bool
+SBuf::operator !=(const SBuf & S) const
+{
+    return !(*this == S);
+}
+
+SBuf
+SBuf::consume(SBuf::size_type n)
+{
+    Must (n == npos || n >= 0);
+    if (n == npos)
+        n = length();
+    else
+        n = min(n, length());
+    SBuf rv(substr(0, n));
+    chop(n);
+    return rv;
+}
+
+const
+SBufStats& SBuf::GetStats()
+{
+    return stats;
+}
+
+SBuf::size_type
+SBuf::copy(char *dest, SBuf::size_type n) const
+{
+    Must(n >= 0);
+
+    SBuf::size_type toexport = length();
+    if (toexport > n)
+        toexport = n;
+
+    memcpy(dest, buf(), toexport);
+
+    ++stats.copyOut;
+    return toexport;
+}
+
+const char*
+SBuf::rawContent() const
+{
+    ++stats.rawAccess;
+    return buf();
+}
+
+char *
+SBuf::rawSpace(size_type minSize)
+{
+    cow(minSize+length());
+    ++stats.rawAccess;
+    return bufEnd();
+}
+
+void
+SBuf::forceSize(SBuf::size_type newSize)
+{
+    Must(store_->LockCount() == 1);
+    len_ = newSize;
+    store_->size = newSize;
+}
+
+const char*
+SBuf::c_str()
+{
+    ++stats.rawAccess;
+    /* null-terminate the current buffer, by hand-appending a \0 at its tail but
+     * without increasing its length. May COW, the side-effect is to guarantee that
+     * the MemBlob's tail is availabe for us to use */
+    *rawSpace(1) = '\0';
+    ++store_->size;
+    ++stats.setChar;
+    return buf();
+}
+
+SBuf&
+SBuf::chop(SBuf::size_type pos, SBuf::size_type n)
+{
+    Must(pos >= 0);
+    Must(n == npos || n >= 0);
+    /*
+     * TODO: possible optimization: if the SBuf is at the tail of the
+     * MemBlob we could decrease the MemBlob tail-pointer so that a subsequent
+     * append will reuse the freed space.
+     */
+    if (pos > length() || n == 0) {
+        clear();
+        return *this;
+    }
+    if (n == npos || (pos+n) > length())
+        n = length()-pos;
+    ++stats.chop;
+    off_ += pos;
+    len_ = n;
+    return *this;
+}
+
+SBuf&
+SBuf::trim(const SBuf &toRemove, bool atBeginning, bool atEnd)
+{
+    ++stats.trim;
+    if (atEnd) {
+        const char *p = bufEnd()-1;
+        while (!isEmpty() && memchr(toRemove.buf(), *p, toRemove.length()) != NULL) {
+            //current end-of-buf is in the searched set
+            --len_;
+            --p;
+        }
+    }
+    if (atBeginning) {
+        const char *p = buf();
+        while (!isEmpty() && memchr(toRemove.buf(), *p, toRemove.length()) != NULL) {
+            --len_;
+            ++off_;
+            ++p;
+        }
+    }
+    if (isEmpty())
+        clear();
+    return *this;
+}
+
+SBuf
+SBuf::substr(SBuf::size_type pos, SBuf::size_type n) const
+{
+    SBuf rv(*this);
+    rv.chop(pos, n); //stats handled by callee
+    return rv;
+}
+
+SBuf::size_type
+SBuf::find(char c, SBuf::size_type startPos) const
+{
+    ++stats.find;
+
+    if (startPos == SBuf::npos)
+        return SBuf::npos;
+
+    // std::string returns npos if needle is outside hay
+    if (startPos >= length())
+        return SBuf::npos;
+
+    // ignore invalid startPos
+    if (startPos < 0)
+        startPos = 0;
+
+    const void *i = memchr(buf()+startPos, (int)c, (size_type)length()-startPos);
+
+    if (i == NULL)
+        return SBuf::npos;
+
+    return (static_cast<const char *>(i)-buf());
+}
+
+SBuf::size_type
+SBuf::find(const SBuf &needle, size_type startPos) const
+{
+    // if needle length is 1 use the char search. Stats updated there
+    if (needle.length() == 1)
+        return find(needle[0], startPos);
+
+    // if needle length is 1, the stats were
+    ++stats.find;
+
+    if (startPos == SBuf::npos)
+        return SBuf::npos;
+
+    // std::string allows needle to overhang hay but not start outside
+    if (startPos > length())
+        return SBuf::npos;
+
+    // ignore invalid startPos
+    if (startPos < 0)
+        startPos = 0;
+
+    // for empty needle std::string returns startPos
+    if (needle.length() == 0)
+        return startPos;
+
+    char *begin = buf()+startPos;
+    char *lastPossible = buf()+length()-needle.length()+1;
+    char needleBegin = needle[0];
+
+    debugs(24, 7, "looking for " << needle << "starting at " << startPos <<
+                    " in id " << id);
+    while (begin < lastPossible) {
+        char *tmp;
+        debugs(24, 8, " begin=" << (void *) begin <<
+               ", lastPossible=" << (void*) lastPossible );
+        tmp = static_cast<char *>(memchr(begin, needleBegin, lastPossible-begin));
+        if (tmp == NULL) {
+            debugs(24, 8 , "First byte not found");
+            return SBuf::npos;
+        }
+        // lastPossible guarrantees no out-of-bounds with memcmp()
+        if (0 == memcmp(needle.buf(), tmp, needle.length())) {
+            debugs(24, 8, "Found at " << (tmp-buf()));
+            return (tmp-buf());
+        }
+        begin = tmp+1;
+    }
+    debugs(24, 8, "not found");
+    return SBuf::npos;
+}
+
+SBuf::size_type
+SBuf::rfind(const SBuf &needle, SBuf::size_type endPos) const
+{
+    // when the needle is 1 char, use the 1-char rfind()
+    if (needle.length() == 1)
+        return rfind(needle[0], endPos);
+
+    ++stats.find;
+
+    // on npos input std::string scans from the end of hay
+    if (endPos == SBuf::npos || endPos > length())
+        endPos=length();
+
+    // on empty hay std::string returns npos
+    if (length() < needle.length())
+        return SBuf::npos;
+
+    if (endPos < 0)
+        return SBuf::npos;
+
+    // on empty needle std::string returns the position the search starts
+    if (needle.length() == 0)
+        return endPos;
+
+/* std::string permits needle to overhang endPos
+    if (endPos <= needle.length())
+        return npos;
+*/
+
+    char *bufBegin = buf();
+    char *cur = bufBegin+endPos;
+    char needleBegin = needle[0];
+    while (cur >= bufBegin) {
+        if (*cur == needleBegin) {
+            if (0 == memcmp(needle.buf(), cur, needle.length())) {
+                // found
+                return (cur-buf());
+            }
+        }
+        --cur;
+    }
+    return npos;
+}
+
+SBuf::size_type
+SBuf::rfind(char c, SBuf::size_type endPos) const
+{
+    ++stats.find;
+
+    // on empty hay std::string returns size of hay
+    if (length() < 1)
+        return SBuf::npos;
+
+    // on npos input std::string compares last octet of hay
+    if (endPos == SBuf::npos || endPos >= length()) {
+        endPos=length();
+    } else if (endPos < 0 ) {
+        return SBuf::npos;
+    } else {
+        // NP: off-by-one weirdness:
+        // endPos is an offset ... 0-based
+        // length() is a count ... 1-based
+        // memrhr() requires a 1-based count of space to scan.
+        ++endPos;
+    }
+
+    const void *i = memrchr(buf(), (int)c, (size_type)endPos);
+
+    if (i == NULL)
+        return SBuf::npos;
+
+    return (static_cast<const char *>(i)-buf());
+}
+
+SBuf::size_type
+SBuf::find_first_of(const SBuf &set, size_type startPos) const
+{
+    // if set is 1 char big, use the char search. Stats updated there
+    if (set.length() == 1)
+        return find(set[0], startPos);
+
+    ++stats.find;
+
+    if (startPos == npos)
+        return SBuf::npos;
+
+    if (startPos > length())
+        return SBuf::npos;
+
+    if (startPos < 0)
+        startPos = 0;
+
+    if (set.length() == 0)
+        return npos;
+
+    debugs(24, 7, "any of '" << set << "' " << " in id " << id);
+    char *cur = buf()+startPos, *end = bufEnd();
+    while (cur < end) {
+        if (memchr(set.buf(), *cur, set.length()))
+            return (cur-buf());
+        ++cur;
+    }
+    debugs(24, 7, "not found");
+    return SBuf::npos;
+}
+
+/*
+ * TODO: borrow a sscanf implementation from Linux or similar?
+ * we'd really need a vsnscanf(3)... ? As an alternative, a
+ * light-regexp-like domain-specific syntax might be an idea.
+ */
+int
+SBuf::scanf(const char *format, ...)
+{
+    va_list arg;
+    int rv;
+    ++stats.scanf;
+    va_start(arg, format);
+    rv = vsscanf(c_str(), format, arg);
+    va_end(arg);
+    return rv;
+}
+
+std::ostream &
+operator <<(std::ostream& os, const SBuf& S)
+{
+    return S.print(os);
+}
+
+std::ostream &
+SBufStats::dump(std::ostream& os) const
+{
+    MemBlobStats ststats = MemBlob::GetStats();
+    os <<
+    "SBuf stats:\nnumber of allocations: " << alloc <<
+    "\ncopy-allocations: " << allocCopy <<
+    "\ncopy-allocations from SquidString: " << allocFromString <<
+    "\ncopy-allocations from C String: " << allocFromCString <<
+    "\nlive references: " << live <<
+    "\nno-copy assignments: " << assignFast <<
+    "\nclearing operations: " << clear <<
+    "\nappend operations: " << append <<
+    "\ndump-to-ostream: " << toStream <<
+    "\nset-char: " << setChar <<
+    "\nget-char: " << getChar <<
+    "\ncomparisons with data-scan: " << compareSlow <<
+    "\ncomparisons not requiring data-scan: " << compareFast <<
+    "\ncopy-out ops: " << copyOut <<
+    "\nraw access to memory: " << rawAccess <<
+    "\nchop operations: " << chop <<
+    "\ntrim operations: " << trim <<
+    "\nfind: " << find <<
+    "\nscanf: " << scanf <<
+    "\ncase-change ops: " << caseChange <<
+    "\nCOW not actually requiring a copy: " << cowFast <<
+    "\nCOW: " << cowSlow <<
+    "\naverage store share factor: " <<
+    (ststats.live != 0 ? static_cast<float>(live)/ststats.live : 0) <<
+    std::endl;
+    return os;
+}
+
+SBuf
+SBuf::toLower() const
+{
+    debugs(24, 8, "\"" << *this << "\"");
+    SBuf rv(*this);
+    for (size_type j = 0; j < length(); ++j) {
+        const int c = (*this)[j];
+        if (isupper(c))
+            rv.setAt(j, tolower(c)); //will cow() if needed
+    }
+    debugs(24, 8, "result: \"" << *this << "\"");
+    ++stats.caseChange;
+    return rv;
+}
+
+SBuf
+SBuf::toUpper() const
+{
+    debugs(24, 8, "\"" << *this << "\"");
+    SBuf rv(*this);
+    for (size_type j = 0; j < length(); ++j) {
+        const int c = (*this)[j];
+        if (islower(c))
+            rv.setAt(j, toupper(c)); //will cow() if needed
+    }
+    debugs(24, 8, "result: \"" << *this << "\"");
+    ++stats.caseChange;
+    return rv;
+}
+
+/**
+ * checks whether the requested 'pos' is within the bounds of the SBuf
+ * \throw OutOfBoundsException if access is out of bounds
+ */
+void
+SBuf::checkAccessBounds(SBuf::size_type pos) const
+{
+    if (pos < 0)
+        throw OutOfBoundsException(*this, pos, __FILE__, __LINE__);
+    if (pos > length())
+        throw OutOfBoundsException(*this, pos, __FILE__, __LINE__);
+}
+
+String
+SBuf::toString() const
+{
+    String rv;
+    rv.limitInit(buf(), length());
+    ++stats.copyOut;
+    return rv;
+}
+
+/*
+ * re-allocate the backing store of the SBuf.
+ * If there are contents in the SBuf, they will be copied over.
+ * NO verifications are made on the size parameters, it's up to the caller to
+ * make sure that the new size is big enough to hold the copied contents.
+ * The re-allocated storage MAY be bigger than the requested size due to size-chunking
+ * algorithms in MemBlock, it is guarranteed NOT to be smaller.
+ */
+void
+SBuf::reAlloc(SBuf::size_type newsize)
+{
+    debugs(24, DBG_DATA, "new size: " << newsize);
+    if (newsize > maxSize)
+        throw SBufTooBigException(__FILE__, __LINE__);
+    MemBlob::Pointer newbuf = new MemBlob(newsize);
+    if (length() > 0)
+        newbuf->append(buf(), length());
+    store_ = newbuf;
+    off_ = 0;
+    ++stats.cowSlow;
+    debugs(24, 7, "new store capacity: " << store_->capacity);
+}
+
+#if !_USE_INLINE_
+#include "SBuf.cci"
+#endif
+
diff --git a/src/SBuf.cci b/src/SBuf.cci
new file mode 100644 (file)
index 0000000..714b851
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * SBuf.cc (C) 2008 Francesco Chemolli <kinkie@squid-cache.org>
+ *
+ * SQUID Web Proxy Cache          http://www.squid-cache.org/
+ * ----------------------------------------------------------
+ *
+ *  Squid is the result of efforts by numerous individuals from
+ *  the Internet community; see the CONTRIBUTORS file for full
+ *  details.   Many organizations have provided support for Squid's
+ *  development; see the SPONSORS file for full details.  Squid is
+ *  Copyrighted (C) 2001 by the Regents of the University of
+ *  California; see the COPYRIGHT file for full details.  Squid
+ *  incorporates software developed and/or copyrighted by other
+ *  sources; see the CREDITS file for full details.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+ */
+
+#include "base/RefCount.h"
+#include "OutOfBoundsException.h"
+#include "SBufExceptions.h"
+
+#if HAVE_CLIMITS
+#include <climits>
+#elif HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+SBuf&
+SBuf::operator =(const SBuf & S)
+{
+    return assign(S);
+}
+
+SBuf&
+SBuf::operator =(const char *S)
+{
+    return assign(S);
+}
+
+bool
+SBuf::operator <(const SBuf &S) const
+{
+    return (compare(S) < 0);
+}
+
+bool
+SBuf::operator >(const SBuf &S) const
+{
+    return (compare(S) > 0);
+}
+
+bool
+SBuf::operator <=(const SBuf &S) const
+{
+    return (compare(S) <= 0);
+}
+
+bool
+SBuf::operator >=(const SBuf &S) const
+{
+    return (compare(S) >= 0);
+}
+
+SBuf::size_type
+SBuf::length() const
+{
+    return len_;
+}
+
+int
+SBuf::plength() const
+{
+    if (length() > INT_MAX)
+        throw SBufTooBigException(__FILE__, __LINE__);
+    return (int)length();
+}
+
+/**
+ * obtains a char* to the beginning of this SBuf in memory.
+ * \note the obtained string is NOT null-terminated.
+ */
+char *
+SBuf::buf() const
+{
+    return store_->mem+off_;
+}
+
+/** returns the pointer to the first char after this SBuf end
+ *
+ */
+char *
+SBuf::bufEnd() const
+{
+    return store_->mem+off_+len_;
+}
+
+/**
+ * copy-on-write: make sure that we are the only holder of the backing store.
+ * If not, reallocate. If a new size is specified, and it is greater than the
+ * current length, the backing store will be extended as needed
+ * \retval false no grow was needed
+ * \retval true had to copy
+ */
+bool
+SBuf::cow(SBuf::size_type newsize)
+{
+    debugs(24, DBG_DATA, "new size:" << newsize);
+    if (newsize == npos || newsize < length())
+        newsize = length();
+
+    if (store_->LockCount() == 1 && newsize == length()) {
+        debugs(24, DBG_DATA, "no cow needed");
+        ++stats.cowFast;
+        return false;
+    }
+    reAlloc(newsize);
+    return true;
+}
+
+/**
+ * Try to guesstimate how big a MemBlob to allocate.
+ * The result is guarranteed to be to be at least the desired
+ * size.
+ */
+const SBuf::size_type
+SBuf::estimateCapacity(SBuf::size_type desired) const
+{
+    return 2*desired;
+}
+
+/**
+ * To be called after having determined that the buffers are equal up to the
+ * length of the shortest one.
+ * If the buffers' length is the same, then they're equal. Otherwise, the
+ * longest one is deemed to be greater than the other.
+ * This matches the behavior of strcmp(1) and strcasecmp(1)
+ */
+int
+SBuf::commonCompareChecksPost(const SBuf &S) const
+{
+    if (length() == S.length()) //I'll be damned..they're REALLY the same..
+        return 0;
+    if (length() > S.length())
+        return 1;
+    return -1;
+}
+
+/** obtain prototype store
+ *
+ * Just-created SBufs all share to the same MemBlob.
+ * This call instantiates and returns it.
+ */
+MemBlob::Pointer
+SBuf::GetStorePrototype()
+{
+    static MemBlob::Pointer InitialStore = NULL;
+    if (InitialStore == NULL) {
+        static char lowPrototype[] = "";
+        InitialStore = new MemBlob(lowPrototype, 0);
+    }
+    return InitialStore;
+}
+
+const char
+SBuf::operator [](SBuf::size_type pos) const
+{
+    ++stats.getChar;
+    if (pos < 0 || pos >= length())
+        return '\0';
+    return store_->mem[off_+pos];
+}
+
+const char
+SBuf::at(SBuf::size_type pos) const
+{
+    checkAccessBounds(pos);
+    return operator[](pos);
+}
+
+bool
+SBuf::isEmpty() const
+{
+    return (len_ == 0);
+}
diff --git a/src/SBuf.h b/src/SBuf.h
new file mode 100644 (file)
index 0000000..33fccfa
--- /dev/null
@@ -0,0 +1,566 @@
+/*
+ * SBuf.h (C) 2008 Francesco Chemolli <kinkie@squid-cache.org>
+ *
+ * SQUID Web Proxy Cache          http://www.squid-cache.org/
+ * ----------------------------------------------------------
+ *
+ *  Squid is the result of efforts by numerous individuals from
+ *  the Internet community; see the CONTRIBUTORS file for full
+ *  details.   Many organizations have provided support for Squid's
+ *  development; see the SPONSORS file for full details.  Squid is
+ *  Copyrighted (C) 2001 by the Regents of the University of
+ *  California; see the COPYRIGHT file for full details.  Squid
+ *  incorporates software developed and/or copyrighted by other
+ *  sources; see the CREDITS file for full details.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+ */
+
+#ifndef SQUID_SBUF_H
+#define SQUID_SBUF_H
+
+#include "base/InstanceId.h"
+#include "Debug.h"
+#include "MemBlob.h"
+#include "SquidString.h"
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_STDARG_H
+#include <stdarg.h>
+#endif
+#if HAVE_IOSFWD
+#include <iosfwd>
+#endif
+
+/* squid string placeholder (for printf) */
+#ifndef SQUIDSBUFPH
+#define SQUIDSBUFPH "%.*s"
+#define SQUIDSBUFPRINT(s) (s).plength(),(s).rawContent()
+#endif /* SQUIDSBUFPH */
+
+typedef enum {
+    caseSensitive,
+    caseInsensitive
+} SBufCaseSensitive;
+
+/**
+ * Container for various SBuf class-wide statistics.
+ *
+ * The stats are not completely accurate; they're mostly meant to
+ * understand whether Squid is leaking resources
+ * and whether SBuf is paying off the expected gains.
+ */
+class SBufStats
+{
+public:
+    u_int64_t alloc; ///<number of calls to SBuf constructors
+    u_int64_t allocCopy; ///<number of calls to SBuf copy-constructor
+    u_int64_t allocFromString; ///<number of copy-allocations from Strings
+    u_int64_t allocFromCString; ///<number of copy-allocations from c-strings
+    u_int64_t assignFast; ///<number of no-copy assignment operations
+    u_int64_t clear; ///<number of clear operations
+    u_int64_t append; ///<number of append operations
+    u_int64_t toStream;  ///<number of write operations to ostreams
+    u_int64_t setChar; ///<number of calls to setAt
+    u_int64_t getChar; ///<number of calls to at() and operator[]
+    u_int64_t compareSlow; ///<number of comparison operations requiring data scan
+    u_int64_t compareFast; ///<number of comparison operations not requiring data scan
+    u_int64_t copyOut; ///<number of data-copies to other forms of buffers
+    u_int64_t rawAccess; ///<number of accesses to raw contents
+    u_int64_t chop;  ///<number of chop operations
+    u_int64_t trim;  ///<number of trim operations
+    u_int64_t find;  ///<number of find operations
+    u_int64_t scanf;  ///<number of scanf operations
+    u_int64_t caseChange; ///<number of toUpper and toLower operations
+    u_int64_t cowFast; ///<number of cow operations not actually requiring a copy
+    u_int64_t cowSlow; ///<number of cow operations requiring a copy
+    u_int64_t live;  ///<number of currently-allocated SBuf
+
+    /**
+     * Dump statistics to an ostream.
+     */
+    std::ostream& dump(std::ostream &os) const;
+    SBufStats();
+
+    SBufStats& operator +=(const SBufStats&);
+};
+
+/**
+ * A String or Buffer.
+ * Features: refcounted backing store, cheap copy and sub-stringing
+ * operations, copy-on-write to isolate change operations to each instance.
+ * Where possible, we're trying to mimic std::string's interface.
+ */
+class SBuf
+{
+public:
+    typedef int32_t size_type;
+    static const size_type npos = -1;
+
+    /// Maximum size of a SBuf. By design it MUST be < MAX(size_type)/2. Currently 256Mb.
+    static const size_type maxSize = 0xfffffff;
+
+    /// create an empty (zero-size) SBuf
+    SBuf();
+    SBuf(const SBuf &S);
+
+    /** Constructor: import c-style string
+     *
+     * Create a new SBuf containing a COPY of the contents of the
+     * c-string
+     * \param S the c string to be copied
+     * \param pos how many bytes to skip at the beginning of the c-string
+     * \param n how many bytes to import into the SBuf. If it is SBuf::npos
+     *              or unspecified, imports to end-of-cstring
+     * \note it is the caller's responsibility not to go out of bounds
+     * \note bounds is 0 <= pos < length()
+     */
+    explicit SBuf(const char *S, size_type pos = 0, size_type n = npos);
+
+    /** Constructor: import SquidString, copying contents.
+     *
+     * This method will be removed once SquidString has gone.
+     */
+    SBuf(const String &S);
+
+    ~SBuf();
+    /** Explicit assignment.
+     *
+     * Current SBuf will share backing store with the assigned one.
+     */
+    SBuf& assign(const SBuf &S);
+    /** Assignment operator.
+     *
+     * Current SBuf will share backing store with the assigned one.
+     */
+    _SQUID_INLINE_ SBuf& operator =(const SBuf & S);
+
+    /** Import a c-string into a SBuf, copying the data.
+     *
+     * It is the caller's duty to free the imported string, if needed.
+     * \param S the c string to be copied
+     * \param pos how many bytes to skip at the beginning of the c-string.
+     * \param n how many bytes to import into the SBuf. If it is SBuf::npos
+     *              or unspecified, imports to end-of-cstring
+     * \note it is the caller's responsibility not to go out of bounds
+     * \note bounds is 0 <= pos < length()
+     */
+    SBuf& assign(const char *S, size_type pos = 0, size_type n = npos);
+
+    /** Assignment operator. Copy a NULL-terminated c-style string into a SBuf.
+     *
+     * Copy a c-style string into a SBuf. Shortcut for SBuf.assign(S)
+     * It is the caller's duty to free the imported string, if needed.
+     */
+    _SQUID_INLINE_ SBuf& operator =(const char *S);
+
+    /** Import a std::string into a SBuf. Contents are copied.
+     *
+     * \param pos skip this many bytes at the beginning of string.
+     *          0 is beginning-of-string
+     * \param n how many bytes to copy. Default is SBuf::npos, end-of-string.
+     */
+    SBuf& assign(const std::string &s, size_type pos = 0, size_type n = npos);
+
+    /** reset the SBuf as if it was just created.
+     *
+     * Resets the SBuf to empty, memory is freed lazily.
+     */
+    void clear();
+
+    /** Append operation
+     *
+     * Append the supplied SBuf to the current one; extend storage as needed.
+     */
+    SBuf& append(const SBuf & S);
+
+    /** Append operation for C-style strings.
+     *
+     * Append the supplied c-string to the SBuf; extend storage
+     * as needed.
+     *
+     * \param S the c string to be copied. Can be NULL.
+     * \param pos how many bytes to skip at the beginning of the c-string
+     * \param n how many bytes to import into the SBuf. If it is SBuf::npos
+     *              or unspecified, imports to end-of-cstring
+     */
+    SBuf& append(const char * S, size_type pos = 0, size_type n = npos);
+
+    /** Append operation for std::string
+     *
+     * Append the supplied std::string to the SBuf; extend storage as needed.
+     *
+     * \param string the std::string to be copied.
+     * \param pos how many bytes to skip at the beginning of the c-string
+     * \param n how many bytes to import into the SBuf. If it is SBuf::npos
+     *              or unspecified, imports to end-of-cstring
+     */
+    SBuf& append(const std::string &str, size_type pos = 0, size_type n = npos);
+
+    /** Assignment operation with printf(3)-style definition
+     * \note arguments may be evaluated more than once, be careful
+     *       of side-effects
+     */
+    SBuf& Printf(const char *fmt, ...);
+
+    /** Append operation with printf-style arguments
+     * \note arguments may be evaluated more than once, be careful
+     *       of side-effects
+     */
+    SBuf& appendf(const char *fmt, ...);
+    /** Append operation, with vsprintf(3)-style arguments.
+     * \note arguments may be evaluated more than once, be careful
+     *       of side-effects
+     */
+    SBuf& vappendf(const char *fmt, va_list vargs);
+
+    /** print a SBuf.
+     */
+    std::ostream& print(std::ostream &os) const;
+
+    /** print the sbuf, debug information and stats
+     *
+     * Debug function, dumps to a stream informations on the current SBuf,
+     * including low-level details and statistics.
+     */
+    std::ostream& dump(std::ostream &os) const;
+
+    /** random-access read to any char within the SBuf
+     *
+     * does not check access bounds. If you need that, use at()
+     */
+    _SQUID_INLINE_ const char operator [](size_type pos) const;
+
+    /** random-access read to any char within the SBuf.
+     *
+     * \throw OutOfBoundsException when access is out of bounds
+     * \note bounds is 0 <= pos < length()
+     */
+    _SQUID_INLINE_ const char at(size_type pos) const;
+
+    /** direct-access set a byte at a specified operation.
+     *
+     * \param pos the position to be overwritten
+     * \param toset the value to be written
+     * \throw OutOfBoundsException when pos is of bounds
+     * \note bounds is 0 <= pos < length()
+     * \note performs a copy-on-write if needed.
+     */
+    void setAt(size_type pos, char toset);
+
+    /** compare to other SBuf, str(case)cmp-style
+     *
+     * \param isCaseSensitive one of caseSensitive or caseInsensitive
+     * \param n compare up to this many bytes. if npos (default), to end-of-string
+     * \retval >0 argument of the call is greater than called SBuf
+     * \retval <0 argument of the call is smaller than called SBuf
+     * \retval 0  argument of the call has the same contents of called SBuf
+     */
+    int compare(const SBuf &S, SBufCaseSensitive isCaseSensitive = caseSensitive, size_type n = npos) const;
+
+    /** check whether the entire supplied argument is a prefix of the SBuf.
+     *  \param S the prefix to match against
+     *  \param isCaseSensitive one of caseSensitive or caseInsensitive
+     *  \retval true argument is a prefix of the SBuf
+     */
+    bool startsWith(const SBuf &S, SBufCaseSensitive isCaseSensitive = caseSensitive) const;
+
+    /** equality check
+     */
+    bool operator ==(const SBuf & S) const;
+    bool operator !=(const SBuf & S) const;
+    _SQUID_INLINE_ bool operator <(const SBuf &S) const;
+    _SQUID_INLINE_ bool operator >(const SBuf &S) const;
+    _SQUID_INLINE_ bool operator <=(const SBuf &S) const;
+    _SQUID_INLINE_ bool operator >=(const SBuf &S) const;
+
+    /** Consume bytes at the head of the SBuf
+     *
+     * Consume N chars at SBuf head, or to SBuf's end,
+     * whichever is shorter. If more bytes are consumed than available,
+     * the SBuf is emptied
+     * \param n how many bytes to remove; could be zero.
+     *     SBuf::npos (or no argument) means 'to the end of SBuf'
+     * \return a new SBuf containing the consumed bytes.
+     */
+    SBuf consume(size_type n = npos);
+
+    /** gets global statistic informations
+     *
+     */
+    static const SBufStats& GetStats();
+
+    /** Copy SBuf contents into user-supplied C buffer.
+     *
+     * Export a copy of the SBuf's contents into the user-supplied
+     * buffer, up to the user-supplied-length. No zero-termination is performed
+     * \return num the number of actually-copied chars.
+     */
+    size_type copy(char *dest, size_type n) const;
+
+    /** exports a pointer to the SBuf internal storage.
+     * \warning ACCESSING RAW STORAGE IS DANGEROUS!
+     *
+     * Returns a pointer to SBuf's content. No terminating null character
+     * is appended (use c_str() for that).
+     * The returned value points to an internal location whose contents
+     * are guaranteed to remain unchanged only until the next call
+     * to a non-constant member function of the SBuf object. Such a
+     * call may be implicit (e.g., when SBuf is destroyed
+     * upon leaving the current context).
+     * This is a very UNSAFE way of accessing the data.
+     * This call never returns NULL.
+     * \see c_str
+     * \note the memory management system guarantees that the exported region
+     *    of memory will remain valid if the caller keeps holding
+     *    a valid reference to the SBuf object and does not write or append to
+     *    it. For example:
+     * \code
+     * SBuf foo("some string");
+     * const char *bar = foo.rawContent();
+     * doSomething(bar); //safe
+     * foo.append(" other string");
+     * doSomething(bar); //unsafe
+     * \endcode
+     */
+    const char* rawContent() const;
+
+    /** Exports a writable pointer to the SBuf internal storage.
+     * \warning Use with EXTREME caution, this is a dangerous operation.
+     *
+     * Returns a pointer to the first unused byte in the SBuf's storage,
+     * to be used for writing. If minsize is specified, it is guaranteed
+     * that at least minsize bytes will be available for writing. Otherwise
+     * it is guaranteed that at least as much storage as is currently
+     * available will be available for the call. A COW will be performed
+     * if necessary to ensure that a following write will not trample
+     * a shared MemBlob. The returned pointer must not be stored, and will
+     * become invalid at the first call to a non-const method call
+     * on the SBuf.
+     * This call guarantees to never return NULL
+     * This call always forces a cow()
+     * \throw SBufTooBigException if the user tries to allocate too big a SBuf
+     */
+    char *rawSpace(size_type minSize = npos);
+
+    /** Force a SBuf's size
+     * \warning use with EXTREME caution, this is a dangerous operation
+     *
+     * Adapt the SBuf internal state after external interference
+     * such as writing into it via rawSpace.
+     * \throw TextException if we
+     */
+    void forceSize(size_type newSize);
+
+    /** exports a null-terminated reference to the SBuf internal storage.
+     * \warning ACCESSING RAW STORAGE IS DANGEROUS! DO NOT EVER USE
+     *  THE RETURNED POINTER FOR WRITING
+     *
+     * The returned value points to an internal location whose contents
+     * are guaranteed to remain unchanged only until the next call
+     * to a non-constant member function of the SBuf object. Such a
+     * call may be implicit (e.g., when SBuf is destroyed
+     * upon leaving the current context).
+     * This is a very UNSAFE way of accessing the data.
+     * This call never returns NULL.
+     * \see rawContent
+     * \note the memory management system guarantees that the exported region
+     *    of memory will remain valid if the caller keeps holding
+     *    a valid reference to the SBuf object and does not write or append to
+     *    it
+     */
+    const char* c_str();
+
+    /** Returns the number of bytes stored in SBuf.
+     */
+    _SQUID_INLINE_ size_type length() const;
+
+    /** Get the length of the SBuf, as a signed integer
+     *
+     * Compatibility function for printf(3) which requires a signed int
+     * \throw SBufTooBigException if the SBuf is too big for a signed integer
+     */
+    _SQUID_INLINE_ int plength() const;
+
+    /** Check whether the SBuf is empty
+     *
+     * \return true if length() == 0
+     */
+    _SQUID_INLINE_ bool isEmpty() const;
+
+    /** Request to extend the SBuf's free store space.
+     *
+     * After the reserveSpace request, the SBuf is guaranteed to have at
+     * least minSpace bytes of append-able backing store (on top of the
+     * currently-used portion).
+     * \throw SBufTooBigException if the user tries to allocate too big a SBuf
+     */
+    void reserveSpace(size_type minSpace);
+
+    /** Request to resize the SBuf's store
+     *
+     * After this method is called, the SBuf is guaranteed to have at least
+     * minCapcity bytes of total space, including the currently-used portion
+     * \throw SBufTooBigException if the user tries to allocate too big a SBuf
+     */
+    void reserveCapacity(size_type minCapacity);
+
+    /** slicing method
+     *
+     * Removes SBuf prefix and suffix, leaving a sequence of <i>n</i>
+     * bytes starting from position <i>pos</i> first byte is at pos 0.
+     * \param pos start sub-stringing from this byte. If it is
+     *      greater than the SBuf length, the SBuf is emptied and
+     *      an empty SBuf is returned
+     * \param n maximum number of bytes of the resulting SBuf.
+     *     SBuf::npos means "to end of SBuf".
+     *     if 0 returns an empty SBuf.
+     */
+    SBuf& chop(size_type pos, size_type n = npos);
+
+    /** Remove characters in the toremove set at the beginning, end or both
+     *
+     * \param toremove characters to be removed. Stops chomping at the first
+     *        found char not in the set
+     * \param atBeginning if true (default), strips at the beginning of the SBuf
+     * \param atEnd if true (default), strips at the end of the SBuf
+     */
+    SBuf& trim(const SBuf &toRemove, bool atBeginning = true, bool atEnd = true);
+
+    /** Extract a part of the current SBuf.
+     *
+     * Return a fresh a fresh copy of a portion the current SBuf, which is left untouched.
+     * \see trim
+     */
+    SBuf substr(size_type pos, size_type n = npos) const;
+
+    /** Find first occurrence of character in SBuf
+     *
+     * Returns the index in the SBuf of the first occurrence of char c.
+     * \return SBuf::npos if the char was not found
+     * \param startPos if specified, ignore any occurrences before that position
+     *     if startPos is SBuf::npos, npos is always returned
+     *     if startPos is < 0, it is ignored
+     */
+    size_type find(char c, size_type startPos = 0) const;
+
+    /** Find first occurrence of SBuf in SBuf.
+     *
+     * Returns the index in the SBuf of the first occurrence of the
+     * sequence contained in the str argument.
+     * \param startPos if specified, ignore any occurrences before that position
+     *     if startPos is SBuf::npos, npos is always returned
+     *     if startPos is < 0, it is ignored
+     * \return SBuf::npos if the SBuf was not found
+     */
+    size_type find(const SBuf & str, size_type startPos = 0) const;
+
+    /** Find last occurrence of character in SBuf
+     *
+     * Returns the index in the SBuf of the last occurrence of char c.
+     * \return SBuf::npos if the char was not found
+     * \param endPos if specified, ignore any occurrences after that position.
+     *   if unspecified or npos, the whole SBuf is considered.
+     *   If < 0, npos is returned
+     */
+    size_type rfind(char c, size_type endPos = npos) const;
+
+    /** Find last occurrence of SBuf in SBuf
+     *
+     * Returns the index in the SBuf of the last occurrence of the
+     * sequence contained in the str argument.
+     * \return SBuf::npos if the sequence  was not found
+     * \param endPos if specified, ignore any occurrences after that position
+     *   if unspecified or npos, the whole SBuf is considered
+     *   if < 0, then npos is always returned
+     */
+    size_type rfind(const SBuf &str, size_type endPos = npos) const;
+
+    /** Find first occurrence of character of set in SBuf
+     *
+     * Finds the first occurrence of ANY of the characters in the supplied set in
+     * the SBuf.
+     * \return SBuf::npos if no character in the set could be found
+     * \param startPos if specified, ignore any occurrences before that position
+     *   if SBuf::npos, then npos is always returned
+     *   if <0, it is ignored.
+     */
+    size_type find_first_of(const SBuf &set, size_type startPos = 0) const;
+
+    /** sscanf-alike
+     *
+     * sscanf re-implementation. Non-const, and not \0-clean.
+     * \return same as sscanf
+     * \see man sscanf(3)
+     */
+    int scanf(const char *format, ...);
+
+    /** Lower-case SBuf
+     *
+     * Returns a lower-cased COPY of the SBuf
+     * \see man tolower(3)
+     */
+    SBuf toLower() const;
+
+    /** Upper-case SBuf
+     *
+     * Returns an upper-cased COPY of the SBuf
+     * \see man toupper(3)
+     */
+    SBuf toUpper() const;
+
+    /** String export function
+     * converts the SBuf to a legacy String, by copy. Transitional.
+     */
+    String toString() const;
+
+    /// TODO: possibly implement erase() similar to std::string's erase
+    /// TODO: possibly implement a replace() call
+private:
+
+    MemBlob::Pointer store_; ///< memory block, possibly shared with other SBufs
+    size_type off_; ///< our content start offset from the beginning of shared store_
+    size_type len_; ///< number of our content bytes in shared store_
+    static SBufStats stats; ///< class-wide statistics
+
+    const InstanceId<SBuf> id; ///< blob identifier
+
+    _SQUID_INLINE_ static MemBlob::Pointer GetStorePrototype();
+
+    _SQUID_INLINE_ char * buf() const;
+    _SQUID_INLINE_ char * bufEnd() const;
+    _SQUID_INLINE_ const size_type estimateCapacity(size_type desired) const;
+    void reAlloc(size_type newsize);
+
+    _SQUID_INLINE_ bool cow(size_type minsize = npos);
+
+    void checkAccessBounds(size_type pos) const;
+    _SQUID_INLINE_ int commonCompareChecksPre(const SBuf &S) const;
+    _SQUID_INLINE_ int commonCompareChecksPost(const SBuf &S) const;
+
+};
+
+/**
+ * Prints a SBuf to the supplied stream, allowing for chaining
+ */
+std::ostream& operator <<(std::ostream &os, const SBuf &S);
+
+#if _USE_INLINE_
+#include "SBuf.cci"
+#endif
+
+#endif /* SQUID_SBUF_H */
diff --git a/src/SBufExceptions.cc b/src/SBufExceptions.cc
new file mode 100644 (file)
index 0000000..89dab78
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * SBufExceptions.cc (C) 2008 Francesco Chemolli <kinkie@squid-cache.org>
+ *
+ * SQUID Web Proxy Cache          http://www.squid-cache.org/
+ * ----------------------------------------------------------
+ *
+ *  Squid is the result of efforts by numerous individuals from
+ *  the Internet community; see the CONTRIBUTORS file for full
+ *  details.   Many organizations have provided support for Squid's
+ *  development; see the SPONSORS file for full details.  Squid is
+ *  Copyrighted (C) 2001 by the Regents of the University of
+ *  California; see the COPYRIGHT file for full details.  Squid
+ *  incorporates software developed and/or copyrighted by other
+ *  sources; see the CREDITS file for full details.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+ */
+
+#include "squid.h"
+#include "OutOfBoundsException.h"
+#include "SBuf.h"
+#include "SBufExceptions.h"
+
+// Note: the SBuf is intentionally passed by copy rather than reference,
+//   to let refcounting act.
+OutOfBoundsException::OutOfBoundsException(const SBuf &throwingBuf,
+        SBuf::size_type &pos,
+        const char *aFileName, int aLineNo)
+        :TextException(NULL, aFileName, aLineNo)
+{
+    _buf = throwingBuf;
+    _pos = pos;
+    SBuf explanatoryText("OutOfBoundsException");
+    if (aLineNo != -1)
+        explanatoryText.appendf(" at line %d", aLineNo);
+    if (aFileName != 0)
+        explanatoryText.appendf(" in file %s", aFileName);
+    explanatoryText.appendf(" while accessing position %d in a SBuf long %d",
+            pos, throwingBuf.length());
+    // we can safely alias c_str as both are local to the object
+    //  and will not further manipulated.
+    message = xstrndup(explanatoryText.c_str(),explanatoryText.length());
+}
+
+OutOfBoundsException::~OutOfBoundsException() throw()
+{ }
+
+NullSBufException::NullSBufException(const char *aFilename, int aLineNo)
+        : TextException("Trying to access a null SBuf", aFilename, aLineNo)
+{ }
+
+InvalidParamException::InvalidParamException(const char *aFilename, int aLineNo)
+        : TextException("Invalid parameter", aFilename, aLineNo)
+{ }
+
+SBufTooBigException::SBufTooBigException(const char *aFilename, int aLineNo)
+        : TextException("Trying to create an oversize SBuf", aFilename, aLineNo)
+{ }
+
+/* */
diff --git a/src/SBufExceptions.h b/src/SBufExceptions.h
new file mode 100644 (file)
index 0000000..e307619
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * SBufExceptions.h (C) 2008 Francesco Chemolli <kinkie@squid-cache.org>
+ *
+ * SQUID Web Proxy Cache          http://www.squid-cache.org/
+ * ----------------------------------------------------------
+ *
+ *  Squid is the result of efforts by numerous individuals from
+ *  the Internet community; see the CONTRIBUTORS file for full
+ *  details.   Many organizations have provided support for Squid's
+ *  development; see the SPONSORS file for full details.  Squid is
+ *  Copyrighted (C) 2001 by the Regents of the University of
+ *  California; see the COPYRIGHT file for full details.  Squid
+ *  incorporates software developed and/or copyrighted by other
+ *  sources; see the CREDITS file for full details.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+ */
+
+#ifndef SQUID_SBUFEXCEPTIONS_H
+#define SQUID_SBUFEXCEPTIONS_H
+
+#include "base/TextException.h"
+
+/**
+ * Exception raised when the user is trying to operate on a Null SBuf
+ * \todo move to an Exceptions.h?
+ */
+class NullSBufException : public TextException
+{
+public:
+    explicit NullSBufException(const char *aFilename = 0, int aLineNo = -1);
+};
+
+/**
+ * Exception raised when call parameters are not valid
+ * \todo move to an Exceptions.h?
+ */
+class InvalidParamException : public TextException
+{
+public:
+    explicit InvalidParamException(const char *aFilename = 0, int aLineNo = -1);
+};
+
+/**
+ * Exception raised when an attempt to resize a SBuf would cause it to reserve too big
+ */
+class SBufTooBigException : public TextException
+{
+public:
+    explicit SBufTooBigException(const char *aFilename = 0, int aLineNo = -1);
+};
+
+#endif /* SQUID_SBUFEXCEPTIONS_H */
diff --git a/src/SBufStream.h b/src/SBufStream.h
new file mode 100644 (file)
index 0000000..fc6044e
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * SQUID Web Proxy Cache          http://www.squid-cache.org/
+ * ----------------------------------------------------------
+ *
+ *  Squid is the result of efforts by numerous individuals from
+ *  the Internet community; see the CONTRIBUTORS file for full
+ *  details.   Many organizations have provided support for Squid's
+ *  development; see the SPONSORS file for full details.  Squid is
+ *  Copyrighted (C) 2001 by the Regents of the University of
+ *  California; see the COPYRIGHT file for full details.  Squid
+ *  incorporates software developed and/or copyrighted by other
+ *  sources; see the CREDITS file for full details.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+ *
+ */
+
+#ifndef SQUID_SBUFSTREAM_H
+#define SQUID_SBUFSTREAM_H
+
+#include "SBuf.h"
+
+#if HAVE_OSTREAM
+#include <ostream>
+#endif
+
+/**
+ * streambuf class for a SBuf-backed stream interface.
+ *
+ */
+class SBufStreamBuf : public std::streambuf
+{
+public:
+    /// initialize streambuf; use supplied SBuf as backing store
+    SBufStreamBuf(SBuf aBuf) : theBuf(aBuf) {}
+
+    /// get a copy of the stream's contents
+    SBuf getBuf() {
+        return theBuf;
+    }
+
+    /// clear the stream's store
+    void clearBuf() {
+        theBuf.clear();
+    }
+
+protected:
+    virtual int_type overflow(int_type aChar = traits_type::eof()) {
+        std::streamsize pending(pptr() - pbase());
+
+        if (pending && sync())
+            return traits_type::eof();
+
+        if (aChar != traits_type::eof()) {
+            char chars[1] = {static_cast<char>(aChar)};
+
+            if (aChar != traits_type::eof())
+                theBuf.append(chars, 0, 1);
+        }
+
+        pbump(-pending);  // Reset pptr().
+        return aChar;
+    }
+
+    /* push the streambuf to the backing SBuf */
+    virtual int sync() {
+        std::streamsize pending(pptr() - pbase());
+
+        if (pending)
+            theBuf.append(pbase(), 0, pending);
+
+        return 0;
+    }
+
+    /* write multiple characters to the store entry
+     * - this is an optimisation method.
+     */
+    virtual std::streamsize xsputn(const char * chars, std::streamsize number) {
+        if (number)
+            theBuf.append(chars, 0, number);
+
+        return number;
+    }
+
+private:
+    SBuf theBuf;
+    SBufStreamBuf(); // no default constructor
+};
+
+/** Stream interface to write to a SBuf.
+ *
+ * Data is appended using standard operator << semantics, and extracted
+ * using the buf() method, in analogy with std::strstream .
+ */
+class SBufStream : public std::ostream
+{
+public:
+    /** Create a SBufStream preinitialized with the argument's SBuf.
+     *
+     * The supplied SBuf is not aliased: in order to retrieve the altered contents
+     * they must be fetched using the buf() class method.
+     */
+    SBufStream(SBuf aBuf): std::ostream(0), theBuffer(aBuf) {
+        rdbuf(&theBuffer); // set the buffer to now-initialized theBuffer
+        clear(); //clear badbit set by calling init(0)
+    }
+
+    /// Create an empty SBufStream
+    SBufStream(): std::ostream(0), theBuffer(SBuf()) {
+        rdbuf(&theBuffer); // set the buffer to now-initialized theBuffer
+        clear(); //clear badbit set by calling init(0)
+    }
+
+    /// Retrieve a copy of the current stream status
+    SBuf buf() {
+        return theBuffer.getBuf();
+    }
+
+    /// Clear the stream's backing store
+    SBufStream& clearBuf() {
+        theBuffer.clearBuf();
+        return *this;
+    }
+
+private:
+    SBufStreamBuf theBuffer;
+};
+
+#endif /* SQUID_SBUFSTREAM_H */
diff --git a/src/tests/SBufFindTest.cc b/src/tests/SBufFindTest.cc
new file mode 100644 (file)
index 0000000..f842540
--- /dev/null
@@ -0,0 +1,431 @@
+#include "squid.h"
+#include "SBufFindTest.h"
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/Message.h>
+#include <limits>
+
+
+/* TODO: The whole SBufFindTest class is currently implemented as a single
+   CppUnit test case (because we do not want to register and report every one
+   of the thousands of generated test cases). Is there a better way to
+   integrate with CppUnit?
+ */
+
+
+SBufFindTest::SBufFindTest():
+    caseLimit(std::numeric_limits<int>::max()),
+    errorLimit(std::numeric_limits<int>::max()),
+    randomSeed(1),
+    hushSimilar(true),
+    maxHayLength(40),
+    thePos(0),
+    thePlacement(placeEof),
+    theStringPos(0),
+    theBareNeedlePos(0),
+    caseCount(0),
+    errorCount(0),
+    reportCount(0)
+{
+}
+
+void
+SBufFindTest::run()
+{
+    srandom(randomSeed);
+
+    for (SBuf::size_type hayLen = 0; hayLen <= maxHayLength; nextLen(hayLen, maxHayLength)) {
+        const SBuf cleanHay = RandomSBuf(hayLen);
+
+        const SBuf::size_type maxNeedleLen = hayLen + 10;
+        for (SBuf::size_type needleLen = 0; needleLen <= maxNeedleLen; nextLen(needleLen, maxNeedleLen)) {
+            theSBufNeedle = RandomSBuf(needleLen);
+
+            for (int i = 0; i < placeEof; i++) {
+                thePlacement = Placement(i);
+                placeNeedle(cleanHay);
+
+                const SBuf::size_type maxArg =
+                   max(theSBufHay.length(), theSBufNeedle.length()) + 10;
+                for (thePos = 0; thePos <= maxArg; nextLen(thePos, maxArg))
+                    testAllMethods();
+
+                // the special npos value is not tested as the behavior is
+                //  different from std::string (where the behavior is undefined)
+                //  It is ad-hoc tested in testSBuf instead
+                //thePos = SBuf::npos;
+                //testAllMethods();
+            }
+        }
+    }
+
+    if (errorCount > 0) {
+        std::cerr << "Generated SBuf test cases: " << caseCount << std::endl;
+        std::cerr << "\tfailed cases: " << errorCount << std::endl;
+        std::cerr << "\treported cases: " << reportCount << std::endl;
+        std::cerr << "Asserting because some cases failed..." << std::endl;
+        CPPUNIT_ASSERT(!SBufFindTest::errorCount);
+    }
+}
+
+/// tests SBuf::find(string needle)
+void
+SBufFindTest::testFindDefs() {
+    theFindString = theBareNeedlePos = theStringHay.find(theStringNeedle);
+    theFindSBuf = theSBufHay.find(theSBufNeedle);
+    checkResults("find");
+}
+
+/// tests SBuf::rfind(string needle)
+void
+SBufFindTest::testRFindDefs() {
+    theFindString = theBareNeedlePos = theStringHay.rfind(theStringNeedle);
+    theFindSBuf = theSBufHay.rfind(theSBufNeedle);
+    checkResults("rfind");
+}
+
+/// tests SBuf::find(string needle, pos)
+void
+SBufFindTest::testFind() {
+    theFindString = theStringHay.find(theStringNeedle, thePos);
+    theBareNeedlePos = theStringHay.find(theStringNeedle);
+    theFindSBuf = theSBufHay.find(theSBufNeedle, thePos);
+    checkResults("find");
+}
+
+/// tests SBuf::find_first_of(string needle, pos)
+void
+SBufFindTest::testFindFirstOf() {
+    theFindString = theStringHay.find_first_of(theStringNeedle, thePos);
+    theBareNeedlePos = theStringHay.find_first_of(theStringNeedle);
+    theFindSBuf = theSBufHay.find_first_of(theSBufNeedle, thePos);
+    checkResults("find_first_of");
+}
+
+
+/// tests SBuf::rfind(string needle, pos)
+void
+SBufFindTest::testRFind() {
+    theFindString = theStringHay.rfind(theStringNeedle, thePos);
+    theBareNeedlePos = theStringHay.rfind(theStringNeedle);
+    theFindSBuf = theSBufHay.rfind(theSBufNeedle, thePos);
+    checkResults("rfind");
+}
+
+/// tests SBuf::find(char needle)
+void
+SBufFindTest::testFindCharDefs() {
+    const char c = theStringNeedle[0];
+    theFindString = theBareNeedlePos = theStringHay.find(c);
+    theFindSBuf = theSBufHay.find(c);
+    checkResults("find");
+}
+
+/// tests SBuf::find(char needle, pos)
+void
+SBufFindTest::testFindChar() {
+    const char c = theStringNeedle[0];
+    theFindString = theStringHay.find(c, thePos);
+    theBareNeedlePos = theStringHay.find(c);
+    theFindSBuf = theSBufHay.find(c, thePos);
+    checkResults("find");
+}
+
+/// tests SBuf::rfind(char needle)
+void
+SBufFindTest::testRFindCharDefs() {
+    const char c = theStringNeedle[0];
+    theFindString = theBareNeedlePos = theStringHay.rfind(c);
+    theFindSBuf = theSBufHay.rfind(c);
+    checkResults("rfind");
+}
+
+/// tests SBuf::rfind(char needle, pos)
+void
+SBufFindTest::testRFindChar() {
+    const char c = theStringNeedle[0];
+    theFindString = theStringHay.rfind(c, thePos);
+    theBareNeedlePos = theStringHay.rfind(c);
+    theFindSBuf = theSBufHay.rfind(c, thePos);
+    checkResults("rfind");
+}
+
+/// whether the last SBuf and std::string find() results are the same
+bool
+SBufFindTest::resultsMatch() const {
+    // this method is needed because SBuf and std::string use different
+    // size_types (and npos values); comparing the result values directly
+    // would lead to bugs
+
+    if (theFindString == std::string::npos && theFindSBuf == SBuf::npos)
+        return true; // both npos
+
+    if (theFindSBuf < 0) // should not happen, treat as error
+       return false;
+
+    // now safe to cast a non-negative SBuf result
+    return theFindString == static_cast<std::string::size_type>(theFindSBuf);
+}
+
+/// called at the end of test case to update state, detect and report failures
+void
+SBufFindTest::checkResults(const char *method) {
+    ++caseCount;
+    if (!resultsMatch())
+        handleFailure(method);
+}
+
+/// helper function to convert "printable" Type to std::string
+template<typename Type>
+inline std::string
+AnyToString(const Type &value)
+{
+    std::stringstream sbuf;
+    sbuf << value;
+    return sbuf.str();
+}
+
+/// helper function to convert SBuf position to a human-friendly string
+inline std::string
+PosToString(const SBuf::size_type pos)
+{
+    return pos == SBuf::npos ? std::string("npos") : AnyToString(pos);
+}
+
+/// helper function to convert std::string position to a human-friendly string
+inline std::string
+PosToString(const std::string::size_type pos)
+{
+    return pos == std::string::npos ? std::string("npos") : AnyToString(pos);
+}
+
+/// tests each supported SBuf::*find() method using generated hay, needle, pos
+void
+SBufFindTest::testAllMethods() {
+    theStringHay = std::string(theSBufHay.rawContent(), theSBufHay.length());
+    theStringNeedle = std::string(theSBufNeedle.rawContent(), theSBufNeedle.length());
+    theBareNeedlePos = std::string::npos;
+    const std::string reportPos = PosToString(thePos);
+
+    // always test string search
+    {
+        theReportQuote = '"';
+        theReportNeedle = theStringNeedle;
+
+        theReportPos = "";
+        testFindDefs();
+        testRFindDefs();
+
+        theReportPos = reportPos;
+        testFind();
+        testRFind();
+        testFindFirstOf();
+    }
+
+    // if possible, test char search
+    if (!theStringNeedle.empty()) {
+        theReportQuote = '\'';
+        theReportNeedle = theStringNeedle[0];
+
+        theReportPos = "";
+        testFindCharDefs();
+        testRFindCharDefs();
+
+        theReportPos = reportPos;
+        testFindChar();
+        testRFindChar();
+    }
+}
+
+/// helper function to format a length-based key (part of case category string)
+inline std::string
+lengthKey(const std::string &str)
+{
+    if (str.length() == 0)
+        return "0";
+    if (str.length() == 1)
+        return "1";
+    return "N";
+}
+
+/// formats position key (part of the case category string)
+std::string
+SBufFindTest::posKey() const
+{
+    // the search position does not matter if needle is not in hay
+    if (theBareNeedlePos == std::string::npos)
+        return std::string();
+
+    if (thePos == SBuf::npos)
+        return ",npos";
+
+    if (thePos < 0)
+        return ",posN"; // negative
+
+    // we know Pos is not negative or special; avoid signed/unsigned warnings
+    const std::string::size_type pos =
+        static_cast<std::string::size_type>(thePos);
+
+    if (pos < theBareNeedlePos)
+        return ",posL"; // to the Left of the needle
+    if (pos == theBareNeedlePos)
+        return ",posB"; // Beginning of the needle
+    if (pos < theBareNeedlePos + theStringNeedle.length())
+        return ",posM"; // in the Middle of the needle
+    if (pos == theBareNeedlePos + theStringNeedle.length())
+        return ",posE"; // at the End of the needle
+    if (pos < theStringHay.length())
+        return ",posR"; // to the Right of the needle
+    return ",posP"; // past the hay
+}
+
+/// formats placement key (part of the case category string)
+std::string
+SBufFindTest::placementKey() const
+{
+    // Ignore thePlacement because theBareNeedlePos covers it better: we may
+    // try to place the needle somewhere, but hay limits the actual placement.
+
+    // the placent does not matter if needle is not in hay
+    if (theBareNeedlePos == std::string::npos)
+        return std::string();
+
+    if (theBareNeedlePos == 0)
+        return "@B"; // at the beggining of the hay string
+    if (theBareNeedlePos == theStringHay.length()-theStringNeedle.length())
+        return "@E"; // at the end of the hay string
+    return "@M"; // in the "middle" of the hay string
+}
+
+/// called when a test case fails; counts and possibly reports the failure
+void
+SBufFindTest::handleFailure(const char *method) {
+    // line break after "........." printed for previous tests
+    if (!errorCount)
+        std::cerr << std::endl;
+
+    ++errorCount;
+
+    if (errorCount > errorLimit) {
+        std::cerr << "Will stop generating SBuf test cases because the " <<
+            "number of failed ones is over the limit: " << errorCount <<
+            " (after " << caseCount << " test cases)" << std::endl;
+        CPPUNIT_ASSERT(errorCount <= errorLimit);
+        /* NOTREACHED */
+    }
+
+    // format test case category; category allows us to hush failure reports
+    // for already seen categories with failed cases (to reduce output noise)
+    std::string category = "hay" + lengthKey(theStringHay) +
+        "." + method + '(';
+    if (theReportQuote == '"')
+        category += "needle" + lengthKey(theStringNeedle);
+    else
+        category += "char";
+    category += placementKey();
+    category += posKey();
+    category += ')';
+
+    if (hushSimilar) {
+        if (failedCats.find(category) != failedCats.end())
+            return; // do not report another similar test case failure
+        failedCats.insert(category);
+    }
+
+    std::string reportPos = theReportPos;
+    if (!reportPos.empty())
+        reportPos = ", " + reportPos;
+
+    std::cerr << "case" << caseCount << ": " <<
+        "SBuf(\"" << theStringHay << "\")." << method <<
+        "(" << theReportQuote << theReportNeedle << theReportQuote <<
+        reportPos << ") returns " << PosToString(theFindSBuf) <<
+        " instead of " << PosToString(theFindString) <<
+        std::endl <<
+        "    std::string(\""  << theStringHay << "\")." << method <<
+        "(" << theReportQuote << theReportNeedle << theReportQuote <<
+        reportPos << ") returns " << PosToString(theFindString) <<
+        std::endl <<
+        "    category: " << category << std::endl;
+
+   ++reportCount;
+}
+
+/// generates a random string of the specified length
+SBuf
+SBufFindTest::RandomSBuf(const int length) {
+    static const char characters[] = 
+        "0123456789"
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+        "abcdefghijklomnpqrstuvwxyz";
+    // sizeof() counts the terminating zero at the end of characters
+    // TODO: add \0 character (needs reporting adjustments to print it as \0)
+    static const size_t charCount = sizeof(characters)-1; 
+
+    char buf[length];
+    for (int i = 0; i < length; ++i) {
+        const unsigned int pos = random() % charCount;
+        assert(pos < sizeof(characters));
+        assert(characters[pos] > 32);
+        buf[i] = characters[random() % charCount];
+    }
+
+    return SBuf(buf, 0, length);
+}
+
+/// increments len to quickly cover [0, max] range, slowing down in risky areas
+/// jumps to max+1 if caseLimit is reached
+void
+SBufFindTest::nextLen(int &len, const int max) {
+    assert(len <= max);
+
+    if (caseCount >= caseLimit)
+        len = max+1; // avoid future test cases
+    else if (len <= 10)
+        ++len; // move slowly at the beginning of the [0,max] range
+    else if (len >= max - 10)
+        ++len; // move slowly at the end of the [0,max] range
+    else {
+        // move fast in the middle of the [0,max] range
+        len += len/10 + 1;
+
+        // but do not overshoot the interesting area at the end of the range
+        if (len > max - 10)
+            len = max - 10;
+    }
+}
+
+/// Places the needle into the hay using cleanHay as a starting point.
+void
+SBufFindTest::placeNeedle(const SBuf &cleanHay) {
+    // For simplicity, we do not overwrite clean hay characters but use them as
+    // needle suffix and/or prefix. Should not matter since hay length varies?
+
+    // TODO: support two needles per hay (explicitly)
+    // TODO: better handle cases where clean hay already contains needle
+    switch (thePlacement)
+    {
+    case placeBeginning:
+        theSBufHay.assign(theSBufNeedle).append(cleanHay);
+        break;
+
+    case placeMiddle:
+    {
+        const SBuf firstHalf = cleanHay.substr(0, cleanHay.length()/2);
+        const SBuf secondHalf = cleanHay.substr(cleanHay.length()/2);
+        theSBufHay.assign(firstHalf).append(theSBufNeedle).append(secondHalf);
+        break;
+    }
+
+    case placeEnd:
+        theSBufHay.assign(cleanHay).append(theSBufNeedle);
+        break;
+
+    case placeNowhere:
+        theSBufHay.assign(cleanHay);
+        break;
+
+    case placeEof:
+        assert(false); // should not happen
+        break;
+    }
+}
diff --git a/src/tests/SBufFindTest.h b/src/tests/SBufFindTest.h
new file mode 100644 (file)
index 0000000..bd65361
--- /dev/null
@@ -0,0 +1,86 @@
+#ifndef SQUID_SRC_TEST_SBUFFINDTEST_H
+#define SQUID_SRC_TEST_SBUFFINDTEST_H
+
+#include "SBuf.h"
+
+#if HAVE_STRING
+#include <string>
+#endif
+#include <set>
+
+/// Generates and executes a [configurable] large number of SBuf::*find()
+/// test cases using random strings. Reports detected failures.
+class SBufFindTest
+{
+public:
+    SBufFindTest();
+
+    void run(); ///< generates and executes cases using configuration params
+
+    /* test configuration parameters; can be optionally set before run() */
+    int caseLimit; ///< approximate caseCount limit
+    int errorLimit; ///< errorCount limit
+    unsigned int randomSeed; ///< pseudo-random sequence choice
+    /// whether to report only one failed test case per "category"
+    bool hushSimilar;
+    /// approximate maximum generated hay string length
+    SBuf::size_type maxHayLength; 
+
+    /// Supported algorithms for placing needle in the hay.
+    typedef enum { placeBeginning, placeMiddle, placeEnd, placeNowhere,
+        placeEof } Placement; // placeLast marker must terminate
+protected:
+
+    static SBuf RandomSBuf(const int length);
+    void nextLen(int &len, const int max);
+    void placeNeedle(const SBuf &cleanHay);
+
+    void testAllMethods();
+    void testFindDefs();
+    void testFind();
+    void testRFindDefs();
+    void testRFind();
+    void testFindCharDefs();
+    void testFindChar();
+    void testRFindCharDefs();
+    void testRFindChar();
+    void testFindFirstOf();
+
+    std::string posKey() const;
+    std::string placementKey() const;
+
+    bool resultsMatch() const;
+    void checkResults(const char *method);
+    void handleFailure(const char *method);
+
+private:
+    /* test case parameters */
+    SBuf theSBufHay; ///< the string to be searched
+    SBuf theSBufNeedle; ///< the string to be found
+    SBuf::size_type thePos; ///< search position limit
+    Placement thePlacement; ///< where in the hay the needle is placed
+    std::string::size_type theStringPos; ///< thePos converted to std::string::size_type
+    std::string theStringHay; ///< theHay converted to std::string
+    std::string theStringNeedle; ///< theNeedle converted to std::string
+
+    /// needle pos w/o thePos restrictions; used for case categorization
+    std::string::size_type theBareNeedlePos;
+
+    /* test case results */
+    std::string::size_type theFindString;
+    SBuf::size_type theFindSBuf;
+    std::string theReportFunc;
+    std::string theReportNeedle;
+    std::string theReportPos;
+    char theReportQuote;
+
+    /* test progress indicators */
+    int caseCount;  ///< cases executed so far
+    int errorCount; ///< total number of failed test cases so far
+    int reportCount; ///< total number of test cases reported so far
+    std::set<std::string> failedCats; ///< reported failed categories
+};
+
+typedef SBufFindTest::Placement Placement;
+
+#endif
diff --git a/src/tests/testSBuf.cc b/src/tests/testSBuf.cc
new file mode 100644 (file)
index 0000000..15ad95d
--- /dev/null
@@ -0,0 +1,723 @@
+#include "squid.h"
+#include "Mem.h"
+#include "SBuf.h"
+#include "SBufList.h"
+#include "SBufStream.h"
+#include "SBufTokenizer.h"
+#include "SBufUtil.h"
+#include "SquidString.h"
+#include "testSBuf.h"
+#include "SBufFindTest.h"
+
+#include <iostream>
+#include <stdexcept>
+
+CPPUNIT_TEST_SUITE_REGISTRATION( testSBuf );
+
+/* let this test link sanely */
+#include "event.h"
+#include "MemObject.h"
+void
+eventAdd(const char *name, EVH * func, void *arg, double when, int, bool cbdata)
+{}
+int64_t
+MemObject::endOffset() const
+{ return 0; }
+/* end of stubs */
+
+// test string
+static char fox[]="The quick brown fox jumped over the lazy dog";
+static char fox1[]="The quick brown fox ";
+static char fox2[]="jumped over the lazy dog";
+
+// TEST: globals variables (default/empty and with contents) are
+//  created outside and before any unit tests and memory subsystem
+//  initialization. Check for correct constructor operation.
+SBuf empty_sbuf;
+SBuf literal("The quick brown fox jumped over the lazy dog");
+
+void
+testSBuf::testSBufConstructDestruct()
+{
+    /* NOTE: Do not initialize memory here because we need
+     * to test correct operation before and after Mem::Init
+     */
+
+    // XXX: partial demo below of how to do constructor unit-test. use scope to ensure each test
+    // is working on local-scope variables constructed fresh for the test, and destructed when
+    // scope exists. use nested scopes to test destructor affects on copied data (MemBlob etc)
+
+    // TEST: default constructor (implicit destructor non-crash test)
+    //  test accessors on empty SBuf.
+    {
+        SBuf s1;
+        CPPUNIT_ASSERT_EQUAL(s1.length(),0);
+        CPPUNIT_ASSERT_EQUAL(s1,SBuf(""));
+        CPPUNIT_ASSERT_EQUAL(s1,empty_sbuf);
+        CPPUNIT_ASSERT(0==strcmp("",s1.c_str()));
+    }
+
+    // TEST: copy-construct NULL string (implicit destructor non-crash test)
+    {
+        SBuf s1(NULL);
+        CPPUNIT_ASSERT_EQUAL(s1.length(),0);
+        CPPUNIT_ASSERT_EQUAL(s1,SBuf(""));
+        CPPUNIT_ASSERT_EQUAL(s1,empty_sbuf);
+        CPPUNIT_ASSERT(0==strcmp("",s1.c_str()));
+    }
+
+    // TEST: copy-construct empty string (implicit destructor non-crash test)
+    {
+        SBuf s1("");
+        CPPUNIT_ASSERT_EQUAL(s1.length(),0);
+        CPPUNIT_ASSERT_EQUAL(s1,SBuf(""));
+        CPPUNIT_ASSERT_EQUAL(s1,empty_sbuf);
+        CPPUNIT_ASSERT(0==strcmp("",s1.c_str()));
+    }
+
+    // TEST: copy-construct from a SBuf
+    {
+        SBuf s1(empty_sbuf);
+        CPPUNIT_ASSERT_EQUAL(s1.length(),0);
+        CPPUNIT_ASSERT_EQUAL(s1,SBuf(""));
+        CPPUNIT_ASSERT_EQUAL(s1,empty_sbuf);
+        CPPUNIT_ASSERT(0==strcmp("",s1.c_str()));
+
+        SBuf s5(literal);
+        CPPUNIT_ASSERT_EQUAL(s5,literal);
+        SBuf s6(fox);
+        CPPUNIT_ASSERT_EQUAL(s6,literal);
+        // XXX: other state checks. expected result of calling any state accessor on s4 ?
+    }
+
+    // TEST: check that COW doesn't happen upon copy-construction
+    {
+        SBuf s1(empty_sbuf), s2(s1);
+        CPPUNIT_ASSERT_EQUAL(s1.rawContent(), s2.rawContent());
+        SBuf s3(literal), s4(literal);
+        CPPUNIT_ASSERT_EQUAL(s3.rawContent(), s4.rawContent());
+    }
+
+    // TEST: sub-string copy
+    {
+        SBuf s1=SBuf(fox,4), s2(fox);
+        SBuf s3=s2.substr(4,s2.length()); //n is out-of-bounds
+        CPPUNIT_ASSERT_EQUAL(s1,s3);
+        SBuf s4=SBuf(fox,0,4);
+        s3=s2.substr(0,4);
+        CPPUNIT_ASSERT_EQUAL(s4,s3);
+    }
+
+    // TEST: go via SquidString adapters.
+    {
+        String str(fox);
+        SBuf s1(str);
+        CPPUNIT_ASSERT_EQUAL(s1,literal);
+    }
+}
+
+void
+testSBuf::testSBufConstructDestructAfterMemInit()
+{
+    Mem::Init();
+    testSBufConstructDestruct();
+
+// XXX: or perhapse ...
+// repeat all of the tests inside testSBufConstructDestructBeforeMemInit()
+// with additional checks on Mem usage stats after each operation ??
+}
+
+void
+testSBuf::testEqualityTest()
+{
+    SBuf s1(fox),s2(fox);
+    CPPUNIT_ASSERT_EQUAL(s1,s1); //self-equality
+    CPPUNIT_ASSERT_EQUAL(s1,s2); //same contents
+    s2.assign("The quick brown fox jumped over the lazy doe");
+    CPPUNIT_ASSERT(!(s1 == s2)); //same length, different contents
+    s2.assign("foo");
+    CPPUNIT_ASSERT(!(s1 == s2)); //different length and contents
+    CPPUNIT_ASSERT(s1 != s2);    //while we're ready, let's test inequality
+    s2.clear();
+    CPPUNIT_ASSERT(!(s1 == s2)); //null and not-null
+    CPPUNIT_ASSERT(s1 != s2);    //while we're ready, let's test inequality
+    s1.clear();
+    CPPUNIT_ASSERT_EQUAL(s1,s2); //null and null
+}
+
+void
+testSBuf::testAppendSBuf()
+{
+    SBuf s1(fox1),s2(fox2);
+    s1.append(s2);
+    CPPUNIT_ASSERT_EQUAL(s1,literal);
+}
+
+void
+testSBuf::testPrintf()
+{
+    SBuf s1,s2;
+    s1.Printf("%s:%d:%03.3f","fox",10,12345.67);
+    s2.assign("fox:10:12345.670");
+    CPPUNIT_ASSERT_EQUAL(s1,s2);
+}
+
+void
+testSBuf::testAppendCString()
+{
+    SBuf s1(fox1);
+    s1.append(fox2);
+    CPPUNIT_ASSERT_EQUAL(s1,literal);
+}
+
+void
+testSBuf::testAppendStdString()
+{
+    SBuf s1(fox1);
+    std::string str(fox2);
+    s1.append(str);
+    CPPUNIT_ASSERT_EQUAL(s1,literal);
+}
+
+void
+testSBuf::testAppendf()
+{
+    SBuf s1,s2;
+    s1.appendf("%s:%d:%03.2f",fox,1234,1234.56);
+    s2.assign("The quick brown fox jumped over the lazy dog:1234:1234.56");
+    CPPUNIT_ASSERT_EQUAL(s1,s2);
+}
+
+void
+testSBuf::testDumpStats()
+{
+    SBuf::GetStats().dump(std::cout);
+    MemBlob::GetStats().dump(std::cout);
+    std::cout << "sizeof(SBuf): " << sizeof(SBuf) << std::endl;
+    std::cout << "sizeof(MemBlob): " << sizeof(MemBlob) << std::endl;
+}
+
+void
+testSBuf::testSubscriptOp()
+{
+    SBuf chg(literal);
+    CPPUNIT_ASSERT_EQUAL(chg[5],'u');
+    chg.setAt(5,'e');
+    CPPUNIT_ASSERT_EQUAL(literal[5],'u');
+    CPPUNIT_ASSERT_EQUAL(chg[5],'e');
+//    std::cout << chg << std::endl << empty_sbuf << std::endl ;
+}
+
+// note: can't use cppunit's CPPUNIT_TEST_EXCEPTION because TextException asserts, and
+// so the test can't be properly completed.
+void
+testSBuf::testSubscriptOpFail()
+{
+    char c;
+    c=literal.at(1234); //out of bounds
+    //notreached
+    std::cout << c << std::endl;
+}
+
+static int sign(int v)
+{
+    if (v < 0)
+        return -1;
+    if (v>0)
+        return 1;
+    return 0;
+}
+
+void
+testSBuf::testComparisons()
+{
+    //same length
+    SBuf s1("foo"),s2("foe");
+    CPPUNIT_ASSERT(s1.compare(s2)>0);
+    CPPUNIT_ASSERT(s1.compare(s2,caseInsensitive,2)==0);
+    CPPUNIT_ASSERT(s1 > s2);
+    CPPUNIT_ASSERT(s2 < s1);
+    CPPUNIT_ASSERT_EQUAL(sign(s1.compare(s2)),sign(strcmp(s1.c_str(),s2.c_str())));
+    //different lengths
+    s1.assign("foo");
+    s2.assign("foof");
+    CPPUNIT_ASSERT(s1.compare(s2)<0);
+    CPPUNIT_ASSERT_EQUAL(sign(s1.compare(s2)),sign(strcmp(s1.c_str(),s2.c_str())));
+    CPPUNIT_ASSERT(s1 < s2);
+}
+
+void
+testSBuf::testConsume()
+{
+    SBuf s1(literal),s2,s3;
+    s2=s1.consume(4);
+    s3.assign("The ");
+    CPPUNIT_ASSERT_EQUAL(s2,s3);
+    s3.assign("quick brown fox jumped over the lazy dog");
+    CPPUNIT_ASSERT_EQUAL(s1,s3);
+    s1.consume(40);
+    CPPUNIT_ASSERT_EQUAL(s1,SBuf());
+}
+
+void
+testSBuf::testRawContent()
+{
+    SBuf s1(literal);
+    SBuf s2(s1);
+    s2.append("foo");
+    const char *foo;
+    foo = s1.rawContent();
+    CPPUNIT_ASSERT(strncmp(fox,foo,s1.length())==0);
+    foo = s1.c_str();
+    CPPUNIT_ASSERT(!strcmp(fox,foo));
+}
+
+void
+testSBuf::testRawSpace()
+{
+    SBuf s1(literal);
+    SBuf s2(fox1);
+    char *rb=s2.rawSpace(strlen(fox2)+1);
+    strcat(rb,fox2);
+    CPPUNIT_ASSERT_EQUAL(s1,s2);
+}
+
+void
+testSBuf::testChop()
+{
+    SBuf s1(literal),s2;
+    s1.chop(4,5);
+    s2.assign("quick");
+    CPPUNIT_ASSERT_EQUAL(s1,s2);
+    s1=literal;
+    s2.clear();
+    s1.chop(5,0);
+    CPPUNIT_ASSERT_EQUAL(s1,s2);
+}
+
+void
+testSBuf::testChomp()
+{
+    SBuf s1("complete string");
+    SBuf s2(s1);
+    s2.trim(SBuf(" ,"));
+    CPPUNIT_ASSERT_EQUAL(s1,s2);
+    s2.assign(" complete string ,");
+    s2.trim(SBuf(" ,"));
+    CPPUNIT_ASSERT_EQUAL(s1,s2);
+    s1.assign(", complete string ,");
+    s2=s1;
+    s2.trim(SBuf(" "));
+    CPPUNIT_ASSERT_EQUAL(s1,s2);
+}
+
+void
+testSBuf::testSubstr()
+{
+    SBuf s1(literal),s2,s3;
+    s2=s1.substr(4,5);
+    s3.assign("quick");
+    CPPUNIT_ASSERT_EQUAL(s2,s3);
+    s1.chop(4,5);
+    CPPUNIT_ASSERT_EQUAL(s1,s2);
+}
+
+void
+testSBuf::testFindChar()
+{
+    const char *alphabet="abcdefghijklmnopqrstuvwxyz";
+    SBuf s1(alphabet);
+    SBuf::size_type idx;
+    SBuf::size_type nposResult=SBuf::npos;
+
+    // FORWARD SEARCH
+    // needle in haystack
+    idx=s1.find('d');
+    CPPUNIT_ASSERT(idx == 3);
+    CPPUNIT_ASSERT(s1[idx]=='d');
+
+    // needle not present in haystack
+    idx=s1.find(' '); //fails
+    CPPUNIT_ASSERT_EQUAL(nposResult,idx);
+
+    // search in portion
+    idx=s1.find('e',3);
+    CPPUNIT_ASSERT_EQUAL(4,idx);
+
+    // char not in searched portion
+    idx=s1.find('e',5);
+    CPPUNIT_ASSERT_EQUAL(nposResult,idx);
+
+    // invalid start position
+    idx=s1.find('d',SBuf::npos);
+    CPPUNIT_ASSERT_EQUAL(nposResult,idx);
+
+    // invalid start position
+    idx=s1.find('d', -5);
+    CPPUNIT_ASSERT_EQUAL(3, idx);
+
+    // search outside of haystack
+    idx=s1.find('d',s1.length()+1);
+    CPPUNIT_ASSERT_EQUAL(nposResult,idx);
+
+    // REVERSE SEARCH
+    // needle in haystack
+    idx=s1.rfind('d');
+    CPPUNIT_ASSERT_EQUAL(3, idx);
+    CPPUNIT_ASSERT_EQUAL('d', s1[idx]);
+
+    // needle not present in haystack
+    idx=s1.rfind(' '); //fails
+    CPPUNIT_ASSERT_EQUAL(nposResult,idx);
+
+    // search in portion
+    idx=s1.rfind('e',5);
+    CPPUNIT_ASSERT_EQUAL(4,idx);
+
+    // char not in searched portion
+    idx=s1.rfind('e',3);
+    CPPUNIT_ASSERT_EQUAL(nposResult,idx);
+
+    // invalid start position
+    idx=s1.rfind('d', -5);
+    CPPUNIT_ASSERT_EQUAL(nposResult,idx);
+
+    // overlong haystack specification
+    idx=s1.rfind('d',s1.length()+1);
+    CPPUNIT_ASSERT_EQUAL(3,idx);
+}
+
+void
+testSBuf::testFindSBuf()
+{
+    const char *alphabet="abcdefghijklmnopqrstuvwxyz";
+    SBuf haystack(alphabet);
+    SBuf::size_type idx;
+    SBuf::size_type nposResult=SBuf::npos;
+
+    // FORWARD search
+    // needle in haystack
+    idx = haystack.find(SBuf("def"));
+    CPPUNIT_ASSERT_EQUAL(3,idx);
+
+    idx = haystack.find(SBuf("xyz"));
+    CPPUNIT_ASSERT_EQUAL(23,idx);
+
+    // needle not in haystack, no initial char match
+    idx = haystack.find(SBuf(" eq"));
+    CPPUNIT_ASSERT_EQUAL(nposResult, idx);
+
+    // needle not in haystack, initial sequence match
+    idx = haystack.find(SBuf("deg"));
+    CPPUNIT_ASSERT_EQUAL(nposResult, idx);
+
+    // needle past end of haystack
+    idx = haystack.find(SBuf("xyz1"));
+    CPPUNIT_ASSERT_EQUAL(nposResult, idx);
+
+    // search in portion: needle not in searched part
+    idx = haystack.find(SBuf("def"),7);
+    CPPUNIT_ASSERT_EQUAL(nposResult, idx);
+
+    // search in portion: overhang
+    idx = haystack.find(SBuf("def"),4);
+    CPPUNIT_ASSERT_EQUAL(nposResult, idx);
+
+    // invalid start position
+    idx = haystack.find(SBuf("def"),SBuf::npos);
+    CPPUNIT_ASSERT_EQUAL(nposResult, idx);
+
+    // invalid start position: negative
+    idx = haystack.find(SBuf("def"),-5);
+    CPPUNIT_ASSERT_EQUAL(3, idx);
+
+    // needle bigger than haystack
+    idx = SBuf("def").find(haystack);
+    CPPUNIT_ASSERT_EQUAL(nposResult, idx);
+
+    // search in a double-matching haystack
+    {
+        SBuf h2=haystack;
+        h2.append(haystack);
+
+        idx = h2.find(SBuf("def"));
+        CPPUNIT_ASSERT_EQUAL(3,idx);
+
+        idx = h2.find(SBuf("xyzab"));
+        CPPUNIT_ASSERT_EQUAL(23,idx);
+    }
+
+
+    // REVERSE search
+    // needle in haystack
+    idx = haystack.rfind(SBuf("def"));
+    CPPUNIT_ASSERT_EQUAL(3,idx);
+
+    idx = haystack.rfind(SBuf("xyz"));
+    CPPUNIT_ASSERT_EQUAL(23,idx);
+
+    // needle not in haystack, no initial char match
+    idx = haystack.rfind(SBuf(" eq"));
+    CPPUNIT_ASSERT_EQUAL(nposResult, idx);
+
+    // needle not in haystack, initial sequence match
+    idx = haystack.rfind(SBuf("deg"));
+    CPPUNIT_ASSERT_EQUAL(nposResult, idx);
+
+    // needle past end of haystack
+    idx = haystack.rfind(SBuf("xyz1"));
+    CPPUNIT_ASSERT_EQUAL(nposResult, idx);
+
+    // search in portion: needle in searched part
+    idx = haystack.rfind(SBuf("def"),7);
+    CPPUNIT_ASSERT_EQUAL(3, idx);
+
+    // search in portion: needle not in searched part
+    idx = haystack.rfind(SBuf("mno"),3);
+    CPPUNIT_ASSERT_EQUAL(nposResult, idx);
+
+    // search in portion: overhang
+    idx = haystack.rfind(SBuf("def"),4);
+    CPPUNIT_ASSERT_EQUAL(3, idx);
+
+    // npos start position
+    idx = haystack.rfind(SBuf("def"),SBuf::npos);
+    CPPUNIT_ASSERT_EQUAL(3, idx);
+
+    // invalid start position: negative
+    idx = haystack.rfind(SBuf("def"),-5);
+    CPPUNIT_ASSERT_EQUAL(nposResult, idx);
+
+    // needle bigger than haystack
+    idx = SBuf("def").rfind(haystack);
+    CPPUNIT_ASSERT_EQUAL(nposResult, idx);
+
+    // search in a double-matching haystack
+    {
+        SBuf h2=haystack;
+        h2.append(haystack);
+
+        idx = h2.rfind(SBuf("def"));
+        CPPUNIT_ASSERT_EQUAL(29,idx);
+
+        idx = h2.find(SBuf("xyzab"));
+        CPPUNIT_ASSERT_EQUAL(23,idx);
+    }
+}
+
+void
+testSBuf::testRFindChar()
+{
+    SBuf s1(literal);
+    SBuf::size_type idx;
+    idx=s1.rfind(' ');
+    CPPUNIT_ASSERT_EQUAL(40,idx);
+    CPPUNIT_ASSERT_EQUAL(' ',s1[idx]);
+}
+
+void
+testSBuf::testRFindSBuf()
+{
+    SBuf haystack(literal),afox("fox");
+    SBuf goobar("goobar");
+    SBuf::size_type idx;
+
+    // corner case: search for a zero-length SBuf
+    idx=haystack.rfind(SBuf(""));
+    CPPUNIT_ASSERT_EQUAL(haystack.length(),idx);
+
+    // corner case: search for a needle longer than the haystack
+    idx=afox.rfind(SBuf("     "));
+    CPPUNIT_ASSERT(idx==SBuf::npos);
+
+    idx=haystack.rfind(SBuf("fox"));
+    CPPUNIT_ASSERT_EQUAL(16,idx);
+
+    // needle not found, no match for first char
+    idx=goobar.rfind(SBuf("foo"));
+    CPPUNIT_ASSERT(idx==SBuf::npos);
+
+    // needle not found, match for first char but no match for SBuf
+    idx=haystack.rfind(SBuf("foe"));
+    CPPUNIT_ASSERT(idx==SBuf::npos);
+
+    SBuf g("g"); //match at the last char
+    idx=haystack.rfind(g);
+    CPPUNIT_ASSERT_EQUAL(43,idx);
+    CPPUNIT_ASSERT_EQUAL('g',haystack[idx]);
+
+    idx=haystack.rfind(SBuf("The"));
+    CPPUNIT_ASSERT_EQUAL(0,idx);
+
+    haystack.append("The");
+    idx=haystack.rfind(SBuf("The"));
+    CPPUNIT_ASSERT_EQUAL(44,idx);
+
+    //partial match
+    haystack="The quick brown fox";
+    SBuf needle("foxy lady");
+    idx=haystack.rfind(needle);
+    CPPUNIT_ASSERT(idx==SBuf::npos);
+}
+
+void
+testSBuf::testSBufLength()
+{
+    SBuf s(fox);
+    CPPUNIT_ASSERT((size_t)s.length()==strlen(fox));
+}
+
+void
+testSBuf::testScanf()
+{
+    SBuf s1;
+    char s[128];
+    int i;
+    float f;
+    int rv;
+    s1.assign("string , 123 , 123.50");
+    rv=s1.scanf("%s , %d , %f",s,&i,&f);
+    CPPUNIT_ASSERT(3 == rv);
+    CPPUNIT_ASSERT(0 == strcmp(s,"string"));
+    CPPUNIT_ASSERT(i == 123);
+    CPPUNIT_ASSERT(f == 123.5);
+}
+
+void testSBuf::testCopy()
+{
+    char buf[40]; //shorter than literal()
+    SBuf s(fox1),s2;
+    CPPUNIT_ASSERT(s.copy(buf,40)==s.length());
+    CPPUNIT_ASSERT(strncmp(s.rawContent(),buf,s.length())==0);
+    s=literal;
+    CPPUNIT_ASSERT(s.copy(buf,40)==40);
+    s2.assign(buf,0,40);
+    s.chop(0,40);
+    CPPUNIT_ASSERT(s==s2);
+}
+
+// XXX: please split each class into a separate unit-test .cc / even if they share a binary.
+//  rule-of-thumb guideline for layering is 'one class Foo -> one Foo.cc,  one Foo.cc -> one testFoo.cc'
+
+static int sbuf_tokens_number=9;
+static SBuf tokens[]={
+    SBuf("The",0,3), SBuf("quick",0,5), SBuf("brown",0,5), SBuf("fox",0,3),
+    SBuf("jumped",0,6), SBuf("over",0,4), SBuf("the",0,3), SBuf("lazy",0,4),
+    SBuf("dog",0,3)
+};
+
+void testSBuf::testSBufTokenizer()
+{
+    int j=0;
+    SBuf s;
+    for (SBufTokenizer st(literal,SBuf(" ",0,1)); !st.atEnd(); st.next()) {
+        s=st.token();
+        CPPUNIT_ASSERT(s==tokens[j]);
+        j++;
+    }
+    CPPUNIT_ASSERT(j==9);
+}
+
+void testSBuf::testStringOps()
+{
+    SBuf sng(literal),
+    ref("the quick brown fox jumped over the lazy dog");
+    sng=sng.toLower();
+    CPPUNIT_ASSERT_EQUAL(ref,sng);
+    sng=literal;
+    CPPUNIT_ASSERT(0==sng.compare(ref,caseInsensitive));
+}
+
+void testSBuf::testGrow()
+{
+    SBuf t;
+    t.assign("foo");
+    const char *ref=t.rawContent();
+    t.reserveCapacity(10240);
+    const char *match=t.rawContent();
+    CPPUNIT_ASSERT(match!=ref);
+    ref=match;
+    t.append(literal).append(literal).append(literal).append(literal).append(literal);
+    t.append(t).append(t).append(t).append(t).append(t);
+    CPPUNIT_ASSERT(match==ref);
+}
+
+void testSBuf::testStartsWith()
+{
+    static SBuf casebuf("THE QUICK");
+    CPPUNIT_ASSERT(literal.startsWith(SBuf(fox1)));
+    CPPUNIT_ASSERT(!SBuf("The quick brown").startsWith(SBuf(fox1))); //too short
+    CPPUNIT_ASSERT(!literal.startsWith(SBuf(fox2))); //wrong contents
+
+    CPPUNIT_ASSERT(literal.startsWith(casebuf,caseInsensitive));
+    casebuf=SBuf(fox1).toUpper();
+    CPPUNIT_ASSERT(literal.startsWith(casebuf,caseInsensitive));
+    CPPUNIT_ASSERT(literal.startsWith(SBuf(fox1),caseInsensitive));
+    casebuf = "tha quick";
+    CPPUNIT_ASSERT(!literal.startsWith(casebuf,caseInsensitive));
+}
+
+void testSBuf::testSBufList()
+{
+    SBufList foo;
+    for (int j=0; j<sbuf_tokens_number; ++j)
+        foo.add(tokens[j]);
+    CPPUNIT_ASSERT(foo.isMember(SBuf("fox")));
+    CPPUNIT_ASSERT(foo.isMember(SBuf("Fox"),caseInsensitive));
+    CPPUNIT_ASSERT(!foo.isMember(SBuf("garble")));
+    CPPUNIT_ASSERT(foo.isPrefix(SBuf("qui")));
+    CPPUNIT_ASSERT(foo.isPrefix(SBuf("qUi"),caseInsensitive));
+    CPPUNIT_ASSERT(!foo.isPrefix(SBuf("qUa"),caseInsensitive));
+}
+
+void testSBuf::testBaseName()
+{
+    SBuf totest("/foo/bar/gazonk");
+    CPPUNIT_ASSERT_EQUAL(BaseName(totest),SBuf("gazonk"));
+    CPPUNIT_ASSERT_EQUAL(BaseName(totest,'.'),totest);
+}
+
+void testSBuf::testSBufStream()
+{
+    SBuf b("const.string, int 10 and a float 10.5");
+    SBufStream ss;
+    ss << "const.string, int " << 10 << " and a float " << 10.5;
+    SBuf o=ss.buf();
+    CPPUNIT_ASSERT_EQUAL(b,o);
+    ss.clearBuf();
+    o=ss.buf();
+    CPPUNIT_ASSERT_EQUAL(SBuf(),o);
+    SBuf f1(fox1);
+    SBufStream ss2(f1);
+    ss2 << fox2;
+    CPPUNIT_ASSERT_EQUAL(ss2.buf(),literal);
+    CPPUNIT_ASSERT_EQUAL(f1,SBuf(fox1));
+}
+
+void testSBuf::testFindFirstOf()
+{
+    SBuf haystack(literal);
+    SBuf::size_type idx;
+
+    // not found
+    idx=haystack.find_first_of(SBuf("ADHRWYP"));
+    CPPUNIT_ASSERT(idx==SBuf::npos);
+
+    // found at beginning
+    idx=haystack.find_first_of(SBuf("THANDF"));
+    CPPUNIT_ASSERT_EQUAL(0,idx);
+
+    //found at end of haystack
+    idx=haystack.find_first_of(SBuf("QWERYVg"));
+    CPPUNIT_ASSERT_EQUAL(haystack.length()-1,idx);
+
+    //found in the middle of haystack
+    idx=haystack.find_first_of(SBuf("QWERqYV"));
+    CPPUNIT_ASSERT_EQUAL(4,idx);
+}
+
+void testSBuf::testAutoFind()
+{
+    SBufFindTest test;
+    test.run();
+}
diff --git a/src/tests/testSBuf.h b/src/tests/testSBuf.h
new file mode 100644 (file)
index 0000000..b3c4374
--- /dev/null
@@ -0,0 +1,98 @@
+#ifndef SQUID_SRC_TEST_TESTSBUF_H
+#define SQUID_SRC_TEST_TESTSBUF_H
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#if 0 // guideline violation: only #include into .h when used by the .h
+#include "SBuf.h"
+#include "SBufExceptions.h"
+#include "SBufList.h"
+#include "SBufUtil.h"
+#endif
+#include "OutOfBoundsException.h"
+
+/*
+ * test the SBuf functionalities
+ */
+
+class testSBuf : public CPPUNIT_NS::TestFixture
+{
+    CPPUNIT_TEST_SUITE( testSBuf );
+    CPPUNIT_TEST( testSBufConstructDestruct );
+    CPPUNIT_TEST( testSBufConstructDestructAfterMemInit );
+    CPPUNIT_TEST( testSBufLength );
+    CPPUNIT_TEST( testEqualityTest );
+    CPPUNIT_TEST( testStartsWith );
+    CPPUNIT_TEST( testAppendSBuf );
+    CPPUNIT_TEST( testAppendCString );
+    CPPUNIT_TEST( testAppendStdString );
+    CPPUNIT_TEST( testAppendf );
+    CPPUNIT_TEST( testSubscriptOp );
+    CPPUNIT_TEST_EXCEPTION( testSubscriptOpFail , OutOfBoundsException );
+    CPPUNIT_TEST( testComparisons );
+    CPPUNIT_TEST( testConsume );
+    CPPUNIT_TEST( testRawContent );
+    //CPPUNIT_TEST( testRawSpace );
+    CPPUNIT_TEST( testChop );
+    CPPUNIT_TEST( testChomp );
+    CPPUNIT_TEST( testSubstr );
+    CPPUNIT_TEST( testFindChar );
+    CPPUNIT_TEST( testFindSBuf );
+    CPPUNIT_TEST( testRFindChar );
+    CPPUNIT_TEST( testRFindSBuf );
+    CPPUNIT_TEST( testFindFirstOf );
+    CPPUNIT_TEST( testPrintf );
+    CPPUNIT_TEST( testScanf );
+    CPPUNIT_TEST( testCopy );
+    CPPUNIT_TEST( testSBufTokenizer );
+    CPPUNIT_TEST( testStringOps );
+    CPPUNIT_TEST( testGrow );
+    CPPUNIT_TEST( testSBufList );
+    CPPUNIT_TEST( testBaseName );
+    CPPUNIT_TEST( testSBufStream );
+    CPPUNIT_TEST( testAutoFind );
+
+//    CPPUNIT_TEST( testDumpStats ); //fake test, to print alloc stats
+
+    CPPUNIT_TEST_SUITE_END();
+protected:
+    void commonInit();
+    void testSBufConstructDestruct();
+    void testSBufConstructDestructAfterMemInit();
+    void testEqualityTest();
+    void testAppendSBuf();
+    void testAppendCString();
+    void testAppendStdString();
+    void testAppendf();
+    void testPrintf();
+    void testScanf();
+    void testSubscriptOp();
+    void testSubscriptOpFail();
+    void testDumpStats();
+    void testComparisons();
+    void testConsume();
+    void testRawContent();
+    void testRawSpace();
+    void testChop();
+    void testChomp();
+    void testSubstr();
+    void testTailCopy();
+    void testSBufLength();
+    void testFindChar();
+    void testFindSBuf();
+    void testRFindChar();
+    void testRFindSBuf();
+    void testSearchFail();
+    void testCopy();
+    void testSBufTokenizer();
+    void testStringOps();
+    void testGrow();
+    void testStartsWith();
+    void testSBufList();
+    void testBaseName();
+    void testSBufStream();
+    void testFindFirstOf();
+    void testAutoFind();
+};
+
+#endif