/* Copyright 2000-2005 The Apache Software Foundation or its licensors, as
 * applicable.
 *
 * 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
 *
 * 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 "testutil.h"

#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#if APR_HAVE_LIMITS_H
#include <limits.h>
#endif

#include "apr_general.h"
#include "apr_strings.h"
#include "apr_errno.h"

/* I haven't bothered to check for APR_ENOTIMPL here, AFAIK, all string
 * functions exist on all platforms.
 */

static void test_strtok(abts_case *tc, void *data)
{
    struct {
        char *input;
        char *sep;
    }
    cases[] = {
        {
            "",
            "Z"
        },
        {
            "      asdf jkl; 77889909            \r\n\1\2\3Z",
            " \r\n\3\2\1"
        },
        {
            NULL,  /* but who cares if apr_strtok() segfaults? */
            " \t"
        },
#if 0     /* don't do this... you deserve to segfault */
        {
            "a b c              ",
            NULL
        },
#endif
        {
            "   a       b        c   ",
            ""
        },
        {
            "a              b c         ",
            " "
        }
    };
    int curtc;

    for (curtc = 0; curtc < sizeof cases / sizeof cases[0]; curtc++) {
        char *retval1, *retval2;
        char *str1, *str2;
        char *state;

        str1 = apr_pstrdup(p, cases[curtc].input);
        str2 = apr_pstrdup(p, cases[curtc].input);

        do {
            retval1 = apr_strtok(str1, cases[curtc].sep, &state);
            retval2 = strtok(str2, cases[curtc].sep);

            if (!retval1) {
                ABTS_TRUE(tc, retval2 == NULL);
            }
            else {
                ABTS_TRUE(tc, retval2 != NULL);
                ABTS_STR_EQUAL(tc, retval2, retval1);
            }

            str1 = str2 = NULL; /* make sure we pass NULL on subsequent calls */
        } while (retval1);
    }
}

static void snprintf_noNULL(abts_case *tc, void *data)
{
    char buff[100];
    char *testing = apr_palloc(p, 10);

    testing[0] = 't';
    testing[1] = 'e';
    testing[2] = 's';
    testing[3] = 't';
    testing[4] = 'i';
    testing[5] = 'n';
    testing[6] = 'g';
    
    /* If this test fails, we are going to seg fault. */
    apr_snprintf(buff, sizeof(buff), "%.*s", 7, testing);
    ABTS_STR_NEQUAL(tc, buff, testing, 7);
}

static void snprintf_0NULL(abts_case *tc, void *data)
{
    int rv;

    rv = apr_snprintf(NULL, 0, "%sBAR", "FOO");
    ABTS_INT_EQUAL(tc, 6, rv);
}

static void snprintf_0nonNULL(abts_case *tc, void *data)
{
    int rv;
    char *buff = "testing";

    rv = apr_snprintf(buff, 0, "%sBAR", "FOO");
    ABTS_INT_EQUAL(tc, 6, rv);
    ABTS_ASSERT(tc, "buff unmangled", strcmp(buff, "FOOBAR") != 0);
}

static void snprintf_underflow(abts_case *tc, void *data)
{
    char buf[20];
    int rv;

    rv = apr_snprintf(buf, sizeof buf, "%.2f", (double)0.0001);
    ABTS_INT_EQUAL(tc, 4, rv);
    ABTS_STR_EQUAL(tc, "0.00", buf);
    
    rv = apr_snprintf(buf, sizeof buf, "%.2f", (double)0.001);
    ABTS_INT_EQUAL(tc, 4, rv);
    ABTS_STR_EQUAL(tc, "0.00", buf);
    
    rv = apr_snprintf(buf, sizeof buf, "%.2f", (double)0.01);
    ABTS_INT_EQUAL(tc, 4, rv);
    ABTS_STR_EQUAL(tc, "0.01", buf);
}

static void string_error(abts_case *tc, void *data)
{
     char buf[128], *rv;
     apr_status_t n;

     buf[0] = '\0';
     rv = apr_strerror(APR_ENOENT, buf, sizeof buf);
     ABTS_PTR_EQUAL(tc, buf, rv);
     ABTS_TRUE(tc, strlen(buf) > 0);

     rv = apr_strerror(APR_TIMEUP, buf, sizeof buf);
     ABTS_PTR_EQUAL(tc, buf, rv);
     ABTS_STR_EQUAL(tc, "The timeout specified has expired", buf);
     
     /* throw some randomish numbers at it to check for robustness */
     for (n = 1; n < 1000000; n *= 2) {
         apr_strerror(n, buf, sizeof buf);
     }
}

#define SIZE 180000
static void string_long(abts_case *tc, void *data)
{
    char s[SIZE + 1];

    memset(s, 'A', SIZE);
    s[SIZE] = '\0';

    apr_psprintf(p, "%s", s);
}

/* ### FIXME: apr.h/apr_strings.h should provide these! */
#define MY_LLONG_MAX (APR_INT64_C(9223372036854775807))
#define MY_LLONG_MIN (-MY_LLONG_MAX - APR_INT64_C(1))

static void string_strtoi64(abts_case *tc, void *data)
{
    static const struct {
        int errnum, base;
        const char *in, *end;
        apr_int64_t result;
    } ts[] = {
        
        /* base 10 tests */
        { 0, 10, "123545", NULL, APR_INT64_C(123545) },
        { 0, 10, "   123545", NULL, APR_INT64_C(123545) },
        { 0, 10, "   +123545", NULL, APR_INT64_C(123545) },
        { 0, 10, "-123545", NULL, APR_INT64_C(-123545) },
        { 0, 10, "   00000123545", NULL, APR_INT64_C(123545) },
        { 0, 10, "123545ZZZ", "ZZZ", APR_INT64_C(123545) },
        { 0, 10, "   123545   ", "   ", APR_INT64_C(123545) },

        /* base 16 tests */
        { 0, 16, "1E299", NULL, APR_INT64_C(123545) },
        { 0, 16, "1e299", NULL, APR_INT64_C(123545) },
        { 0, 16, "0x1e299", NULL, APR_INT64_C(123545) },
        { 0, 16, "0X1E299", NULL, APR_INT64_C(123545) },
        { 0, 16, "+1e299", NULL, APR_INT64_C(123545) },
        { 0, 16, "-1e299", NULL, APR_INT64_C(-123545) },
        { 0, 16, "   -1e299", NULL, APR_INT64_C(-123545) },

        /* automatic base detection tests */
        { 0, 0, "123545", NULL, APR_INT64_C(123545) },
        { 0, 0, "0x1e299", NULL, APR_INT64_C(123545) },
        { 0, 0, "  0x1e299", NULL, APR_INT64_C(123545) },
        { 0, 0, "+0x1e299", NULL, APR_INT64_C(123545) },
        { 0, 0, "-0x1e299", NULL, APR_INT64_C(-123545) },

        /* large number tests */
        { 0, 10, "8589934605", NULL, APR_INT64_C(8589934605) },
        { 0, 10, "-8589934605", NULL, APR_INT64_C(-8589934605) },
        { 0, 16, "0x20000000D", NULL, APR_INT64_C(8589934605) },
        { 0, 16, "-0x20000000D", NULL, APR_INT64_C(-8589934605) },
        { 0, 16, "   0x20000000D", NULL, APR_INT64_C(8589934605) },
        { 0, 16, "   0x20000000D", NULL, APR_INT64_C(8589934605) },

        /* error cases */
        { ERANGE, 10, "999999999999999999999999999999999", "", MY_LLONG_MAX },
        { ERANGE, 10, "-999999999999999999999999999999999", "", MY_LLONG_MIN },

#if 0
        /* C99 doesn't require EINVAL for an invalid range. */
        { EINVAL, 99, "", (void *)-1 /* don't care */, 0 },
#endif

        /* some strtoll implementations give EINVAL when no conversion
         * is performed. */
        { -1 /* don't care */, 10, "zzz", "zzz", APR_INT64_C(0) },
        { -1 /* don't care */, 10, "", NULL, APR_INT64_C(0) }

    };
    int n;

    for (n = 0; n < sizeof(ts)/sizeof(ts[0]); n++) {
        char *end = "end ptr not changed";
        apr_int64_t result;
        int errnum;
        
        errno = 0;
        result = apr_strtoi64(ts[n].in, &end, ts[n].base);
        errnum = errno;

        ABTS_ASSERT(tc,
                 apr_psprintf(p, "for '%s': result was %" APR_INT64_T_FMT 
                              " not %" APR_INT64_T_FMT, ts[n].in,
                              result, ts[n].result),
                 result == ts[n].result);
        
        if (ts[n].errnum != -1) {
            ABTS_ASSERT(tc,
                     apr_psprintf(p, "for '%s': errno was %d not %d", ts[n].in,
                                  errnum, ts[n].errnum),
                     ts[n].errnum == errnum);
        }

        if (ts[n].end == NULL) {
            /* end must point to NUL terminator of .in */
            ABTS_PTR_EQUAL(tc, ts[n].in + strlen(ts[n].in), end);
        } else if (ts[n].end != (void *)-1) {
            ABTS_ASSERT(tc,
                     apr_psprintf(p, "for '%s', end was '%s' not '%s'",
                                  ts[n].in, end, ts[n].end),
                     strcmp(ts[n].end, end) == 0);
        }
    }
}

static void string_strtoff(abts_case *tc, void *data)
{
    apr_off_t off;

    ABTS_ASSERT(tc, "strtoff fails on out-of-range integer",
                apr_strtoff(&off, "999999999999999999999999999999",
                            NULL, 10) != APR_SUCCESS);

    ABTS_ASSERT(tc, "strtoff failed for 1234",
                apr_strtoff(&off, "1234", NULL, 10) == APR_SUCCESS);

    ABTS_ASSERT(tc, "strtoff failed to parse 1234", off == 1234);
}

/* random-ish checks for strfsize buffer overflows */
static void overflow_strfsize(abts_case *tc, void *data)
{
    apr_off_t off;
    char buf[7];

    buf[5] = '$';
    buf[6] = '@';

    for (off = -9999; off < 20000; off++) {
        apr_strfsize(off, buf);
    }
    for (; off < 9999999; off += 9) {
        apr_strfsize(off, buf);
    }
    for (; off < 999999999; off += 999) {
        apr_strfsize(off, buf);
    }
    for (off = 1; off < LONG_MAX && off > 0; off *= 2) {
        apr_strfsize(off, buf);
        apr_strfsize(off + 1, buf);
        apr_strfsize(off - 1, buf);
    }

    ABTS_ASSERT(tc, "strfsize overflowed", buf[5] == '$');
    ABTS_ASSERT(tc, "strfsize overflowed", buf[6] == '@');
}

static void string_strfsize(abts_case *tc, void *data)
{
    static const struct {
        apr_off_t size;
        const char *buf;
    } ts[] = {
        { -1,   "  - " },
        { 0,    "  0 " },
        { 666,  "666 " },
        { 1024, "1.0K" },
        { 1536, "1.5K" },
        { 2048, "2.0K" },
        { 1293874, "1.2M" },
        { 9999999, "9.5M" },
        { 103809024, " 99M" },
        { 1047527424, "1.0G" } /* "999M" would be more correct */
    };
    apr_size_t n;

    for (n = 0; n < sizeof(ts)/sizeof(ts[0]); n++) {
        char buf[6], *ret;
        
        buf[5] = '%';

        ret = apr_strfsize(ts[n].size, buf);
        ABTS_ASSERT(tc, "strfsize returned wrong buffer", ret == buf);
        ABTS_ASSERT(tc, "strfsize overflowed", buf[5] == '%');

        ABTS_STR_EQUAL(tc, ts[n].buf, ret);
    }
}

abts_suite *teststr(abts_suite *suite)
{
    suite = ADD_SUITE(suite)

    abts_run_test(suite, snprintf_0NULL, NULL);
    abts_run_test(suite, snprintf_0nonNULL, NULL);
    abts_run_test(suite, snprintf_noNULL, NULL);
    abts_run_test(suite, snprintf_underflow, NULL);
    abts_run_test(suite, test_strtok, NULL);
    abts_run_test(suite, string_error, NULL);
    abts_run_test(suite, string_long, NULL);
    abts_run_test(suite, string_strtoi64, NULL);
    abts_run_test(suite, string_strtoff, NULL);
    abts_run_test(suite, overflow_strfsize, NULL);
    abts_run_test(suite, string_strfsize, NULL);

    return suite;
}

