Back to home page

Enduro/X

 
 

    


0001 /* inih -- simple .INI file parser
0002 
0003 inih is released under the New BSD license (see LICENSE.txt). Go to the project
0004 home page for more info:
0005 
0006 https://github.com/benhoyt/inih
0007 
0008 */
0009 
0010 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
0011 #define _CRT_SECURE_NO_WARNINGS
0012 #endif
0013 
0014 #include <stdio.h>
0015 #include <ctype.h>
0016 #include <string.h>
0017 #include <ndebug.h>
0018 #include <ndrstandard.h>
0019 
0020 #include "ndrx_ini.h"
0021 #include "userlog.h"
0022 
0023 #if !INI_USE_STACK
0024 #include <stdlib.h>
0025 #endif
0026 
0027 #define MAX_SECTION 50
0028 #define MAX_NAME 50
0029 
0030 /* Strip whitespace chars off end of given string, in place. Return s. */
0031 static char* rstrip(char* s)
0032 {
0033     char* p = s + strlen(s);
0034     while (p > s && isspace((unsigned char)(*--p)))
0035         *p = '\0';
0036     return s;
0037 }
0038 
0039 /* Return pointer to first non-whitespace char in given string. */
0040 static char* lskip(const char* s)
0041 {
0042     while (*s && isspace((unsigned char)(*s)))
0043         s++;
0044     return (char*)s;
0045 }
0046 
0047 /* Return pointer to first char (of chars) or inline comment in given string,
0048    or pointer to null at end of string if neither found. Inline comment must
0049    be prefixed by a whitespace character to register as a comment. */
0050 static char* find_chars_or_comment(const char* s, const char* chars)
0051 {
0052 #if INI_ALLOW_INLINE_COMMENTS
0053     int was_space = 0;
0054     while (*s && (!chars || !strchr(chars, *s)) &&
0055            !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
0056         was_space = isspace((unsigned char)(*s));
0057         s++;
0058     }
0059 #else
0060     while (*s && (!chars || !strchr(chars, *s))) {
0061         s++;
0062     }
0063 #endif
0064     return (char*)s;
0065 }
0066 
0067 /* Version of strncpy that ensures dest (size bytes) is null-terminated. */
0068 static char* strncpy0(char* dest, const char* src, size_t size)
0069 {
0070     NDRX_STRCPY_SAFE_DST(dest, src, size);
0071     
0072     return dest;
0073 }
0074 
0075 /* See documentation in header file. 
0076  * mvitolin: TODO: Add support for multi-line value for single call of the handler
0077  * Needs to do some buffering...
0078  */
0079 int ndrx_ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
0080                      void* user, void *user2, void *user3)
0081 {
0082     /* Uses a fair bit of stack (use heap instead if you need to) */
0083 #if INI_USE_STACK
0084     char tmp_line[INI_MAX_LINE];
0085     char tmp_line2[INI_MAX_LINE];
0086 #endif
0087     char section[MAX_SECTION] = "";
0088     char prev_name[MAX_NAME] = "";
0089 
0090     char* start;
0091     char* end;
0092     char* name;
0093     char* value;
0094     int lineno = 0;
0095     int error = 0;
0096     
0097     char *line;
0098     char *line2;
0099     
0100 
0101 #if !INI_USE_STACK
0102     line = (char*)NDRX_MALLOC(INI_MAX_LINE);
0103     if (!line) {
0104         return -2;
0105     }
0106     
0107     line2 = (char*)NDRX_MALLOC(INI_MAX_LINE);
0108     if (!line2) {
0109         return -2;
0110     }
0111 #else
0112     line = tmp_line;
0113     line2 = tmp_line2;
0114 #endif
0115 
0116     /* Scan through stream line by line */
0117     while (reader(line, INI_MAX_LINE, stream) != NULL) {
0118         lineno++;
0119 
0120         start = line;
0121         
0122 #if INI_ALLOW_BOM
0123         if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
0124                            (unsigned char)start[1] == 0xBB &&
0125                            (unsigned char)start[2] == 0xBF) {
0126             start += 3;
0127         }
0128 #endif
0129         start = lskip(rstrip(start));
0130         
0131 line_buffered:
0132         if (*start == ';' || *start == '#') {
0133             /* Per Python configparser, allow both ; and # comments at the
0134                start of a line */
0135         }
0136 #if INI_ALLOW_MULTILINE
0137         else if (*prev_name && *start && start > line) {
0138             /* Non-blank line with leading whitespace, treat as continuation
0139                of previous name's value (as per Python configparser). */
0140             if (!handler(user, user2, user3, section, prev_name, start) && !error)
0141                 error = lineno;
0142         }
0143 #endif
0144         else if (*start == '[') {
0145             /* A "[section]" line */
0146             end = find_chars_or_comment(start + 1, "]");
0147             if (*end == ']') {
0148                 *end = '\0';
0149                 strncpy0(section, start + 1, sizeof(section));
0150                 *prev_name = '\0';
0151             }
0152             else if (!error) {
0153                 /* No ']' found on section line */
0154                 error = lineno;
0155             }
0156         }
0157         else if (*start) {
0158             /* Not a comment, must be a name[=:]value pair */
0159             end = find_chars_or_comment(start, "=:");
0160             if (*end == '=' || *end == ':') {
0161                 *end = '\0';
0162                 name = rstrip(start);
0163                 value = lskip(end + 1);
0164 #if INI_ALLOW_INLINE_COMMENTS
0165                 end = find_chars_or_comment(value, NULL);
0166                 if (*end)
0167                     *end = '\0';
0168 #endif
0169                 rstrip(value);
0170 
0171                 /* Valid name[=:]value pair found, call handler */
0172                 strncpy0(prev_name, name, sizeof(prev_name));
0173                 
0174 #if INI_ALLOW_MULTILINE
0175                 /*
0176                  * Home some more code for multi-line support, buffer the line read...
0177                  */
0178                 
0179                 while (reader(line2, INI_MAX_LINE, stream) != NULL)
0180                 {
0181                     lineno++;
0182 
0183                     start = line2;
0184                     start = lskip(rstrip(start));
0185                     
0186                     if (*start == ';' || *start == '#') {
0187                         /* Per Python configparser, allow both ; and # comments at the
0188                            start of a line */
0189                         continue; /* Skip the line... */
0190                     }
0191                     else if (*start && start > line2) {
0192                         int free_space_in_value;
0193                         int additional_value_len;
0194                         /* we have an additional data */
0195                         rstrip(start);
0196                         #if INI_ALLOW_INLINE_COMMENTS
0197                                 end = find_chars_or_comment(start, NULL);
0198                                 if (*end)
0199                                     *end = '\0';
0200                         #endif
0201                         /* calculate free space in dest buffer */
0202                         free_space_in_value = INI_MAX_LINE - ((value+strlen(value)) - line);
0203                         additional_value_len = strlen(start);
0204                         
0205                         if (free_space_in_value < additional_value_len)
0206                         {
0207                             userlog("Failed to parse config - value too large,"
0208                                     "config param: %s (limit:%d) runs over by: %d", 
0209                                     name, INI_MAX_LINE, additional_value_len,
0210                                     free_space_in_value);
0211                             error = lineno;
0212                         }
0213                         else
0214                         {
0215                             strcat(value, start);
0216                         }
0217                     }
0218                     else
0219                     {
0220                         /* Send value to user and continue with next line */
0221                         if (!handler(user, user2, user3, section, name, value) && !error)
0222                         {
0223                             error = lineno;
0224                         }
0225                         else
0226                         {
0227                             /* so we have finished with value
0228                              * swap the pointers...
0229                              */
0230                             char *p;
0231                             
0232                             p = line;
0233                             line = line2;
0234                             line2 = p;
0235                             
0236                             goto line_buffered;
0237                         }
0238                     }
0239                 }
0240                 
0241                 /* so if we get here, this was last line, process it accordingly.. */
0242                 if (!handler(user, user2, user3, section, name, value) && !error)
0243                 {
0244                     error = lineno;
0245                 }
0246                 
0247 #else
0248                 if (!handler(user, user2, user3, section, name, value) && !error)
0249                     error = lineno;
0250 #endif
0251                 
0252 
0253             }
0254             else if (!error) {
0255                 /* No '=' or ':' found on name[=:]value line */
0256                 error = lineno;
0257             }
0258         }
0259 
0260 #if INI_STOP_ON_FIRST_ERROR
0261         if (error)
0262             break;
0263 #endif
0264     }
0265 
0266 #if !INI_USE_STACK
0267     NDRX_FREE(line);
0268     NDRX_FREE(line2);
0269 #endif
0270 
0271     return error;
0272 }
0273 
0274 /* See documentation in header file. */
0275 int ndrx_ini_parse_file(FILE* file, ini_handler handler, void* user, void *user2, 
0276         void *user3)
0277 {
0278     return ndrx_ini_parse_stream((ini_reader)fgets, file, handler, user, user2, user3);
0279 }
0280 
0281 /* See documentation in header file. */
0282 int ndrx_ini_parse(const char* filename, ini_handler handler, void* user, 
0283         void *user2, void *user3)
0284 {
0285     FILE* file;
0286     int error;
0287 
0288     file = NDRX_FOPEN(filename, "r");
0289     if (!file)
0290         return -1;
0291     error = ndrx_ini_parse_file(file, handler, user, user2, user3);
0292     NDRX_FCLOSE(file);
0293     return error;
0294 }