/** * This file is part of Hercules. * http://herc.ws - http://github.com/HerculesWS/Hercules * * Copyright (C) 2012-2018 Hercules Dev Team * Copyright (C) Athena Dev Teams * * Hercules is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define HERCULES_CORE #include "strlib.h" #include "common/cbasetypes.h" #include "common/memmgr.h" #include "common/showmsg.h" #include #include #include #define J_MAX_MALLOC_SIZE 65535 static struct strlib_interface strlib_s; static struct stringbuf_interface stringbuf_s; static struct sv_interface sv_s; struct strlib_interface *strlib; struct stringbuf_interface *StrBuf; struct sv_interface *sv; // escapes a string in-place (' -> \' , \ -> \\ , % -> _) static 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 static 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 static 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. static int strlib_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!! static char *strlib_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 null-terminator str[end] = '\0'; memmove(str,str+start,end-start+1); } return str; } // Converts one or more consecutive occurrences of the delimiters into a single space // and removes such occurrences from the beginning and end of string // NOTE: make sure the string is not const!! static char *strlib_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 // static const char *strlib_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; } static char* strlib_strtok_r(char *s1, const char *s2, char **lasts) { #ifdef __WIN32 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; #else return strtok_r(s1, s2, lasts); #endif } static size_t strlib_strnlen(const char *string, size_t maxlen) { // TODO: Verify whether this implementation is still necessary for NetBSD 5.x // and possibly some Solaris versions. #if !(defined(WIN32) && defined(_MSC_VER)) && !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. */ const char* end = (const char*)memchr(string, '\0', maxlen); return end ? (size_t) (end - string) : maxlen; #else return strnlen(string, maxlen); #endif } //---------------------------------------------------- // E-mail check: return 0 (not correct) or 1 (valid). //---------------------------------------------------- static int strlib_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, yes/no, true/false, number //-------------------------------------------------- static int strlib_config_switch(const char *str) { size_t len = strlen(str); if ((len == 2 && strcmpi(str, "on") == 0) || (len == 3 && strcmpi(str, "yes") == 0) || (len == 4 && strcmpi(str, "true") == 0) // || (len == 3 && strcmpi(str, "oui") == 0) // Uncomment and edit to add your own localized versions ) return 1; if ((len == 3 && strcmpi(str, "off") == 0) || (len == 2 && strcmpi(str, "no") == 0) || (len == 5 && strcmpi(str, "false") == 0) // || (len == 3 && strcmpi(str, "non") == 0) // Uncomment and edit to add your own localized versions ) return 0; return (int)strtol(str, NULL, 0); } /// strncpy that always null-terminates the string /// @remark this function will read at most `n` - 1 bytes from `src` (from 0 to `n` - 2) static char *strlib_safestrncpy(char *dst, const char *src, size_t n) { if( n > 0 ) { char* d = dst; const char* s = src; d[--n] = '\0';/* null-terminate string */ for( ; n > 0; --n ) { if( (*d++ = *s++) == '\0' ) {/* null-pad remaining bytes */ while( --n > 0 ) *d++ = '\0'; break; } } } return dst; } /// doesn't crash on null pointer static size_t strlib_safestrnlen(const char *string, size_t maxlen) { return ( string != NULL ) ? strnlen(string, maxlen) : 0; } /// Works like snprintf, but always null-terminates the buffer. /// Returns the size of the string (without null-terminator) /// or -1 if the buffer is too small. /// /// @param buf Target buffer /// @param sz Size of the buffer (including null-terminator) /// @param fmt Format string /// @param ... Format arguments /// @return The size of the string or -1 if the buffer is too small static int strlib_safesnprintf(char *buf, size_t sz, const char *fmt, ...) __attribute__((format(printf, 3, 4))); static int strlib_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 null-terminate return -1; } return ret; } /// Returns the line of the target position in the string. /// Lines start at 1. static int strlib_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 static bool strlib_bin2hex(char *output, const 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 svstate Parse state /// @return 1 if a field was parsed, 0 if already done, -1 on error. static int sv_parse_next(struct s_svstate *svstate) { 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( svstate == NULL ) return -1;// error str = svstate->str; len = svstate->len; opt = svstate->opt; delim = svstate->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( svstate->done || str == NULL ) { svstate->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() svstate->start = i #define SET_FIELD_END() svstate->end = i i = svstate->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 svstate->done = true; state = END; break; } } if( IS_END() ) svstate->done = true; svstate->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 behavior /// @return Number of fields found in the string or -1 if an error occurred static int sv_parse(const char *str, int len, int startoff, char delim, int *out_pos, int npos, enum e_svopt opt) { struct s_svstate svstate; int count; // initialize if( out_pos == NULL ) npos = 0; for( count = 0; count < npos; ++count ) out_pos[count] = -1; svstate.str = str; svstate.len = len; svstate.off = startoff; svstate.opt = opt; svstate.delim = delim; svstate.done = false; svstate.start = 0; svstate.end = 0; // parse count = 0; if( npos > 0 ) out_pos[0] = startoff; while( !svstate.done ) { ++count; if( sv_parse_next(&svstate) <= 0 ) return -1;// error if( npos > count*2 ) out_pos[count*2] = svstate.start; if( npos > count*2+1 ) out_pos[count*2+1] = svstate.end; } if( npos > 1 ) out_pos[1] = svstate.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 (null-terminated). /// 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 null-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 behavior /// @return Number of fields found in the string or -1 if an error occurred static 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 static 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; 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 static 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 '\\'). static 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 static 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]; snprintf(path, sizeof(path), "%s/%s", directory, filename); // open file if( (fp = fopen(path, "r")) == 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) ) { char *match; 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, (int)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 static StringBuf *StringBuf_Malloc(void) { StringBuf* self; CREATE(self, StringBuf, 1); StrBuf->Init(self); return self; } /// Initializes a previously allocated StringBuf static 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 static int StringBuf_Printf(StringBuf *self, const char *fmt, ...) __attribute__((format(printf, 2, 3))); static int StringBuf_Printf(StringBuf *self, const char *fmt, ...) { int len; va_list ap; va_start(ap, fmt); len = StrBuf->Vprintf(self, fmt, ap); va_end(ap); return len; } /// Appends the result of vprintf to the StringBuf static int StringBuf_Vprintf(StringBuf *self, const char *fmt, va_list ap) { for(;;) { va_list apcopy; int n, off; /* Try to print in the allocated space. */ size_t 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 && (size_t)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 static int StringBuf_Append(StringBuf *self, const StringBuf *sbuf) { size_t available = self->max_ - (self->ptr_ - self->buf_); size_t needed = sbuf->ptr_ - sbuf->buf_; if( needed >= available ) { size_t off = (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 static int StringBuf_AppendStr(StringBuf *self, const char *str) { size_t available = self->max_ - (self->ptr_ - self->buf_); size_t needed = strlen(str); if( needed >= available ) { // not enough space, expand the buffer (minimum expansion = 1024) size_t off = (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 static int StringBuf_Length(StringBuf *self) { return (int)(self->ptr_ - self->buf_); } /// Returns the data in the StringBuf static char *StringBuf_Value(StringBuf *self) { *self->ptr_ = '\0'; return self->buf_; } /// Clears the contents of the StringBuf static void StringBuf_Clear(StringBuf *self) { self->ptr_ = self->buf_; } /// Destroys the StringBuf static void StringBuf_Destroy(StringBuf *self) { aFree(self->buf_); self->ptr_ = self->buf_ = 0; self->max_ = 0; } // Frees a StringBuf returned by StringBuf_Malloc static void StringBuf_Free(StringBuf *self) { StrBuf->Destroy(self); aFree(self); } void strlib_defaults(void) { /* connect */ strlib = &strlib_s; StrBuf = &stringbuf_s; sv = &sv_s; /* link~u! */ strlib->jstrescape = jstrescape; strlib->jstrescapecpy = jstrescapecpy; strlib->jmemescapecpy = jmemescapecpy; strlib->remove_control_chars_ = strlib_remove_control_chars; strlib->trim_ = strlib_trim; strlib->normalize_name_ = strlib_normalize_name; strlib->stristr_ = strlib_stristr; strlib->strnlen_ = strlib_strnlen; strlib->strtok_r_ = strlib_strtok_r; strlib->e_mail_check_ = strlib_e_mail_check; strlib->config_switch_ = strlib_config_switch; strlib->safestrncpy_ = strlib_safestrncpy; strlib->safestrnlen_ = strlib_safestrnlen; strlib->safesnprintf_ = strlib_safesnprintf; strlib->strline_ = strlib_strline; strlib->bin2hex_ = strlib_bin2hex; StrBuf->Malloc = StringBuf_Malloc; StrBuf->Init = StringBuf_Init; StrBuf->Printf = StringBuf_Printf; StrBuf->Vprintf = StringBuf_Vprintf; StrBuf->Append = StringBuf_Append; StrBuf->AppendStr = StringBuf_AppendStr; StrBuf->Length = StringBuf_Length; StrBuf->Value = StringBuf_Value; StrBuf->Clear = StringBuf_Clear; StrBuf->Destroy = StringBuf_Destroy; StrBuf->Free = StringBuf_Free; sv->parse_next = sv_parse_next; sv->parse = sv_parse; sv->split = sv_split; sv->escape_c = sv_escape_c; sv->unescape_c = sv_unescape_c; sv->skip_escaped_c = skip_escaped_c; sv->readdb = sv_readdb; }