summaryrefslogblamecommitdiffstats
path: root/src/server/ini.c
blob: c796d5cf44c84f282f0eaed59304583f76fc5c42 (plain) (tree)
1
2
3
4
5
6
7
8
9








                                                                                

                


                   

































































































                                                                                                        










                                                                      


















































                                                                                                             
/* inih -- simple .INI file parser

 inih is released under the New BSD license (see LICENSE.txt). Go to the project
 home page for more info:

 http://code.google.com/p/inih/

 */

#include "ini.h"

#include <ctype.h>
#include <string.h>

#if !INI_USE_STACK
#include <stdlib.h>
#endif

#define MAX_SECTION 50
#define MAX_NAME 50

/* Strip whitespace chars off end of given string, in place. Return s. */
static char* rstrip(char* s)
{
	char* p = s + strlen( s );
	while ( p > s && isspace((unsigned char)(*--p)))
		*p = '\0';
	return s;
}

/* Return pointer to first non-whitespace char in given string. */
static char* lskip(const char* s)
{
	while ( *s && isspace((unsigned char)(*s)))
		s++;
	return (char*)s;
}

/* Return pointer to first char c or ';' comment in given string, or pointer to
 null at end of string if neither found. ';' must be prefixed by a whitespace
 character to register as a comment. */
static char* find_char_or_comment(const char* s, char c)
{
	int was_whitespace = 0;
	while ( *s && *s != c && !(was_whitespace && *s == ';') ) {
		was_whitespace = isspace((unsigned char)(*s));
		s++;
	}
	return (char*)s;
}

/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
static char* strncpy0(char* dest, const char* src, size_t size)
{
	strncpy( dest, src, size );
	dest[size - 1] = '\0';
	return dest;
}

/* See documentation in header file. */
int ini_parse_file(FILE* file, int (*handler)(void*, const char*, const char*, const char*), void* user)
{
	/* Uses a fair bit of stack (use heap instead if you need to) */
#if INI_USE_STACK
	char line[INI_MAX_LINE];
#else
	char* line;
#endif
	char section[MAX_SECTION] = "";
	char prev_name[MAX_NAME] = "";

	char* start;
	char* end;
	char* name;
	char* value;
	int lineno = 0;
	int error = 0;

#if !INI_USE_STACK
	line = (char*)malloc( INI_MAX_LINE );
	if ( !line ) {
		return -2;
	}
#endif

	/* Scan through file line by line */
	while ( fgets( line, INI_MAX_LINE, file ) != NULL ) {
		lineno++;

		start = line;
#if INI_ALLOW_BOM
		if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
				(unsigned char)start[1] == 0xBB &&
				(unsigned char)start[2] == 0xBF) {
			start += 3;
		}
#endif
		start = lskip( rstrip( start ) );

		if ( *start == ';' || *start == '#' ) {
			/* Per Python ConfigParser, allow '#' comments at start of line */
		}
#if INI_ALLOW_MULTILINE
		else if (*prev_name && *start && start > line) {
			/* Non-black line with leading whitespace, treat as continuation
			 of previous name's value (as per Python ConfigParser). */
			if (!handler(user, section, prev_name, start) && !error)
			error = lineno;
		}
#endif
		else if ( *start == '[' ) {
			/* A "[section]" line */
			int cnt = 0;
			char *f = start, *sstart = start;
			while ( *++f ) {
				if ( *f == '[' ) cnt++;
				if ( *f == ']' ) cnt--;
				if ( cnt < 0 ) {
					sstart = f - 1;
					break;
				}
			}
			end = find_char_or_comment( sstart + 1, ']' );
			if ( *end == ']' ) {
				*end = '\0';
				strncpy0( section, start + 1, sizeof(section) );
				*prev_name = '\0';
			} else if ( !error ) {
				/* No ']' found on section line */
				error = lineno;
			}
		} else if ( *start && *start != ';' ) {
			/* Not a comment, must be a name[=:]value pair */
			end = find_char_or_comment( start, '=' );
			if ( *end != '=' ) {
				end = find_char_or_comment( start, ':' );
			}
			if ( *end == '=' || *end == ':' ) {
				*end = '\0';
				name = rstrip( start );
				value = lskip( end + 1 );
				end = find_char_or_comment( value, '\0' );
				if ( *end == ';' ) *end = '\0';
				rstrip( value );

				/* Valid name[=:]value pair found, call handler */
				strncpy0( prev_name, name, sizeof(prev_name) );
				if ( !handler( user, section, name, value ) && !error ) error = lineno;
			} else if ( !error ) {
				/* No '=' or ':' found on name[=:]value line */
				error = lineno;
			}
		}
	}

#if !INI_USE_STACK
	free( line );
#endif

	return error;
}

/* See documentation in header file. */
int ini_parse(const char* filename, int (*handler)(void*, const char*, const char*, const char*), void* user)
{
	FILE* file;
	int error;

	file = fopen( filename, "r" );
	if ( !file ) return -1;
	error = ini_parse_file( file, handler, user );
	fclose( file );
	return error;
}