summaryrefslogblamecommitdiff
path: root/src/common/strlib.c
blob: 89aac3b409658d006a2fcf651684dbe0448d0e3d (plain) (tree)
1
2
3
4
5
6
7
8
9
10


                                                          

                                 
                              

                   

                   
                  
 
 


                                                         
                          
 




























                                         

 
                                          
                                              
 





























                                                                                  
 

                                                                  
                                                      
 






















                                   

 
                                                       
                                   
 

                   
 





                              
 
                  

 

                                                                                
                     
 




















                                                                                    

 


                                                                                    
                                                  
 




























                                                          
 
 
                                                              

                                                           
                                                             
 

















                                                                     


              
                                                       
 














                                   

      
 
                                                                                        

                                                                             
                                                 
 

                                                                 

      
 
                                                           
                                                         
 































                                                              


      


                                                      
                             
 


                               
 


                            
 


                                                                    
 

                            
 
                                      
 

                                                                               
 


                                            
 

                                                                             
 

                  





                                                    
                                  
 



                                                                                                                                              
 
                                     
 
 
                                                 
                                                       
 













                                                
 
 
                                 
                                                     
 
                                                          

 








                                                                   
                                                            
 











                                                

 

                                                          
                                        
 













                                         
 
 






                                                               
                                                              
 









                                                

 


                                                                     




                                                                    
                                       
 



































                                                                                                                           



                                      


                                                                                                        
                                                             

                                       
 































































                                                                                           
     




                                                                                               
      







                                







                      
             







                                                                           
   

                                                                    
                                                                            

                                     
   







                                                                          
                                                                                                          
 

























                                                          

 





                                                                
   

                                                               
                                                                                         
   







                                                                          
                                                                                                            
 























































                                                                                       

 








                                                                      
                                                                                    
 




































































                                                                                          









                                                                        
                                                                 
 

































































































                                                                                          

 
                                                   
                                         
 























                                                            


 










                                                                                                                           
                                                                                                                                                                           
 































































                                                                                                                                             
 

 





                                                                     
                             
 



                               


                                                
                                    
 

                                                              


                                                 
                                                           
 

               
 


                                           
 
               


                                                  
                                                                   
 



















                                                                  


                                                              
                                                            
 












                                                                  


                               
                                                         
 













                                                                         


                                                  
                                     
 
                                          


                                     
                                      
 

                       


                                        
                                     
 
                            


                          
                                       
 


                                


                                                 
                                    
 

                            
 
// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
// For more information, see LICENCE in the main folder

#include "../common/cbasetypes.h"
#include "../common/malloc.h"
#include "../common/showmsg.h"
#include "strlib.h"

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>


#define J_MAX_MALLOC_SIZE 65535

// escapes a string in-place (' -> \' , \ -> \\ , % -> _)
char *jstrescape(char *pt)
{
    //copy from here
    char *ptr;
    int i = 0, j = 0;

    //copy string to temporary
    CREATE(ptr, char, J_MAX_MALLOC_SIZE);
    strcpy(ptr,pt);

    while (ptr[i] != '\0') {
        switch (ptr[i]) {
            case '\'':
                pt[j++] = '\\';
                pt[j++] = ptr[i++];
                break;
            case '\\':
                pt[j++] = '\\';
                pt[j++] = ptr[i++];
                break;
            case '%':
                pt[j++] = '_';
                i++;
                break;
            default:
                pt[j++] = ptr[i++];
        }
    }
    pt[j++] = '\0';
    aFree(ptr);
    return pt;
}

// escapes a string into a provided buffer
char *jstrescapecpy(char *pt, const char *spt)
{
    //copy from here
    //WARNING: Target string pt should be able to hold strlen(spt)*2, as each time
    //a escape character is found, the target's final length increases! [Skotlex]
    int i =0, j=0;

    if (!spt) { //Return an empty string [Skotlex]
        pt[0] = '\0';
        return &pt[0];
    }

    while (spt[i] != '\0') {
        switch (spt[i]) {
            case '\'':
                pt[j++] = '\\';
                pt[j++] = spt[i++];
                break;
            case '\\':
                pt[j++] = '\\';
                pt[j++] = spt[i++];
                break;
            case '%':
                pt[j++] = '_';
                i++;
                break;
            default:
                pt[j++] = spt[i++];
        }
    }
    pt[j++] = '\0';
    return &pt[0];
}

// escapes exactly 'size' bytes of a string into a provided buffer
int jmemescapecpy(char *pt, const char *spt, int size)
{
    //copy from here
    int i =0, j=0;

    while (i < size) {
        switch (spt[i]) {
            case '\'':
                pt[j++] = '\\';
                pt[j++] = spt[i++];
                break;
            case '\\':
                pt[j++] = '\\';
                pt[j++] = spt[i++];
                break;
            case '%':
                pt[j++] = '_';
                i++;
                break;
            default:
                pt[j++] = spt[i++];
        }
    }
    // copy size is 0 ~ (j-1)
    return j;
}

// Function to suppress control characters in a string.
int remove_control_chars(char *str)
{
    int i;
    int change = 0;

    for (i = 0; str[i]; i++) {
        if (ISCNTRL(str[i])) {
            str[i] = '_';
            change = 1;
        }
    }

    return change;
}

// Removes characters identified by ISSPACE from the start and end of the string
// NOTE: make sure the string is not const!!
char *trim(char *str)
{
    size_t start;
    size_t end;

    if (str == NULL)
        return str;

    // get start position
    for (start = 0; str[start] && ISSPACE(str[start]); ++start)
        ;
    // get end position
    for (end = strlen(str); start < end && str[end-1] && ISSPACE(str[end-1]); --end)
        ;
    // trim
    if (start == end)
        *str = '\0';// empty string
    else {
        // move string with nul terminator
        str[end] = '\0';
        memmove(str,str+start,end-start+1);
    }
    return str;
}

// Converts one or more consecutive occurences of the delimiters into a single space
// and removes such occurences from the beginning and end of string
// NOTE: make sure the string is not const!!
char *normalize_name(char *str,const char *delims)
{
    char *in = str;
    char *out = str;
    int put_space = 0;

    if (str == NULL || delims == NULL)
        return str;

    // trim start of string
    while (*in && strchr(delims,*in))
        ++in;
    while (*in) {
        if (put_space) {
            // replace trim characters with a single space
            *out = ' ';
            ++out;
        }
        // copy non trim characters
        while (*in && !strchr(delims,*in)) {
            *out = *in;
            ++out;
            ++in;
        }
        // skip trim characters
        while (*in && strchr(delims,*in))
            ++in;
        put_space = 1;
    }
    *out = '\0';
    return str;
}

//stristr: Case insensitive version of strstr, code taken from
//http://www.daniweb.com/code/snippet313.html, Dave Sinkula
//
const char *stristr(const char *haystack, const char *needle)
{
    if (!*needle) {
        return haystack;
    }
    for (; *haystack; ++haystack) {
        if (TOUPPER(*haystack) == TOUPPER(*needle)) {
            // matched starting char -- loop through remaining chars
            const char *h, *n;
            for (h = haystack, n = needle; *h && *n; ++h, ++n) {
                if (TOUPPER(*h) != TOUPPER(*n)) {
                    break;
                }
            }
            if (!*n) { // matched all of 'needle' to null termination
                return haystack; // return the start of the match
            }
        }
    }
    return 0;
}

#ifdef __WIN32
char *_strtok_r(char *s1, const char *s2, char **lasts)
{
    char *ret;

    if (s1 == NULL)
        s1 = *lasts;
    while (*s1 && strchr(s2, *s1))
        ++s1;
    if (*s1 == '\0')
        return NULL;
    ret = s1;
    while (*s1 && !strchr(s2, *s1))
        ++s1;
    if (*s1)
        *s1++ = '\0';
    *lasts = s1;
    return ret;
}
#endif

#if !(defined(WIN32) && defined(_MSC_VER) && _MSC_VER >= 1400) && !defined(HAVE_STRNLEN)
/* Find the length of STRING, but scan at most MAXLEN characters.
   If no '\0' terminator is found in that many characters, return MAXLEN.  */
size_t strnlen(const char *string, size_t maxlen)
{
    const char *end = (const char *)memchr(string, '\0', maxlen);
    return end ? (size_t)(end - string) : maxlen;
}
#endif

#if defined(WIN32) && defined(_MSC_VER) && _MSC_VER <= 1200
uint64 strtoull(const char *str, char **endptr, int base)
{
    uint64 result;
    int count;
    int n;

    if (base == 0) {
        if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X'))
            base = 16;
        else if (str[0] == '0')
            base = 8;
        else
            base = 10;
    }

    if (base == 8)
        count = sscanf(str, "%I64o%n", &result, &n);
    else if (base == 10)
        count = sscanf(str, "%I64u%n", &result, &n);
    else if (base == 16)
        count = sscanf(str, "%I64x%n", &result, &n);
    else
        count = 0; // fail

    if (count < 1) {
        errno = EINVAL;
        result = 0;
        n = 0;
    }

    if (endptr)
        *endptr = (char *)str + n;

    return result;
}
#endif

//----------------------------------------------------
// E-mail check: return 0 (not correct) or 1 (valid).
//----------------------------------------------------
int e_mail_check(char *email)
{
    char ch;
    char *last_arobas;
    size_t len = strlen(email);

    // athena limits
    if (len < 3 || len > 39)
        return 0;

    // part of RFC limits (official reference of e-mail description)
    if (strchr(email, '@') == NULL || email[len-1] == '@')
        return 0;

    if (email[len-1] == '.')
        return 0;

    last_arobas = strrchr(email, '@');

    if (strstr(last_arobas, "@.") != NULL || strstr(last_arobas, "..") != NULL)
        return 0;

    for (ch = 1; ch < 32; ch++)
        if (strchr(last_arobas, ch) != NULL)
            return 0;

    if (strchr(last_arobas, ' ') != NULL || strchr(last_arobas, ';') != NULL)
        return 0;

    // all correct
    return 1;
}

//--------------------------------------------------
// Return numerical value of a switch configuration
// on/off, english, fran�ais, deutsch, espa�ol
//--------------------------------------------------
int config_switch(const char *str)
{
    if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0)
        return 1;
    if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0)
        return 0;

    return (int)strtol(str, NULL, 0);
}

/// strncpy that always nul-terminates the string
char *safestrncpy(char *dst, const char *src, size_t n)
{
    if (n > 0) {
        char *d = dst;
        const char *s = src;
        d[--n] = '\0';/* nul-terminate string */
        for (; n > 0; --n) {
            if ((*d++ = *s++) == '\0') {
                /* nul-pad remaining bytes */
                while (--n > 0)
                    *d++ = '\0';
                break;
            }
        }
    }
    return dst;
}

/// doesn't crash on null pointer
size_t safestrnlen(const char *string, size_t maxlen)
{
    return (string != NULL) ? strnlen(string, maxlen) : 0;
}

/// Works like snprintf, but always nul-terminates the buffer.
/// Returns the size of the string (without nul-terminator)
/// or -1 if the buffer is too small.
///
/// @param buf Target buffer
/// @param sz Size of the buffer (including nul-terminator)
/// @param fmt Format string
/// @param ... Format arguments
/// @return The size of the string or -1 if the buffer is too small
int safesnprintf(char *buf, size_t sz, const char *fmt, ...)
{
    va_list ap;
    int ret;

    va_start(ap,fmt);
    ret = vsnprintf(buf, sz, fmt, ap);
    va_end(ap);
    if (ret < 0 || (size_t)ret >= sz) {
        // overflow
        buf[sz-1] = '\0';// always nul-terminate
        return -1;
    }
    return ret;
}

/// Returns the line of the target position in the string.
/// Lines start at 1.
int strline(const char *str, size_t pos)
{
    const char *target;
    int line;

    if (str == NULL || pos == 0)
        return 1;

    target = str+pos;
    for (line = 1; ; ++line) {
        str = strchr(str, '\n');
        if (str == NULL || target <= str)
            break;// found target line
        ++str;// skip newline
    }
    return line;
}

/// Produces the hexadecimal representation of the given input.
/// The output buffer must be at least count*2+1 in size.
/// Returns true on success, false on failure.
///
/// @param output Output string
/// @param input Binary input buffer
/// @param count Number of bytes to convert
bool bin2hex(char *output, unsigned char *input, size_t count)
{
    char toHex[] = "0123456789abcdef";
    size_t i;

    for (i = 0; i < count; ++i) {
        *output++ = toHex[(*input & 0xF0) >> 4];
        *output++ = toHex[(*input & 0x0F) >> 0];
        ++input;
    }
    *output = '\0';
    return true;
}



/////////////////////////////////////////////////////////////////////
/// Parses a single field in a delim-separated string.
/// The delimiter after the field is skipped.
///
/// @param sv Parse state
/// @return 1 if a field was parsed, 0 if already done, -1 on error.
int sv_parse_next(struct s_svstate *sv)
{
    enum {
        START_OF_FIELD,
        PARSING_FIELD,
        PARSING_C_ESCAPE,
        END_OF_FIELD,
        TERMINATE,
        END
    } state;
    const char *str;
    int len;
    enum e_svopt opt;
    char delim;
    int i;

    if (sv == NULL)
        return -1;// error

    str = sv->str;
    len = sv->len;
    opt = sv->opt;
    delim = sv->delim;

    // check opt
    if (delim == '\n' && (opt&(SV_TERMINATE_CRLF|SV_TERMINATE_LF))) {
        ShowError("sv_parse_next: delimiter '\\n' is not compatible with options SV_TERMINATE_LF or SV_TERMINATE_CRLF.\n");
        return -1;// error
    }
    if (delim == '\r' && (opt&(SV_TERMINATE_CRLF|SV_TERMINATE_CR))) {
        ShowError("sv_parse_next: delimiter '\\r' is not compatible with options SV_TERMINATE_CR or SV_TERMINATE_CRLF.\n");
        return -1;// error
    }

    if (sv->done || str == NULL) {
        sv->done = true;
        return 0;// nothing to parse
    }

#define IS_END() ( i >= len )
#define IS_DELIM() ( str[i] == delim )
#define IS_TERMINATOR() ( \
                          ((opt&SV_TERMINATE_LF) && str[i] == '\n') || \
                          ((opt&SV_TERMINATE_CR) && str[i] == '\r') || \
                          ((opt&SV_TERMINATE_CRLF) && i+1 < len && str[i] == '\r' && str[i+1] == '\n') )
#define IS_C_ESCAPE() ( (opt&SV_ESCAPE_C) && str[i] == '\\' )
#define SET_FIELD_START() sv->start = i
#define SET_FIELD_END() sv->end = i

    i = sv->off;
    state = START_OF_FIELD;
    while (state != END) {
        switch (state) {
            case START_OF_FIELD:// record start of field and start parsing it
                SET_FIELD_START();
                state = PARSING_FIELD;
                break;

            case PARSING_FIELD:// skip field character
                if (IS_END() || IS_DELIM() || IS_TERMINATOR())
                    state = END_OF_FIELD;
                else if (IS_C_ESCAPE())
                    state = PARSING_C_ESCAPE;
                else
                    ++i;// normal character
                break;

            case PARSING_C_ESCAPE: { // skip escape sequence (validates it too)
                    ++i;// '\\'
                    if (IS_END()) {
                        ShowError("sv_parse_next: empty escape sequence\n");
                        return -1;
                    }
                    if (str[i] == 'x') {
                        // hex escape
                        ++i;// 'x'
                        if (IS_END() || !ISXDIGIT(str[i])) {
                            ShowError("sv_parse_next: \\x with no following hex digits\n");
                            return -1;
                        }
                        do {
                            ++i;// hex digit
                        } while (!IS_END() && ISXDIGIT(str[i]));
                    } else if (str[i] == '0' || str[i] == '1' || str[i] == '2') {
                        // octal escape
                        ++i;// octal digit
                        if (!IS_END() && str[i] >= '0' && str[i] <= '7')
                            ++i;// octal digit
                        if (!IS_END() && str[i] >= '0' && str[i] <= '7')
                            ++i;// octal digit
                    } else if (strchr(SV_ESCAPE_C_SUPPORTED, str[i])) {
                        // supported escape character
                        ++i;
                    } else {
                        ShowError("sv_parse_next: unknown escape sequence \\%c\n", str[i]);
                        return -1;
                    }
                    state = PARSING_FIELD;
                    break;
                }

            case END_OF_FIELD:// record end of field and stop
                SET_FIELD_END();
                state = END;
                if (IS_END())
                    ;// nothing else
                else if (IS_DELIM())
                    ++i;// delim
                else if (IS_TERMINATOR())
                    state = TERMINATE;
                break;

            case TERMINATE:
#if 0
                // skip line terminator
                if ((opt&SV_TERMINATE_CRLF) && i+1 < len && str[i] == '\r' && str[i+1] == '\n')
                    i += 2;// CRLF
                else
                    ++i;// CR or LF
#endif
                sv->done = true;
                state = END;
                break;
        }
    }
    if (IS_END())
        sv->done = true;
    sv->off = i;

#undef IS_END
#undef IS_DELIM
#undef IS_TERMINATOR
#undef IS_C_ESCAPE
#undef SET_FIELD_START
#undef SET_FIELD_END

    return 1;
}


/// Parses a delim-separated string.
/// Starts parsing at startoff and fills the pos array with position pairs.
/// out_pos[0] and out_pos[1] are the start and end of line.
/// Other position pairs are the start and end of fields.
/// Returns the number of fields found or -1 if an error occurs.
///
/// out_pos can be NULL.
/// If a line terminator is found, the end position is placed there.
/// out_pos[2] and out_pos[3] for the first field, out_pos[4] and out_pos[5]
/// for the seconds field and so on.
/// Unfilled positions are set to -1.
///
/// @param str String to parse
/// @param len Length of the string
/// @param startoff Where to start parsing
/// @param delim Field delimiter
/// @param out_pos Array of resulting positions
/// @param npos Size of the pos array
/// @param opt Options that determine the parsing behaviour
/// @return Number of fields found in the string or -1 if an error occured
int sv_parse(const char *str, int len, int startoff, char delim, int *out_pos, int npos, enum e_svopt opt)
{
    struct s_svstate sv;
    int count;

    // initialize
    if (out_pos == NULL) npos = 0;
    for (count = 0; count < npos; ++count)
        out_pos[count] = -1;
    sv.str = str;
    sv.len = len;
    sv.off = startoff;
    sv.opt = opt;
    sv.delim = delim;
    sv.done = false;

    // parse
    count = 0;
    if (npos > 0) out_pos[0] = startoff;
    while (!sv.done) {
        ++count;
        if (sv_parse_next(&sv) <= 0)
            return -1;// error
        if (npos > count*2) out_pos[count*2] = sv.start;
        if (npos > count*2+1) out_pos[count*2+1] = sv.end;
    }
    if (npos > 1) out_pos[1] = sv.off;
    return count;
}

/// Splits a delim-separated string.
/// WARNING: this function modifies the input string
/// Starts splitting at startoff and fills the out_fields array.
/// out_fields[0] is the start of the next line.
/// Other entries are the start of fields (nul-teminated).
/// Returns the number of fields found or -1 if an error occurs.
///
/// out_fields can be NULL.
/// Fields that don't fit in out_fields are not nul-terminated.
/// Extra entries in out_fields are filled with the end of the last field (empty string).
///
/// @param str String to parse
/// @param len Length of the string
/// @param startoff Where to start parsing
/// @param delim Field delimiter
/// @param out_fields Array of resulting fields
/// @param nfields Size of the field array
/// @param opt Options that determine the parsing behaviour
/// @return Number of fields found in the string or -1 if an error occured
int sv_split(char *str, int len, int startoff, char delim, char **out_fields, int nfields, enum e_svopt opt)
{
    int pos[1024];
    int i;
    int done;
    char *end;
    int ret = sv_parse(str, len, startoff, delim, pos, ARRAYLENGTH(pos), opt);

    if (ret == -1 || out_fields == NULL || nfields <= 0)
        return ret; // nothing to do

    // next line
    end = str + pos[1];
    if (end[0] == '\0') {
        *out_fields = end;
    } else if ((opt&SV_TERMINATE_LF) && end[0] == '\n') {
        if (!(opt&SV_KEEP_TERMINATOR))
            end[0] = '\0';
        *out_fields = end + 1;
    } else if ((opt&SV_TERMINATE_CRLF) && end[0] == '\r' && end[1] == '\n') {
        if (!(opt&SV_KEEP_TERMINATOR))
            end[0] = end[1] = '\0';
        *out_fields = end + 2;
    } else if ((opt&SV_TERMINATE_CR) && end[0] == '\r') {
        if (!(opt&SV_KEEP_TERMINATOR))
            end[0] = '\0';
        *out_fields = end + 1;
    } else {
        ShowError("sv_split: unknown line delimiter 0x02%x.\n", (unsigned char)end[0]);
        return -1;// error
    }
    ++out_fields;
    --nfields;

    // fields
    i = 2;
    done = 0;
    while (done < ret && nfields > 0) {
        if (i < ARRAYLENGTH(pos)) {
            // split field
            *out_fields = str + pos[i];
            end = str + pos[i+1];
            *end = '\0';
            // next field
            i += 2;
            ++done;
            ++out_fields;
            --nfields;
        } else {
            // get more fields
            sv_parse(str, len, pos[i-1] + 1, delim, pos, ARRAYLENGTH(pos), opt);
            i = 2;
        }
    }
    // remaining fields
    for (i = 0; i < nfields; ++i)
        out_fields[i] = end;
    return ret;
}

/// Escapes src to out_dest according to the format of the C compiler.
/// Returns the length of the escaped string.
/// out_dest should be len*4+1 in size.
///
/// @param out_dest Destination buffer
/// @param src Source string
/// @param len Length of the source string
/// @param escapes Extra characters to be escaped
/// @return Length of the escaped string
size_t sv_escape_c(char *out_dest, const char *src, size_t len, const char *escapes)
{
    size_t i;
    size_t j;

    if (out_dest == NULL)
        return 0;// nothing to do
    if (src == NULL) {
        // nothing to escape
        *out_dest = 0;
        return 0;
    }
    if (escapes == NULL)
        escapes = "";

    for (i = 0, j = 0; i < len; ++i) {
        switch (src[i]) {
            case '\0':// octal 0
                out_dest[j++] = '\\';
                out_dest[j++] = '0';
                out_dest[j++] = '0';
                out_dest[j++] = '0';
                break;
            case '\r':// carriage return
                out_dest[j++] = '\\';
                out_dest[j++] = 'r';
                break;
            case '\n':// line feed
                out_dest[j++] = '\\';
                out_dest[j++] = 'n';
                break;
            case '\\':// escape character
                out_dest[j++] = '\\';
                out_dest[j++] = '\\';
                break;
            default:
                if (strchr(escapes,src[i])) {
                    // escape
                    out_dest[j++] = '\\';
                    switch (src[i]) {
                        case '\a':
                            out_dest[j++] = 'a';
                            break;
                        case '\b':
                            out_dest[j++] = 'b';
                            break;
                        case '\t':
                            out_dest[j++] = 't';
                            break;
                        case '\v':
                            out_dest[j++] = 'v';
                            break;
                        case '\f':
                            out_dest[j++] = 'f';
                            break;
                        case '\?':
                            out_dest[j++] = '?';
                            break;
                        default:// to octal
                            out_dest[j++] = '0'+((char)(((unsigned char)src[i]&0700)>>6));
                            out_dest[j++] = '0'+((char)(((unsigned char)src[i]&0070)>>3));
                            out_dest[j++] = '0'+((char)(((unsigned char)src[i]&0007)));
                            break;
                    }
                } else
                    out_dest[j++] = src[i];
                break;
        }
    }
    out_dest[j] = 0;
    return j;
}

/// Unescapes src to out_dest according to the format of the C compiler.
/// Returns the length of the unescaped string.
/// out_dest should be len+1 in size and can be the same buffer as src.
///
/// @param out_dest Destination buffer
/// @param src Source string
/// @param len Length of the source string
/// @return Length of the escaped string
size_t sv_unescape_c(char *out_dest, const char *src, size_t len)
{
    static unsigned char low2hex[256] = {
        0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x0?
        0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x1?
        0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x2?
        0,  1,  2,  3,  4,  5,  6, 7, 8, 9, 0, 0, 0, 0, 0, 0,// 0x3?
        0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x4?
        0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x5?
        0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x6?
        0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x7?
        0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x8?
        0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0x9?
        0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0xA?
        0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0xB?
        0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0xC?
        0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0xD?
        0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,// 0xE?
        0,  0,  0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 0xF?
    };
    size_t i;
    size_t j;

    for (i = 0, j = 0; i < len;) {
        if (src[i] == '\\') {
            ++i;// '\\'
            if (i >= len)
                ShowWarning("sv_unescape_c: empty escape sequence\n");
            else if (src[i] == 'x') {
                // hex escape sequence
                unsigned char c = 0;
                unsigned char inrange = 1;

                ++i;// 'x'
                if (i >= len || !ISXDIGIT(src[i])) {
                    ShowWarning("sv_unescape_c: \\x with no following hex digits\n");
                    continue;
                }
                do {
                    if (c > 0x0F && inrange) {
                        ShowWarning("sv_unescape_c: hex escape sequence out of range\n");
                        inrange = 0;
                    }
                    c = (c<<4)|low2hex[(unsigned char)src[i]];// hex digit
                    ++i;
                } while (i < len && ISXDIGIT(src[i]));
                out_dest[j++] = (char)c;
            } else if (src[i] == '0' || src[i] == '1' || src[i] == '2' || src[i] == '3') {
                // octal escape sequence (255=0377)
                unsigned char c = src[i]-'0';
                ++i;// '0', '1', '2' or '3'
                if (i < len && src[i] >= '0' && src[i] <= '7') {
                    c = (c<<3)|(src[i]-'0');
                    ++i;// octal digit
                }
                if (i < len && src[i] >= '0' && src[i] <= '7') {
                    c = (c<<3)|(src[i]-'0');
                    ++i;// octal digit
                }
                out_dest[j++] = (char)c;
            } else {
                // other escape sequence
                if (strchr(SV_ESCAPE_C_SUPPORTED, src[i]) == NULL)
                    ShowWarning("sv_unescape_c: unknown escape sequence \\%c\n", src[i]);
                switch (src[i]) {
                    case 'a':
                        out_dest[j++] = '\a';
                        break;
                    case 'b':
                        out_dest[j++] = '\b';
                        break;
                    case 't':
                        out_dest[j++] = '\t';
                        break;
                    case 'n':
                        out_dest[j++] = '\n';
                        break;
                    case 'v':
                        out_dest[j++] = '\v';
                        break;
                    case 'f':
                        out_dest[j++] = '\f';
                        break;
                    case 'r':
                        out_dest[j++] = '\r';
                        break;
                    case '?':
                        out_dest[j++] = '\?';
                        break;
                    default:
                        out_dest[j++] = src[i];
                        break;
                }
                ++i;// escaped character
            }
        } else
            out_dest[j++] = src[i++];// normal character
    }
    out_dest[j] = 0;
    return j;
}

/// Skips a C escape sequence (starting with '\\').
const char *skip_escaped_c(const char *p)
{
    if (p && *p == '\\') {
        ++p;
        switch (*p) {
            case 'x':// hexadecimal
                ++p;
                while (ISXDIGIT(*p))
                    ++p;
                break;
            case '0':
            case '1':
            case '2':
            case '3':// octal
                ++p;
                if (*p >= '0' && *p <= '7')
                    ++p;
                if (*p >= '0' && *p <= '7')
                    ++p;
                break;
            default:
                if (*p && strchr(SV_ESCAPE_C_SUPPORTED, *p))
                    ++p;
        }
    }
    return p;
}


/// Opens and parses a file containing delim-separated columns, feeding them to the specified callback function row by row.
/// Tracks the progress of the operation (current line number, number of successfully processed rows).
/// Returns 'true' if it was able to process the specified file, or 'false' if it could not be read.
///
/// @param directory Directory
/// @param filename File to process
/// @param delim Field delimiter
/// @param mincols Minimum number of columns of a valid row
/// @param maxcols Maximum number of columns of a valid row
/// @param parseproc User-supplied row processing function
/// @return true on success, false if file could not be opened
bool sv_readdb(const char *directory, const char *filename, char delim, int mincols, int maxcols, int maxrows, bool (*parseproc)(char *fields[], int columns, int current))
{
    FILE *fp;
    int lines = 0;
    int entries = 0;
    char **fields; // buffer for fields ([0] is reserved)
    int columns, fields_length;
    char path[1024], line[1024];
    char *match;

    snprintf(path, sizeof(path), "%s/%s", directory, filename);

    // open file
    fp = fopen(path, "r");
    if (fp == NULL) {
        ShowError("sv_readdb: can't read %s\n", path);
        return false;
    }

    // allocate enough memory for the maximum requested amount of columns plus the reserved one
    fields_length = maxcols+1;
    fields = (char **)aMalloc(fields_length*sizeof(char *));

    // process rows one by one
    while (fgets(line, sizeof(line), fp)) {
        lines++;

        if ((match = strstr(line, "//")) != NULL) {
            // strip comments
            match[0] = 0;
        }

        //TODO: strip trailing whitespace
        if (line[0] == '\0' || line[0] == '\n' || line[0] == '\r')
            continue;

        columns = sv_split(line, strlen(line), 0, delim, fields, fields_length, (e_svopt)(SV_TERMINATE_LF|SV_TERMINATE_CRLF));

        if (columns < mincols) {
            ShowError("sv_readdb: Insufficient columns in line %d of \"%s\" (found %d, need at least %d).\n", lines, path, columns, mincols);
            continue; // not enough columns
        }
        if (columns > maxcols) {
            ShowError("sv_readdb: Too many columns in line %d of \"%s\" (found %d, maximum is %d).\n", lines, path, columns, maxcols);
            continue; // too many columns
        }
        if (entries == maxrows) {
            ShowError("sv_readdb: Reached the maximum allowed number of entries (%d) when parsing file \"%s\".\n", maxrows, path);
            break;
        }

        // parse this row
        if (!parseproc(fields+1, columns, entries)) {
            ShowError("sv_readdb: Could not process contents of line %d of \"%s\".\n", lines, path);
            continue; // invalid row contents
        }

        // success!
        entries++;
    }

    aFree(fields);
    fclose(fp);
    ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", entries, path);

    return true;
}


/////////////////////////////////////////////////////////////////////
// StringBuf - dynamic string
//
// @author MouseJstr (original)

/// Allocates a StringBuf
StringBuf *StringBuf_Malloc()
{
    StringBuf *self;
    CREATE(self, StringBuf, 1);
    StringBuf_Init(self);
    return self;
}

/// Initializes a previously allocated StringBuf
void StringBuf_Init(StringBuf *self)
{
    self->max_ = 1024;
    self->ptr_ = self->buf_ = (char *)aMalloc(self->max_ + 1);
}

/// Appends the result of printf to the StringBuf
int StringBuf_Printf(StringBuf *self, const char *fmt, ...)
{
    int len;
    va_list ap;

    va_start(ap, fmt);
    len = StringBuf_Vprintf(self, fmt, ap);
    va_end(ap);

    return len;
}

/// Appends the result of vprintf to the StringBuf
int StringBuf_Vprintf(StringBuf *self, const char *fmt, va_list ap)
{
    int n, size, off;

    for (;;) {
        va_list apcopy;
        /* Try to print in the allocated space. */
        size = self->max_ - (self->ptr_ - self->buf_);
        va_copy(apcopy, ap);
        n = vsnprintf(self->ptr_, size, fmt, apcopy);
        va_end(apcopy);
        /* If that worked, return the length. */
        if (n > -1 && n < size) {
            self->ptr_ += n;
            return (int)(self->ptr_ - self->buf_);
        }
        /* Else try again with more space. */
        self->max_ *= 2; // twice the old size
        off = (int)(self->ptr_ - self->buf_);
        self->buf_ = (char *)aRealloc(self->buf_, self->max_ + 1);
        self->ptr_ = self->buf_ + off;
    }
}

/// Appends the contents of another StringBuf to the StringBuf
int StringBuf_Append(StringBuf *self, const StringBuf *sbuf)
{
    int available = self->max_ - (self->ptr_ - self->buf_);
    int needed = (int)(sbuf->ptr_ - sbuf->buf_);

    if (needed >= available) {
        int off = (int)(self->ptr_ - self->buf_);
        self->max_ += needed;
        self->buf_ = (char *)aRealloc(self->buf_, self->max_ + 1);
        self->ptr_ = self->buf_ + off;
    }

    memcpy(self->ptr_, sbuf->buf_, needed);
    self->ptr_ += needed;
    return (int)(self->ptr_ - self->buf_);
}

// Appends str to the StringBuf
int StringBuf_AppendStr(StringBuf *self, const char *str)
{
    int available = self->max_ - (self->ptr_ - self->buf_);
    int needed = (int)strlen(str);

    if (needed >= available) {
        // not enough space, expand the buffer (minimum expansion = 1024)
        int off = (int)(self->ptr_ - self->buf_);
        self->max_ += max(needed, 1024);
        self->buf_ = (char *)aRealloc(self->buf_, self->max_ + 1);
        self->ptr_ = self->buf_ + off;
    }

    memcpy(self->ptr_, str, needed);
    self->ptr_ += needed;
    return (int)(self->ptr_ - self->buf_);
}

// Returns the length of the data in the Stringbuf
int StringBuf_Length(StringBuf *self)
{
    return (int)(self->ptr_ - self->buf_);
}

/// Returns the data in the StringBuf
char *StringBuf_Value(StringBuf *self)
{
    *self->ptr_ = '\0';
    return self->buf_;
}

/// Clears the contents of the StringBuf
void StringBuf_Clear(StringBuf *self)
{
    self->ptr_ = self->buf_;
}

/// Destroys the StringBuf
void StringBuf_Destroy(StringBuf *self)
{
    aFree(self->buf_);
    self->ptr_ = self->buf_ = 0;
    self->max_ = 0;
}

// Frees a StringBuf returned by StringBuf_Malloc
void StringBuf_Free(StringBuf *self)
{
    StringBuf_Destroy(self);
    aFree(self);
}