/*
** (c) Copyright 2003-2005 Matt Messier and John Viega
** All rights reserved.
** 
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** 
** Redistributions of source code must retain the above copyright notice,
** this list of conditions and the following disclaimer.
** 
** Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 
** Neither the name of the primary nor the names of the contributors may
** be used to endorse or promote products derived from this software
** without specific prior written permission.
** 
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "isafestr.h"
#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
#ifdef HAVE_PATHS_H
#include <paths.h>
#endif

#ifdef WIN32
#include <io.h>
#define _PATH_TTY "CONIN$"
#else
#ifdef HAVE_PTHREAD
#include <pthread.h>
#endif
#endif

#ifndef _PATH_TTY
#define _PATH_TTY   "/dev/tty"
#endif

#define SAFESTR_READLINE_SIZE   (128 - (sizeof(small_isafestr_t) + 1))

typedef union safestr_integer_t
{
    int32_t     s32;
    int64_t     s64;
    u_int32_t   u32;
    u_int64_t   u64;
} safestr_integer_t;

static char ascii_values[64] =
{
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
     0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1,
    -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
    25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,
};

static int alpha_bytes[256] =
{
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /*   0 -  15 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /*  16 -  31 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /*  32 -  47 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /*  48 -  63 */
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,     /*  64 -  79 */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,     /*  80 -  95 */
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,     /*  96 - 111 */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,     /* 112 - 127 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /* 128 - 143 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /* 144 - 159 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /* 160 - 175 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /* 176 - 191 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /* 192 - 207 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /* 208 - 223 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /* 224 - 239 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /* 240 - 255 */
};

static int space_bytes[256] =
{
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,     /*   0 -  15 */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,     /*  16 -  31 */
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /*  32 -  47 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /*  48 -  63 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /*  64 -  79 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /*  80 -  95 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /*  96 - 111 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,     /* 112 - 127 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /* 128 - 143 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /* 144 - 159 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /* 160 - 175 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /* 176 - 191 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /* 192 - 207 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /* 208 - 223 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /* 224 - 239 */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     /* 240 - 255 */
};

static void cleanup_safestr(safestr_t, void *);
static void parse_integer(isafestr_t, safestr_integer_t *, int, int, int);
#ifndef WIN32
static void restore_signals(sigset_t *, void *);
static void restore_terminal(struct termios *, FILE *);
#endif

static void
cleanup_safestr(safestr_t str, void *arg)
{
    safestr_free(str);
}

static void
parse_integer(isafestr_t istr, safestr_integer_t *result, int base, int size, int sign)
{
    int             negative = 0, value;
    char            *cptr;
    unsigned char   c;

    if (size != 32 && size != 64)
        XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_PARAMETER, NULL);
    if (base && (base < 2 || base > 36))
        XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_PARAMETER, NULL);

    if (size == 32 && sign)      result->s32 = 0;
    else if (size == 32)         result->u32 = 0;
    else if (size == 64 && sign) result->s64 = 0;
    else if (size == 64)         result->u64 = 0;

    cptr = istr->str;
    while (space_bytes[(int)(unsigned char)*cptr])
        cptr++;
    if (*cptr == '+')
        cptr++;
    else if (*cptr == '-' && sign)
    {
        cptr++;
        negative = 1;
    }

    if (*cptr == '0' && (*(cptr + 1) == 'b' || *(cptr + 1) == 'B'))
    {
        if (base && base != 2)
            XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT, NULL);
        base = 2;
        cptr += 2;
    }
    else if (*cptr == '0' && *(cptr + 1) >= '0' && *(cptr + 1) <= '7')
    {
        if (base && base != 8)
            XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT, NULL);
        base = 8;
        cptr++;
    }
    else if (*cptr == '0' && (*(cptr + 1) == 'x' || *(cptr + 1) == 'X'))
    {
        if (base && base != 16)
            XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT, NULL);
        base = 16;
        cptr += 2;
    }
    else
    {
        base = 10;
    }

    for (;  *cptr;  cptr++)
    {
        c = safestr_casemap_upper[(int)*cptr & 0xff];
        if (c < 32 || c > sizeof(ascii_values) + 32)
            XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT, NULL);
        if ((value = ascii_values[c - 32]) == -1)
        {
            if (space_bytes[(int)c])
                break;
            XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT, NULL);
        }
        if (value >= base)
            XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT, NULL);

        if      (size == 32 && sign) result->s32 = (result->s32 * base) + value;
        else if (size == 32)         result->u32 = (result->u32 * base) + value;
        else if (size == 64 && sign) result->s64 = (result->s64 * base) + value;
        else if (size == 64)         result->u64 = (result->u64 * base) + value;
    }

    if (sign && negative)
    {
        if (size == 32)      result->s32 *= -1;
        else if (size == 64) result->s64 *= -1;
    }
}

#ifndef WIN32
static void
restore_signals(sigset_t *saved_signals, void *arg)
{
#ifdef HAVE_PTHREAD
    pthread_sigmask(SIG_SETMASK, saved_signals, NULL);
#endif
}

static void
restore_terminal(struct termios *saved_term, FILE *term)
{
    tcsetattr(fileno(term), TCSAFLUSH, saved_term);
}
#endif

void
safestr_convert(safestr_t str, u_int32_t flags)
{
    char        *cptr;
    u_int32_t   i = 0;
    isafestr_t  istr;

    XXL_ASSET_BLOCK_BEGIN
    {
        istr = safestr_get(str, SAFESTR_GET_WRITABLE);
        if (flags & SAFESTR_CONVERT_UPPERCASE)
        {
            for (cptr = istr->str;  i < istr->hdr.length;  i++, cptr++)
                *cptr = (char)safestr_casemap_upper[(int)(unsigned char)*cptr];
        }
        else if (flags & SAFESTR_CONVERT_LOWERCASE)
        {
            for (cptr = istr->str;  i < istr->hdr.length;  i++, cptr++)
                *cptr = (char)safestr_casemap_lower[(int)(unsigned char)*cptr];
        }
        else if (flags & SAFESTR_CONVERT_TITLECASE)
        {
            for (cptr = istr->str;  i < istr->hdr.length;  i++, cptr++)
            {
                if (i + 2 < istr->hdr.length &&
                    safestr_casemap_lower[(int)(unsigned char)*cptr] == 'm' &&
                    safestr_casemap_lower[(int)(unsigned char)*(cptr + 1)] == 'c')
                {
                    *cptr = (char)safestr_casemap_upper[(int)(unsigned char)*cptr];
                    i++;  cptr++;
                    *cptr = (char)safestr_casemap_lower[(int)(unsigned char)*cptr];
                    i++;  cptr++;
                    *cptr = (char)safestr_casemap_upper[(int)(unsigned char)*cptr];
                }
                else if (cptr == istr->str)
                {
                    *cptr = (char)safestr_casemap_upper[(int)(unsigned char)*cptr];
                }
                else if (i + 1 < istr->hdr.length && !alpha_bytes[(int)(unsigned char)*cptr])
                {
                    i++, cptr++;
                    *cptr = (char)safestr_casemap_upper[(int)(unsigned char)*cptr];
                }
                else
                {
                    /* Last resort ... lowercase it */
                    *cptr = (char)safestr_casemap_lower[(int)(unsigned char)*cptr];
                }
            }
        }
    }
    XXL_ASSET_BLOCK_END;
}

safestr_t
safestr_do_getpassword(FILE *term, safestr_t prompt, const char *file, unsigned int lineno)
{
    int             done = 0, termfd;
    char            ch;
#ifndef WIN32
    sigset_t        saved_signals, set_signals;
#endif
    safestr_t       str;
    u_int32_t       actual_length;
    isafestr_t      iorig, iprompt, istr;
#ifndef WIN32
    struct termios  saved_term, set_term;
#endif

    XXL_ASSET_BLOCK_BEGIN
    {
        if (!term && !(term = xxl_fopen(_PATH_TTY, "r+", XXL_ASSET_TEMPORARY)))
            XXL_THROW_ERROR(errno, 0);

        termfd  = fileno(term);
        iprompt = safestr_get(prompt, SAFESTR_GET_READONLY);
        fprintf(term, "%s", iprompt->str);
        fflush(term);

#ifndef WIN32
        /* Defer interrupt when echo is turned off */
        sigemptyset(&set_signals);
        sigaddset(&set_signals, SIGINT);
        sigaddset(&set_signals, SIGTSTP);
#ifdef HAVE_PTHREAD
        pthread_sigmask(SIG_BLOCK, &set_signals, &saved_signals);
#endif
        XXL_ASSET_SAVE(&saved_signals, restore_signals, NULL, XXL_ASSET_TEMPORARY);

        /* Set the terminal to not echo */
        tcgetattr(termfd, &saved_term);
        set_term = saved_term;
        set_term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
        tcsetattr(termfd, TCSAFLUSH, &set_term);
        XXL_ASSET_SAVE(&saved_term, restore_terminal, term, XXL_ASSET_TEMPORARY);
#endif

        /* Allocate a string to hold the user input */
        str = safestr_do_alloc(SAFESTR_READLINE_SIZE, 0, file, lineno);
        XXL_ASSET_SAVE(str, cleanup_safestr, NULL, XXL_ASSET_PROMOTE);
        istr = iorig = safestr_get(str, SAFESTR_GET_WRITABLE);

        /* Read the password from the terminal user */
        while (!done)
        {
            switch (read(termfd, &ch, 1))
            {
                case 1:
                    if (ch != '\n')
                        break;
                    /* FALL THROUGH */
                case 0:
                    istr->str[istr->hdr.length] = '\0';
                    done = 1;
                    break;
                case -1:
                    XXL_THROW_ERROR(errno, NULL);
                    break;
            }
            istr->str[istr->hdr.length++] = ch;
            if (istr->hdr.length == istr->hdr.size)
            {
                actual_length = istr->hdr.length;
                istr = safestr_resize(istr, actual_length + SAFESTR_READLINE_SIZE);
                istr->hdr.length = actual_length;
            }
        }

        if (istr->str[istr->hdr.length - 1] == '\n')
            istr->str[--istr->hdr.length] = '\0';

        putc('\n', term);
        str = safestr_complete(iorig, istr);
    }
    XXL_ASSET_BLOCK_END;

    return str;
}

safestr_t
safestr_do_readline(FILE *f, const char *file, unsigned int lineno)
{
    int         eof_reached = 0;
    safestr_t   str;
    u_int32_t   actual_length;
    isafestr_t  iorig, istr;
    
    XXL_ASSET_BLOCK_BEGIN
    {
        str = safestr_do_alloc(SAFESTR_READLINE_SIZE, 0, file, lineno);
        XXL_ASSET_SAVE(str, cleanup_safestr, NULL, XXL_ASSET_PROMOTE);
        istr = iorig = safestr_get(str, SAFESTR_GET_WRITABLE);

        while (!eof_reached)
        {
            if (!fgets(istr->str + istr->hdr.length,
                       istr->hdr.size - istr->hdr.length + 1, f))
            {
                if (!feof(f))
                    XXL_THROW_ERROR(errno, NULL);
                eof_reached = 1;
                break;
            }

            /* we have to play some games with string length here, because
             * safestr_resize() sets the length to the requested length, but
             * we don't actually want that.
             */
            actual_length = istr->hdr.length + strlen(istr->str + istr->hdr.length);
            if (istr->str[actual_length - 1] == '\n')
            {
                istr->str[--actual_length] = '\0';
                if (istr->str[actual_length - 1] == '\r')
                    istr->str[--actual_length] = '\0';
                istr->hdr.length = actual_length;
                break;
            }

            istr->hdr.length = actual_length;
            istr = safestr_resize(istr, actual_length + SAFESTR_READLINE_SIZE);
            istr->hdr.length = actual_length;
        }
        str = safestr_complete(iorig, istr);
    }
    XXL_ASSET_BLOCK_END;

    if (eof_reached)
    {
        safestr_free(str);
        return NULL;
    }

    return str;
}

double
safestr_todouble(safestr_t str)
{
    char        *end;
    double      result;
    isafestr_t  istr;
    
    XXL_ASSET_BLOCK_BEGIN
    {
        istr = safestr_get(str, SAFESTR_GET_READONLY);
        result = strtod(istr->str, &end);
        if (end && (*end || end == istr->str))
            XXL_THROW_ERROR(SAFESTR_ERROR_INVALID_FORMAT, end);
    }
    XXL_ASSET_BLOCK_END;

    return result;
}

int32_t
safestr_toint32(safestr_t str, int base)
{
    safestr_integer_t   result;

    XXL_ASSET_BLOCK_BEGIN
    {
        parse_integer(safestr_get(str, SAFESTR_GET_READONLY), &result, base, 32, 1);
    }
    XXL_ASSET_BLOCK_END;

    return result.s32;
}

int64_t
safestr_toint64(safestr_t str, int base)
{
    safestr_integer_t   result;

    XXL_ASSET_BLOCK_BEGIN
    {
        parse_integer(safestr_get(str, SAFESTR_GET_READONLY), &result, base, 64, 1);
    }
    XXL_ASSET_BLOCK_END;

    return result.s64;
}

u_int32_t
safestr_touint32(safestr_t str, int base)
{
    safestr_integer_t   result;

    XXL_ASSET_BLOCK_BEGIN
    {
        parse_integer(safestr_get(str, SAFESTR_GET_READONLY), &result, base, 32, 0);
    }
    XXL_ASSET_BLOCK_END;

    return result.u32;
}

u_int64_t
safestr_touint64(safestr_t str, int base)
{
    safestr_integer_t   result;

    XXL_ASSET_BLOCK_BEGIN
    {
        parse_integer(safestr_get(str, SAFESTR_GET_READONLY), &result, base, 64, 0);
    }
    XXL_ASSET_BLOCK_END;

    return result.u64;
}

void
safestr_trim(safestr_t str, u_int32_t flags)
{
    char        *cptr;
    u_int32_t   length;
    isafestr_t  istr;

    XXL_ASSET_BLOCK_BEGIN
    {
        istr   = safestr_get(str, SAFESTR_GET_WRITABLE);
        length = istr->hdr.length;
        if (!(flags & (SAFESTR_TRIM_BOTH)))
            flags = SAFESTR_TRIM_BOTH;

        if (flags & SAFESTR_TRIM_LEADING)
        {
            for (cptr = istr->str;
                 length > 0 && space_bytes[(int)(unsigned char)*cptr];
                 cptr++)
            {
                length--;
            }
            memmove(istr->str, cptr, length + 1);
        }
        if (flags & SAFESTR_TRIM_TRAILING)
        {
            while (length > 0 && space_bytes[(int)(unsigned char)istr->str[length - 1]])
                length--;
            istr->str[length] = '\0';
        }

        istr->hdr.length = length;
    }
    XXL_ASSET_BLOCK_END;
}



syntax highlighted by Code2HTML, v. 0.9.1