/*
 * Copyright (C) 2013 by egnite GmbH
 * Copyright (c) 1989, 1993 The Regents of the University of California
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. Neither the name of the copyright holders nor the names of
 *    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.
 *
 * For additional information see http://www.ethernut.de/
 */

#include <sys/types.h>
#include <limits.h>
#include <ctype.h>
#include <string.h>
#include <vis.h>

#define isoctal(c)  (((u_char)(c)) >= '0' && ((u_char)(c)) <= '7')
#define isvisible(c)    \
    (((u_int)(c) <= UCHAR_MAX && isascii((u_char)(c)) &&        \
    (((c) != '*' && (c) != '?' && (c) != '[' && (c) != '#') ||  \
        (flag & VIS_GLOB) == 0) && isgraph((u_char)(c))) ||     \
    ((flag & VIS_SP) == 0 && (c) == ' ') ||                     \
    ((flag & VIS_TAB) == 0 && (c) == '\t') ||                   \
    ((flag & VIS_NL) == 0 && (c) == '\n') ||                    \
    ((flag & VIS_SAFE) && ((c) == '\b' ||                       \
        (c) == '\007' || (c) == '\r' ||                         \
        isgraph((u_char)(c)))))

/*!
 * \brief Visually encode a single character.
 *
 * This function copies into dst a string which represents the character c.
 * If c needs no encoding, it is copied in unaltered. The string is null
 * terminated and the maximum length of any encoding is four characters.
 *
 * \param dst   Pointer to a buffer that receives the encoded string.
 *              At least 5 bytes of buffer space should be available.
 * \param c     The character to be encoded.
 * \param flag  Used for altering the default range of characters considered
 *              for encoding and for altering the visual representation.
 *              The following range flags are supported:
 *
 *              - VIS_GLOB  Also encodes '*', '?', '[' and '#'.
 *              - VIS_SP    Also encodes space.
 *              - VIS_TAB   Also encodes tab.
 *              - VIS_NL    Also encodes newline.
 *              - VIS_WHITE Same as VIS_SP | VIS_TAB | VIS_NL.
 *              - VIS_SAFE  Only encodes "unsafe" characters, which may
 *                          cause unexpected functions in terminal emulators.
 *
 *              The following flags specify the visual format:
 *
 *              - VIS_CSTYLE    Use C-style backslash sequences.
 *              - VIS_HTTPSTYLE Use URI encoding as described in RFC1808.
 *              - VIS_OCTAL     Use 3 digit octal sequences.
 *
 *              In addition, the flag VIS_NOSLASH can be used to inhibit
 *              the doubling of backslashes.
 * \param nextc Only used when selecting the VIS_CSTYLE encoding format.
 *
 * \return Pointer to the end of the destination string.
 */
char *vis(char *dst, int c, int flag, int nextc)
{
    static char *hex = "0123456789abcdef";

    if (flag & VIS_HTTPSTYLE) {
        /* Described in RFC 1808 */
        if (!(isalnum(c) /* alpha-numeric */
            /* safe */
            || c == '$' || c == '-' || c == '_' || c == '.' || c == '+'
            /* extra */
            || c == '!' || c == '*' || c == '\'' || c == '('
            || c == ')' || c == ',')) {
            *dst++ = '%';
            *dst++ = hex[((unsigned int) c >> 4) & 0xf];
            *dst++ = hex[(unsigned int) c & 0xf];
            goto done;
        }
    }
    if (isvisible(c)) {
        *dst++ = c;
        if (c == '\\' && (flag & VIS_NOSLASH) == 0)
            *dst++ = '\\';
        *dst = '\0';
        return dst;
    }

    if (flag & VIS_CSTYLE) {
        switch (c) {
        case '\n':
            *dst++ = '\\';
            *dst++ = 'n';
            goto done;
        case '\r':
            *dst++ = '\\';
            *dst++ = 'r';
            goto done;
        case '\b':
            *dst++ = '\\';
            *dst++ = 'b';
            goto done;
        case '\a':
            *dst++ = '\\';
            *dst++ = 'a';
            goto done;
        case '\v':
            *dst++ = '\\';
            *dst++ = 'v';
            goto done;
        case '\t':
            *dst++ = '\\';
            *dst++ = 't';
            goto done;
        case '\f':
            *dst++ = '\\';
            *dst++ = 'f';
            goto done;
        case ' ':
            *dst++ = '\\';
            *dst++ = 's';
            goto done;
        case '\0':
            *dst++ = '\\';
            *dst++ = '0';
            if (isoctal(nextc)) {
                *dst++ = '0';
                *dst++ = '0';
            }
            goto done;
        }
    }
    if (((c & 0177) == ' ') || (flag & VIS_OCTAL) ||
        ((flag & VIS_GLOB)
         && (c == '*' || c == '?' || c == '[' || c == '#'))) {
        *dst++ = '\\';
        *dst++ = ((u_char) c >> 6 & 07) + '0';
        *dst++ = ((u_char) c >> 3 & 07) + '0';
        *dst++ = ((u_char) c & 07) + '0';
        goto done;
    }
    if ((flag & VIS_NOSLASH) == 0)
        *dst++ = '\\';
    if (c & 0200) {
        c &= 0177;
        *dst++ = 'M';
    }
    if (iscntrl((u_char) c)) {
        *dst++ = '^';
        if (c == 0177)
            *dst++ = '?';
        else
            *dst++ = c + '@';
    } else {
        *dst++ = '-';
        *dst++ = c;
    }
  done:
    *dst = '\0';

    return dst;
}

/*!
 * \brief Visually encode a string.
 *
 * \param dst  Pointer to a buffer that receives the encoded string.
 *             The size of this buffer must be at least four times the
 *             number of characters encoded, plus one for the string
 *             terminator.
 * \param src  The string to be encoded.
 * \param flag Used for altering the range of characters and the visual
 *             representation, see vis().
 *
 * \return The length of the resulting string.
 */
int strvis(char *dst, const char *src, int flag)
{
    char c;
    char *start;

    for (start = dst; (c = *src);)
        dst = vis(dst, c, flag, *++src);
    *dst = '\0';
    return dst - start;
}

/*!
 * \brief Visually encode a string up to a maximum number of characters.
 *
 * \param dst  Pointer to a buffer that receives the encoded string.
 * \param src  Pointer to the buffer to be encoded.
 * \param siz  The size of the destination buffer.
 * \param flag Used for altering the range of characters and the visual
 *             representation, see vis().
 *
 * \return The length of the resulting string.
 */
int strnvis(char *dst, const char *src, size_t siz, int flag)
{
    char *start, *end;
    char tbuf[5];
    int c, i;

    i = 0;
    for (start = dst, end = start + siz - 1; (c = *src) && dst < end;) {
        if (isvisible(c)) {
            i = 1;
            *dst++ = c;
            if (c == '\\' && (flag & VIS_NOSLASH) == 0) {
                /* need space for the extra '\\' */
                if (dst < end)
                    *dst++ = '\\';
                else {
                    dst--;
                    i = 2;
                    break;
                }
            }
            src++;
        } else {
            i = vis(tbuf, c, flag, *++src) - tbuf;
            if (dst + i <= end) {
                memcpy(dst, tbuf, i);
                dst += i;
            } else {
                src--;
                break;
            }
        }
    }
    if (siz > 0)
        *dst = '\0';
    if (dst + i > end) {
        /* adjust return value for truncation */
        while ((c = *src))
            dst += vis(tbuf, c, flag, *++src) - tbuf;
    }
    return dst - start;
}

/*!
 * \brief Visually encode a buffer.
 *
 * This function is useful for encoding a block of data that may contain
 * NULs.
 *
 * \param dst  Pointer to a buffer that receives the encoded string.
 *             The size of this buffer must be at least four times the
 *             number of characters encoded, plus one for the string
 *             terminator.
 * \param src  The buffer to encode.
 * \param len  The number of characters to encode.
 * \param flag Used for altering the range of characters and the visual
 *             representation, see vis().
 *
 * \return The length of the resulting string.
 */
int strvisx(char *dst, const char *src, size_t len, int flag)
{
    char c;
    char *start;

    for (start = dst; len > 1; len--) {
        c = *src;
        dst = vis(dst, c, flag, *++src);
    }
    if (len)
        dst = vis(dst, *src, flag, '\0');
    *dst = '\0';
    return dst - start;
}
