/* This is the byte-counted string class for rsyslog.
 * This object has a lot of legacy. Among others, it was started to
 * support embedded \0 bytes, which looked like they were needed to
 * be supported by RFC developments at that time. Later, this was
 * no longer a requirement, and we refactored the class in 2016
 * to some simpler internals which make use of the fact that no
 * NUL can ever occur in rsyslog strings (they are escaped at the
 * input side of rsyslog).
 * It now serves primarily to a) dynamic string creation, b) keep
 * old interfaces supported, and c) some special functionality,
 * e.g. search. Further refactoring and simplificytin may make
 * sense.
 *
 * Copyright (C) 2005-2025 Adiscon GmbH
 *
 * This file is part of the rsyslog runtime library.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *       -or-
 *       see COPYING.ASL20 in the source distribution
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "config.h"

#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <sys/types.h>
#include <libestr.h>
#include "rsyslog.h"
#include "stringbuf.h"
#include "srUtils.h"
#include "regexp.h"
#include "errmsg.h"
#include "unicode-helper.h"

#define DEV_DEBUG 0 /* set to 1 to enable very verbose developer debugging messages */


/* ################################################################# *
 * private members                                                   *
 * ################################################################# */

/* static data */
DEFobjCurrIf(obj) DEFobjCurrIf(regexp)

    /* ################################################################# *
     * public members                                                    *
     * ################################################################# */


    rsRetVal cstrConstruct(cstr_t **const ppThis) {
    DEFiRet;
    cstr_t *pThis;

    CHKmalloc(pThis = (cstr_t *)malloc(sizeof(cstr_t)));
    rsSETOBJTYPE(pThis, OIDrsCStr);
#ifndef NDEBUG
    pThis->isFinalized = 0;
#endif
    pThis->pBuf = NULL;
    pThis->iBufSize = 0;
    pThis->iStrLen = 0;
    *ppThis = pThis;

finalize_it:
    RETiRet;
}


/* construct from sz string
 * rgerhards 2005-09-15
 */
rsRetVal rsCStrConstructFromszStr(cstr_t **const ppThis, const uchar *const sz) {
    DEFiRet;
    cstr_t *pThis;

    CHKiRet(rsCStrConstruct(&pThis));

    pThis->iStrLen = strlen((char *)sz);
    pThis->iBufSize = strlen((char *)sz) + 1;
    if ((pThis->pBuf = (uchar *)malloc(pThis->iBufSize)) == NULL) {
        RSFREEOBJ(pThis);
        ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
    }

    /* we do NOT need to copy the \0! */
    memcpy(pThis->pBuf, sz, pThis->iStrLen);

    *ppThis = pThis;

finalize_it:
    RETiRet;
}


/* a helper function for rsCStr*Strf()
 */
static rsRetVal rsCStrConstructFromszStrv(cstr_t **ppThis, const char *fmt, va_list ap)
    __attribute__((format(printf, 2, 0)));
static rsRetVal rsCStrConstructFromszStrv(cstr_t **const ppThis, const char *const fmt, va_list ap) {
    DEFiRet;
    cstr_t *pThis;
    va_list ap2;
    int len;

    va_copy(ap2, ap);
    len = vsnprintf(NULL, 0, (char *)fmt, ap2);
    va_end(ap2);

    if (len < 0) ABORT_FINALIZE(RS_RET_ERR);

    CHKiRet(rsCStrConstruct(&pThis));

    pThis->iStrLen = len;
    pThis->iBufSize = len + 1;
    len++; /* account for the \0 written by vsnprintf */
    if ((pThis->pBuf = (uchar *)malloc(pThis->iBufSize)) == NULL) {
        RSFREEOBJ(pThis);
        ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
    }

    vsnprintf((char *)pThis->pBuf, len, (char *)fmt, ap);
    *ppThis = pThis;
finalize_it:
    RETiRet;
}


/* construct from a printf-style formated string
 */
rsRetVal rsCStrConstructFromszStrf(cstr_t **ppThis, const char *fmt, ...) {
    DEFiRet;
    va_list ap;

    va_start(ap, fmt);
    iRet = rsCStrConstructFromszStrv(ppThis, fmt, ap);
    va_end(ap);

    RETiRet;
}


/* construct from es_str_t string
 * rgerhards 2010-12-03
 */
rsRetVal cstrConstructFromESStr(cstr_t **const ppThis, es_str_t *const str) {
    DEFiRet;
    cstr_t *pThis;

    CHKiRet(rsCStrConstruct(&pThis));

    pThis->iStrLen = es_strlen(str);
    pThis->iBufSize = pThis->iStrLen + 1;
    if ((pThis->pBuf = (uchar *)malloc(pThis->iBufSize)) == NULL) {
        RSFREEOBJ(pThis);
        ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
    }

    /* we do NOT need to copy the \0! */
    memcpy(pThis->pBuf, es_getBufAddr(str), pThis->iStrLen);

    *ppThis = pThis;

finalize_it:
    RETiRet;
}

/* construct from CStr object.
 * rgerhards 2005-10-18
 */
rsRetVal ATTR_NONNULL() rsCStrConstructFromCStr(cstr_t **const ppThis, const cstr_t *const pFrom) {
    DEFiRet;
    cstr_t *pThis;

    rsCHECKVALIDOBJECT(pFrom, OIDrsCStr);

    CHKiRet(rsCStrConstruct(&pThis));
    if (pFrom->iStrLen > 0) {
        pThis->iStrLen = pFrom->iStrLen;
        pThis->iBufSize = pFrom->iStrLen + 1;
        if ((pThis->pBuf = (uchar *)malloc(pThis->iBufSize)) == NULL) {
            RSFREEOBJ(pThis);
            ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
        }
        memcpy(pThis->pBuf, pFrom->pBuf, pThis->iStrLen);
    }

    *ppThis = pThis;
finalize_it:
    RETiRet;
}


void rsCStrDestruct(cstr_t **const ppThis) {
    free((*ppThis)->pBuf);
    RSFREEOBJ(*ppThis);
    *ppThis = NULL;
}


/* extend the string buffer if its size is insufficient.
 * Param iMinNeeded is the minumum free space needed. If it is larger
 * than the default alloc increment, space for at least this amount is
 * allocated. In practice, a bit more is allocated because we envision that
 * some more characters may be added after these.
 * rgerhards, 2008-01-07
 * changed to utilized realloc() -- rgerhards, 2009-06-16
 */
static rsRetVal rsCStrExtendBuf(cstr_t *const __restrict__ pThis, const size_t iMinNeeded) {
    uchar *pNewBuf;
    size_t iNewSize;
    DEFiRet;

    /* first compute the new size needed */
    if (iMinNeeded > RS_STRINGBUF_ALLOC_INCREMENT) {
        /* we allocate "n" ALLOC_INCREMENTs. Usually, that should
         * leave some room after the absolutely needed one. It also
         * reduces memory fragmentation. Note that all of this are
         * integer operations (very important to understand what is
         * going on)! Parenthesis are for better readibility.
         */
        iNewSize = (iMinNeeded / RS_STRINGBUF_ALLOC_INCREMENT + 1) * RS_STRINGBUF_ALLOC_INCREMENT;
    } else {
        iNewSize = pThis->iBufSize + RS_STRINGBUF_ALLOC_INCREMENT;
    }
    iNewSize += pThis->iBufSize; /* add current size */

#if DEV_DEBUG == 1
    dbgprintf("extending string buffer, old %d, new %d\n", pThis->iBufSize, iNewSize);
#endif
    CHKmalloc(pNewBuf = (uchar *)realloc(pThis->pBuf, iNewSize));
    pThis->iBufSize = iNewSize;
    pThis->pBuf = pNewBuf;

finalize_it:
    RETiRet;
}

/* Append a character to the current string object. This may only be done until
 * cstrFinalize() is called.
 * rgerhards, 2009-06-16
 */
rsRetVal cstrAppendChar(cstr_t *const __restrict__ pThis, const uchar c) {
    rsRetVal iRet = RS_RET_OK;

    if (pThis->iStrLen + 1 >= pThis->iBufSize) {
        CHKiRet(rsCStrExtendBuf(pThis, 1)); /* need more memory! */
    }

    /* ok, when we reach this, we have sufficient memory */
    *(pThis->pBuf + pThis->iStrLen++) = c;

finalize_it:
    return iRet;
}

/* append a string of known length. In this case, we make sure we do at most
 * one additional memory allocation.
 */
rsRetVal rsCStrAppendStrWithLen(cstr_t *const pThis, const uchar *const psz, const size_t iStrLen) {
    DEFiRet;

    rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
    assert(psz != NULL);

    /* does the string fit? */
    if (pThis->iStrLen + iStrLen >= pThis->iBufSize) {
        CHKiRet(rsCStrExtendBuf(pThis, iStrLen)); /* need more memory! */
    }

    /* ok, now we always have sufficient continues memory to do a memcpy() */
    memcpy(pThis->pBuf + pThis->iStrLen, psz, iStrLen);
    pThis->iStrLen += iStrLen;

finalize_it:
    RETiRet;
}


/* changed to be a wrapper to rsCStrAppendStrWithLen() so that
 * we can save some time when we have the length but do not
 * need to change existing code.
 * rgerhards, 2007-07-03
 */
rsRetVal rsCStrAppendStr(cstr_t *const pThis, const uchar *const psz) {
    return rsCStrAppendStrWithLen(pThis, psz, strlen((char *)psz));
}


/* append the contents of one cstr_t object to another
 * rgerhards, 2008-02-25
 */
rsRetVal cstrAppendCStr(cstr_t *pThis, cstr_t *pstrAppend) {
    return rsCStrAppendStrWithLen(pThis, pstrAppend->pBuf, pstrAppend->iStrLen);
}


/* append a printf-style formated string
 */
rsRetVal rsCStrAppendStrf(cstr_t *pThis, const char *fmt, ...) {
    DEFiRet;
    va_list ap;
    cstr_t *pStr = NULL;

    va_start(ap, fmt);
    iRet = rsCStrConstructFromszStrv(&pStr, (char *)fmt, ap);
    va_end(ap);

    if (iRet != RS_RET_OK) goto finalize_it;

    iRet = cstrAppendCStr(pThis, pStr);
    rsCStrDestruct(&pStr);
finalize_it:
    RETiRet;
}


rsRetVal rsCStrAppendInt(cstr_t *pThis, long i) {
    DEFiRet;
    uchar szBuf[32];

    rsCHECKVALIDOBJECT(pThis, OIDrsCStr);

    CHKiRet(srUtilItoA((char *)szBuf, sizeof(szBuf), i));

    iRet = rsCStrAppendStr(pThis, szBuf);
finalize_it:
    RETiRet;
}


/* Sets the string object to the classigal sz-string provided.
 * Any previously stored vlaue is discarded. If a NULL pointer
 * the the new value (pszNew) is provided, an empty string is
 * created (this is NOT an error!).
 * rgerhards, 2005-10-18
 */
rsRetVal rsCStrSetSzStr(cstr_t *const __restrict__ pThis, uchar *const __restrict__ pszNew) {
    rsCHECKVALIDOBJECT(pThis, OIDrsCStr);

    if (pszNew == NULL) {
        free(pThis->pBuf);
        pThis->pBuf = NULL;
        pThis->iStrLen = 0;
        pThis->iBufSize = 0;
    } else {
        const size_t newlen = strlen((char *)pszNew);
        if (newlen > pThis->iBufSize) {
            uchar *const newbuf = (uchar *)realloc(pThis->pBuf, newlen + 1);
            if (newbuf == NULL) {
                /* we keep the old value, best we can do */
                return RS_RET_OUT_OF_MEMORY;
            }
            pThis->pBuf = newbuf;
            pThis->iBufSize = newlen + 1;
        }
        pThis->iStrLen = newlen;
        memcpy(pThis->pBuf, pszNew, pThis->iStrLen);
    }

    return RS_RET_OK;
}

/* Converts the CStr object to a classical zero-terminated C string
 * and returns that string. The caller must not free it and must not
 * destroy the CStr object as long as the ascii string is used.
 */
uchar *cstrGetSzStrNoNULL(cstr_t *const __restrict__ pThis) {
    assert(pThis->isFinalized);
    return (pThis->pBuf == NULL) ? (uchar *)"" : pThis->pBuf;
}


/* Converts the CStr object to a classical zero-terminated C string,
 * returns that string and destroys the CStr object. The returned string
 * MUST be freed by the caller. The function might return NULL if
 * no memory can be allocated.
 *
 * This is the NEW replacement for rsCStrConvSzStrAndDestruct which does
 * no longer utilize a special buffer but soley works on pBuf (and also
 * assumes that cstrFinalize had been called).
 *
 * Parameters are as follows:
 * pointer to the object, pointer to string-pointer to receive string and
 * bRetNULL: 0 - must not return NULL on empty string, return "" in that
 * case, 1 - return NULL instead of an empty string.
 * PLEASE NOTE: the caller must free the memory returned in ppSz in any case
 * (except, of course, if it is NULL).
 */
rsRetVal cstrConvSzStrAndDestruct(cstr_t **ppThis, uchar **ppSz, int bRetNULL) {
    DEFiRet;
    uchar *pRetBuf;
    cstr_t *pThis;

    assert(ppThis != NULL);
    pThis = *ppThis;
    assert(pThis->isFinalized);
    rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
    assert(ppSz != NULL);
    assert(bRetNULL == 0 || bRetNULL == 1);

    if (pThis->pBuf == NULL) {
        if (bRetNULL == 0) {
            CHKmalloc(pRetBuf = malloc(1));
            *pRetBuf = '\0';
        } else {
            pRetBuf = NULL;
        }
    } else {
        pThis->pBuf[pThis->iStrLen] = '\0'; /* space for this is reserved */
        pRetBuf = pThis->pBuf;
    }

    *ppSz = pRetBuf;

finalize_it:
    /* We got it, now free the object ourselfs. Please note
     * that we can NOT use the rsCStrDestruct function as it would
     * also free the sz String buffer, which we pass on to the user.
     */
    RSFREEOBJ(pThis);
    *ppThis = NULL;

    RETiRet;
}


/* return the length of the current string
 * 2005-09-09 rgerhards
 * Please note: this is only a function in a debug build.
 * For release builds, it is a macro defined in stringbuf.h.
 * This is due to performance reasons.
 */
#ifndef NDEBUG
size_t cstrLen(cstr_t *pThis) {
    rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
    return (pThis->iStrLen);
}
#endif

/* Truncate characters from the end of the string.
 * rgerhards 2005-09-15
 */
rsRetVal rsCStrTruncate(cstr_t *pThis, size_t nTrunc) {
    rsCHECKVALIDOBJECT(pThis, OIDrsCStr);

    if (pThis->iStrLen < nTrunc) return RS_TRUNCAT_TOO_LARGE;

    pThis->iStrLen -= nTrunc;

    return RS_RET_OK;
}

/* Trim trailing whitespace from a given string
 */
void cstrTrimTrailingWhiteSpace(cstr_t *const __restrict__ pThis) {
    register int i;
    register uchar *pC;
    rsCHECKVALIDOBJECT(pThis, OIDrsCStr);

    if (pThis->iStrLen == 0) goto done; /* empty string -> nothing to trim ;) */
    i = pThis->iStrLen;
    pC = pThis->pBuf + i - 1;
    while (i > 0 && isspace((int)*pC)) {
        --pC;
        --i;
    }
    /* i now is the new string length! */
    if (i != (int)pThis->iStrLen) {
        pThis->iStrLen = i;
        pThis->pBuf[pThis->iStrLen] = '\0'; /* we always have this space */  // TODO: can we remove this?
    }

done:
    return;
}

/* compare two string objects - works like strcmp(), but operates
 * on CStr objects. Please note that this version here is
 * faster in the majority of cases, simply because it can
 * rely on StrLen.
 * rgerhards 2005-09-19
 * fixed bug, in which only the last byte was actually compared
 * in equal-size strings.
 * rgerhards, 2005-09-26
 */
int rsCStrCStrCmp(cstr_t *const __restrict__ pCS1, cstr_t *const __restrict__ pCS2) {
    rsCHECKVALIDOBJECT(pCS1, OIDrsCStr);
    rsCHECKVALIDOBJECT(pCS2, OIDrsCStr);
    if (pCS1->iStrLen == pCS2->iStrLen)
        if (pCS1->iStrLen == 0)
            return 0; /* zero-sized string are equal ;) */
        else
            return memcmp(pCS1->pBuf, pCS2->pBuf, pCS1->iStrLen);
    else
        return pCS1->iStrLen - pCS2->iStrLen;
}


/* check if a sz-type string starts with a CStr object. This function
 * is initially written to support the "startswith" property-filter
 * comparison operation. Maybe it also has other needs.
 * This functions is modelled after the strcmp() series, thus a
 * return value of 0 indicates that the string starts with the
 * sequence while -1 indicates it does not!
 * rgerhards 2005-10-19
 */
int rsCStrSzStrStartsWithCStr(cstr_t *const __restrict__ pCS1, uchar *const __restrict__ psz, const size_t iLenSz) {
    rsCHECKVALIDOBJECT(pCS1, OIDrsCStr);
    assert(psz != NULL);
    assert(iLenSz == strlen((char *)psz)); /* just make sure during debugging! */
    if (iLenSz >= pCS1->iStrLen) {
        if (pCS1->iStrLen == 0)
            return 0; /* yes, it starts with a zero-sized string ;) */
        else
            return memcmp(psz, pCS1->pBuf, pCS1->iStrLen);
    } else {
        return -1; /* pCS1 is less then psz */
    }
}

/* check if a sz-type string ends with a CStr object. This helper mirrors
 * rsCStrSzStrStartsWithCStr and is primarily intended for the new
 * "endswith" property-filter operation.
 * returns 0 if the string ends with the pattern, -1 otherwise.
 */
int rsCStrSzStrEndsWithCStr(cstr_t *const __restrict__ pCS1, uchar *const __restrict__ psz, const size_t iLenSz) {
    rsCHECKVALIDOBJECT(pCS1, OIDrsCStr);
    assert(psz != NULL);
    assert(iLenSz == strlen((char *)psz));
    if (iLenSz >= pCS1->iStrLen) {
        if (pCS1->iStrLen == 0) {
            return 0; /* yes, it ends with a zero-sized string ;) */
        } else {
            return memcmp(psz + iLenSz - pCS1->iStrLen, pCS1->pBuf, pCS1->iStrLen);
        }
    } else {
        return -1;
    }
}


/* check if a CStr object matches a regex.
 * msamia@redhat.com 2007-07-12
 * @return returns 0 if matched
 * bug: doesn't work for CStr containing \0
 * rgerhards, 2007-07-16: bug is no real bug, because rsyslogd ensures there
 * never is a \0 *inside* a property string.
 * Note that the function returns -1 if regexp functionality is not available.
 * rgerhards: 2009-03-04: ERE support added, via parameter iType: 0 - BRE, 1 - ERE
 * Arnaud Cornet/rgerhards: 2009-04-02: performance improvement by caching compiled regex
 * If a caller does not need the cached version, it must still provide memory for it
 * and must call rsCStrRegexDestruct() afterwards.
 */
rsRetVal rsCStrSzStrMatchRegex(cstr_t *pCS1, uchar *psz, int iType, void *rc) {
    regex_t **cache = (regex_t **)rc;
    int ret;
    DEFiRet;

    assert(pCS1 != NULL);
    assert(psz != NULL);
    assert(cache != NULL);

    if (objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) {
        if (*cache == NULL) {
            *cache = calloc(1, sizeof(regex_t));
            int errcode;
            if ((errcode = regexp.regcomp(*cache, (char *)rsCStrGetSzStrNoNULL(pCS1),
                                          (iType == 1 ? REG_EXTENDED : 0) | REG_NOSUB))) {
                char errbuff[512];
                regexp.regerror(errcode, *cache, errbuff, sizeof(errbuff));
                LogError(0, NO_ERRCODE, "Error in regular expression: %s\n", errbuff);
                ABORT_FINALIZE(RS_RET_NOT_FOUND);
            }
        }
        ret = regexp.regexec(*cache, (char *)psz, 0, NULL, 0);
        if (ret != 0) ABORT_FINALIZE(RS_RET_NOT_FOUND);
    } else {
        ABORT_FINALIZE(RS_RET_NOT_FOUND);
    }

finalize_it:
    RETiRet;
}


/* free a cached compiled regex
 * Caller must provide a pointer to a buffer that was created by
 * rsCStrSzStrMatchRegexCache()
 */
void rsCStrRegexDestruct(void *rc) {
    regex_t **cache = rc;

    assert(cache != NULL);
    assert(*cache != NULL);

    if (objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) {
        regexp.regfree(*cache);
        free(*cache);
        *cache = NULL;
    }
}


/* compare a rsCStr object with a classical sz string.  This function
 * is almost identical to rsCStrZsStrCmp(), but it also takes an offset
 * to the CStr object from where the comparison is to start.
 * I have thought quite a while if it really makes sense to more or
 * less duplicate the code. After all, if you call it with an offset of
 * zero, the functionality is exactly the same. So it looks natural to
 * just have a single function. However, supporting the offset requires
 * some (few) additional integer operations. While they are few, they
 * happen at places in the code that is run very frequently. All in all,
 * I have opted for performance and thus duplicated the code. I hope
 * this is a good, or at least acceptable, compromise.
 * rgerhards, 2005-09-26
 * This function also has an offset-pointer which allows one to
 * specify *where* the compare operation should begin in
 * the CStr. If everything is to be compared, it must be set
 * to 0. If some leading bytes are to be skipped, it must be set
 * to the first index that is to be compared. It must not be
 * set higher than the string length (this is considered a
 * program bug and will lead to unpredictable results and program aborts).
 * rgerhards 2005-09-26
 */
int rsCStrOffsetSzStrCmp(cstr_t *pCS1, size_t iOffset, uchar *psz, size_t iLenSz) {
    rsCHECKVALIDOBJECT(pCS1, OIDrsCStr);
    assert(iOffset < pCS1->iStrLen);
    assert(iLenSz == strlen((char *)psz)); /* just make sure during debugging! */
    if ((pCS1->iStrLen - iOffset) == iLenSz) {
        /* we are using iLenSz below, because the lengths
         * are equal and iLenSz is faster to access
         */
        if (iLenSz == 0) {
            return 0; /* zero-sized strings are equal ;) */
        } else { /* we now have two non-empty strings of equal
                  * length, so we need to actually check if they
                  * are equal.
                  */
            return memcmp(pCS1->pBuf + iOffset, psz, iLenSz);
        }
    } else {
        return pCS1->iStrLen - iOffset - iLenSz;
    }
}


/* compare a rsCStr object with a classical sz string.
 * Just like rsCStrCStrCmp, just for a different data type.
 * There must not only the sz string but also its length be
 * provided. If the caller does not know the length he can
 * call with
 * rsCstrSzStrCmp(pCS, psz, strlen((char*)psz));
 * we are not doing the strlen((char*)) ourselfs as the caller might
 * already know the length and in such cases we can save the
 * overhead of doing it one more time (strelen() is costly!).
 * The bottom line is that the provided length MUST be correct!
 * The to sz string pointer must not be NULL!
 * rgerhards 2005-09-26
 */
int rsCStrSzStrCmp(cstr_t *pCS1, uchar *psz, size_t iLenSz) {
    rsCHECKVALIDOBJECT(pCS1, OIDrsCStr);
    assert(psz != NULL);
    assert(iLenSz == strlen((char *)psz)); /* just make sure during debugging! */
    if (pCS1->iStrLen == iLenSz)
        if (iLenSz == 0)
            return 0; /* zero-sized strings are equal ;) */
        else
            return strncmp((char *)pCS1->pBuf, (char *)psz, iLenSz);
    else
        return (ssize_t)pCS1->iStrLen - (ssize_t)iLenSz;
}


/* Locate the first occurrence of this rsCStr object inside a standard sz string.
 * Returns the offset (0-bound) of this first occurrence. If not found, -1 is
 * returned. Both parameters MUST be given (NULL is not allowed).
 * rgerhards 2005-09-19
 */
int ATTR_NONNULL(1, 2) rsCStrLocateInSzStr(cstr_t *const pThis, uchar *const sz) {
    size_t i;
    size_t iMax;
    size_t len_sz = ustrlen(sz);
    int bFound;
    rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
    assert(sz != NULL);

    if (pThis->iStrLen == 0) return 0;

    /* compute the largest index where a match could occur - after all,
     * the to-be-located string must be able to be present in the
     * searched string (it needs its size ;)).
     */
    iMax = (pThis->iStrLen >= len_sz) ? 0 : len_sz - pThis->iStrLen;

    bFound = 0;
    i = 0;
    while (i <= iMax && !bFound) {
        size_t iCheck;
        uchar *pComp = sz + i;
        for (iCheck = 0; iCheck < pThis->iStrLen; ++iCheck)
            if (*(pComp + iCheck) != *(pThis->pBuf + iCheck)) break;
        if (iCheck == pThis->iStrLen)
            bFound = 1; /* found! - else it wouldn't be equal */
        else
            ++i; /* on to the next try */
    }

    return (bFound ? (int)i : -1);
}


/* our exit function. TODO: remove once converted to a class
 * rgerhards, 2008-03-11
 */
rsRetVal strExit(void) {
    DEFiRet;
    objRelease(regexp, LM_REGEXP_FILENAME);
    RETiRet;
}


/* our init function. TODO: remove once converted to a class
 */
rsRetVal strInit(void) {
    DEFiRet;
    CHKiRet(objGetObjInterface(&obj));

finalize_it:
    RETiRet;
}
