core/TextParsers.cpp
author David Anderson <dvander@alliedmods.net>
Mon Mar 19 01:44:09 2007 +0000 (2007-03-19)
changeset 647 1e29dcc9ca97
parent 631 76dc2aa558e5
child 653 ef60870067ff
permissions -rw-r--r--
fixed a potential bug with kicking players during authorization
     1 /**
     2  * ===============================================================
     3  * SourceMod (C)2004-2007 AlliedModders LLC.  All rights reserved.
     4  * ===============================================================
     5  *
     6  * This file is not open source and may not be copied without explicit
     7  * written permission of AlliedModders LLC.  This file may not be redistributed 
     8  * in whole or significant part.
     9  * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
    10  *
    11  * Version: $Id$
    12  */
    13 
    14 #include <stdio.h>
    15 #include <ctype.h>
    16 #include <wctype.h>
    17 #include <string.h>
    18 #include <stdlib.h>
    19 #include <assert.h>
    20 #include "TextParsers.h"
    21 #include "ShareSys.h"
    22 
    23 TextParsers g_TextParser;
    24 
    25 static int g_ini_chartable1[255] = {0};
    26 static int g_ws_chartable[255] = {0};
    27 
    28 bool IsWhitespace(const char *stream)
    29 {
    30 	return g_ws_chartable[(unsigned)*stream] == 1;
    31 }
    32 
    33 TextParsers::TextParsers()
    34 {
    35 	g_ini_chartable1[(unsigned)'_'] = 1;
    36 	g_ini_chartable1[(unsigned)'-'] = 1;
    37 	g_ini_chartable1[(unsigned)','] = 1;
    38 	g_ini_chartable1[(unsigned)'+'] = 1;
    39 	g_ini_chartable1[(unsigned)'.'] = 1;
    40 	g_ini_chartable1[(unsigned)'$'] = 1;
    41 	g_ini_chartable1[(unsigned)'?'] = 1;
    42 	g_ini_chartable1[(unsigned)'/'] = 1;
    43 	g_ws_chartable[(unsigned)'\n'] = 1;
    44 	g_ws_chartable[(unsigned)'\v'] = 1;
    45 	g_ws_chartable[(unsigned)'\r'] = 1;
    46 	g_ws_chartable[(unsigned)'\t'] = 1;
    47 	g_ws_chartable[(unsigned)'\f'] = 1;
    48 	g_ws_chartable[(unsigned)' '] = 1;
    49 }
    50 
    51 void TextParsers::OnSourceModAllInitialized()
    52 {
    53 	g_ShareSys.AddInterface(NULL, this);
    54 }
    55 
    56 unsigned int TextParsers::GetUTF8CharBytes(const char *stream)
    57 {
    58 	return _GetUTF8CharBytes(stream);
    59 }
    60 
    61 /**
    62  * Character streams
    63  */
    64 
    65 struct CharStream
    66 {
    67 	const char *curpos;
    68 };
    69 
    70 bool CharStreamReader(void *stream, char *buffer, size_t maxlength, unsigned int *read)
    71 {
    72 	CharStream *srdr = (CharStream *)stream;
    73 
    74 	const char *ptr = srdr->curpos;
    75 	for (size_t i=0; i<maxlength; i++)
    76 	{
    77 		if (*ptr == '\0')
    78 		{
    79 			break;
    80 		}
    81 		*buffer++ = *ptr++;
    82 	}
    83 
    84 	*read = ptr - srdr->curpos;
    85 
    86 	srdr->curpos = ptr;
    87 
    88 	return true;
    89 }
    90 
    91 SMCParseError TextParsers::ParseString_SMC(const char *stream, 
    92 					 ITextListener_SMC *smc,
    93 					 unsigned int *line,
    94 					 unsigned int *col)
    95 {
    96 	CharStream srdr = { stream };
    97 
    98 	return ParseStream_SMC(&srdr, CharStreamReader, smc, line, col);
    99 }
   100 
   101 /**
   102  * File streams
   103  */
   104 
   105 bool FileStreamReader(void *stream, char *buffer, size_t maxlength, unsigned int *read)
   106 {
   107 	size_t num = fread(buffer, 1, maxlength, (FILE *)stream);
   108 
   109 	*read = static_cast<unsigned int>(num);
   110 
   111 	if (num == 0 && feof((FILE *)stream))
   112 	{
   113 		return true;
   114 	}
   115 
   116 	return (ferror((FILE *)stream) == 0);
   117 }
   118 
   119 SMCParseError TextParsers::ParseFile_SMC(const char *file, ITextListener_SMC *smc, unsigned int *line, unsigned int *col)
   120 {
   121 	FILE *fp = fopen(file, "rt");
   122 
   123 	if (!fp)
   124 	{
   125 		return SMCParse_StreamOpen;
   126 	}
   127 
   128 	SMCParseError result = ParseStream_SMC(fp, FileStreamReader, smc, line, col);
   129 
   130 	fclose(fp);
   131 
   132 	return result;
   133 }
   134 
   135 /** 
   136  * Raw parsing of streams with helper functions
   137  */
   138 
   139 struct StringInfo
   140 {
   141 	StringInfo() : quoted(false), ptr(NULL), end(NULL), special(false) { }
   142 	bool quoted;
   143 	char *ptr;
   144 	char *end;
   145 	bool special;
   146 };
   147 
   148 const char *FixupString(StringInfo &data)
   149 {
   150 	if (!data.ptr)
   151 	{
   152 		return NULL;
   153 	}
   154 
   155 	if (data.quoted)
   156 	{
   157 		data.ptr++;
   158 	}
   159 #if defined _DEBUG
   160 	else {
   161 		/* A string will never have beginning whitespace because we ignore it in the stream.
   162 		 * Furthermore, if there is trailing whitespace, the end ptr will point to it, so it is valid
   163 		 * to overwrite!  Lastly, the last character must be whitespace or a comment/invalid character.
   164 		 */
   165  	}
   166 #endif
   167 
   168 	if (data.special)
   169 	{
   170 		//:TODO: this string has special tokens in it, like \, and we must
   171 		//resolve these before passing the string back to the app
   172 	}
   173 
   174 	*(data.end) = '\0';
   175 
   176 	return data.ptr;
   177 }
   178 
   179 const char *rotate(StringInfo info[3])
   180 {
   181 	if (info[2].ptr != NULL)
   182 	{
   183 		return info[2].ptr;
   184 	}
   185 
   186 	if (info[0].ptr != NULL)
   187 	{
   188 		info[2] = info[1];
   189 		info[1] = info[0];
   190 		info[0] = StringInfo();
   191 	}
   192 	
   193 	return NULL;
   194 }
   195 
   196 void scrap(StringInfo info[3])
   197 {
   198 	info[2] = StringInfo();
   199 	info[1] = StringInfo();
   200 	info[0] = StringInfo();
   201 }
   202 
   203 void reloc(StringInfo &data, unsigned int bytes)
   204 {
   205 	if (data.ptr)
   206 	{
   207 		data.ptr -= bytes;
   208 	}
   209 	if (data.end)
   210 	{
   211 		data.end -= bytes;
   212 	}
   213 }
   214 
   215 char *lowstring(StringInfo info[3])
   216 {
   217 	for (int i=2; i>=0; i--)
   218 	{
   219 		if (info[i].ptr)
   220 		{
   221 			return info[i].ptr;
   222 		}
   223 	}
   224 
   225 	return NULL;
   226 }
   227 
   228 SMCParseError TextParsers::ParseStream_SMC(void *stream, 
   229 								   STREAMREADER srdr, 
   230 								   ITextListener_SMC *smc, 
   231 								   unsigned int *line, 
   232 								   unsigned int *col)
   233 {
   234 	char in_buf[4096];
   235 	char *reparse_point = NULL;
   236 	char *parse_point = in_buf;
   237 	char *line_begin = in_buf;
   238 	unsigned int read;
   239 	unsigned int curline = 1;
   240 	unsigned int curtok = 0;
   241 	unsigned int curlevel = 0;
   242 	bool in_quote = false;
   243 	bool ignoring = false;
   244 	bool eol_comment = false;
   245 	bool ml_comment = false;
   246 	unsigned int i;
   247 	SMCParseError err = SMCParse_Okay;
   248 	SMCParseResult res;
   249 	char c;
   250 
   251 	StringInfo strings[3];
   252 	StringInfo emptystring;
   253 
   254 	smc->ReadSMC_ParseStart();
   255 
   256 	while (srdr(stream, parse_point, sizeof(in_buf) - (parse_point - line_begin) - 1, &read))
   257 	{
   258 		if (!read)
   259 		{
   260 			break;
   261 		}
   262 
   263 		/* :TODO: do this outside of the main loop somehow
   264 		 * This checks for BOM markings
   265 		 */
   266 		if (curline == 1 && 
   267 			in_buf[0] == (char)0xEF && 
   268 			in_buf[1] == (char)0xBB && 
   269 			in_buf[2] == (char)0xBF)
   270 		{
   271 			parse_point = &in_buf[3];
   272 		}
   273 
   274 		if (reparse_point)
   275 		{
   276 			read += (parse_point - reparse_point);
   277 			parse_point = reparse_point;
   278 			reparse_point = NULL;
   279 		}
   280 
   281 		for (i=0; i<read; i++)
   282 		{
   283 			c = parse_point[i];
   284 			if (c == '\n')
   285 			{
   286 				/* If we got a newline, there's a lot of things that could have happened in the interim.
   287 				 * First, let's make sure the staged strings are rotated.
   288 				 */
   289 				if (strings[0].ptr)
   290 				{
   291 					strings[0].end = &parse_point[i];
   292 					if (rotate(strings) != NULL)
   293 					{
   294 						err = SMCParse_InvalidTokens;
   295 						goto failed;
   296 					}
   297 				}
   298 
   299 				/* Next, let's clear some line-based values that may no longer have meaning */
   300 				eol_comment = false;
   301 				in_quote = false;
   302 				if (ignoring && !ml_comment)
   303 				{
   304 					ignoring = false;
   305 				}
   306 
   307 				/* Pass the raw line onto the listener */
   308 				if ((res=smc->ReadSMC_RawLine(line_begin, curline)) != SMCParse_Continue)
   309 				{
   310 					err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
   311 					goto failed;
   312 				}
   313 
   314 				/* Now we check the sanity of our staged strings! */
   315 				if (strings[2].ptr)
   316 				{
   317 					if (!curlevel)
   318 					{
   319 						err = SMCParse_InvalidProperty1;
   320 						goto failed;
   321 					}
   322 					/* Assume the next string is a property and pass the info on. */
   323 					if ((res=smc->ReadSMC_KeyValue(
   324 						FixupString(strings[2]),
   325 						FixupString(strings[1]),
   326 						strings[2].quoted,
   327 						strings[1].quoted)) != SMCParse_Continue)
   328 					{
   329 						err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
   330 						goto failed;
   331 					}
   332 					scrap(strings);
   333 				}
   334 
   335 				/* Change the states for the next line */
   336 				curtok = 0;
   337 				curline++;
   338 				line_begin = &parse_point[i+1];		//Note: safe because this gets relocated later
   339 			} else if (ignoring) {
   340 				if (in_quote)
   341 				{
   342 					/* If i was 0, this case is impossible due to reparsing */
   343 					if ((i != 0) && c == '"' && parse_point[i-1] != '\\')
   344 					{
   345 						/* If we reached a quote in an ignore phase,
   346 						 * we're staging a string and we must rotate it out.
   347 						 */
   348 						in_quote = false;
   349 						ignoring = false;
   350 						/* Set our info */
   351 						strings[0].end = &parse_point[i];
   352 						strings[0].quoted = true;
   353 						if (rotate(strings) != NULL)
   354 						{
   355 							/* If we rotated too many strings, there was too much crap on one line */
   356 							err = SMCParse_InvalidTokens;
   357 							goto failed;
   358 						}
   359 					} else if (c == '\\' && i == (read - 1)) {
   360 						strings[0].special = true;
   361 						reparse_point = &parse_point[i];
   362 						break;
   363 					}
   364 				} else if (ml_comment) {
   365 					if (c == '*')
   366 					{
   367 						/* Check if we need to get more input first */
   368 						if (i == read - 1)
   369 						{
   370 							reparse_point = &parse_point[i];
   371 							break;
   372 						}
   373 						if (parse_point[i+1] == '/')
   374 						{
   375 							ml_comment = false;
   376 							ignoring = false;
   377 							/* We should not be staging anything right now. */
   378 							assert(strings[0].ptr == NULL);
   379 							/* Advance the input stream so we don't choke on this token */
   380 							i++;
   381 							curtok++;
   382 						}
   383 					}
   384 				}
   385 			} else {
   386 				/* Check if we're whitespace or not */
   387 				if (!g_ws_chartable[(unsigned)c])
   388 				{
   389 					bool restage = false;
   390 					/* Check various special tokens:
   391 					 * ;
   392 					 * //
   393 					 * / *
   394 					 * {
   395 					 * }
   396 					 */
   397 					if (c == ';' || c == '/')
   398 					{
   399 						/* If it's a line-based comment (that is, ; or //)
   400 						 * we will need to scrap everything until the end of the line.
   401 						 */
   402 						if (c == '/')
   403 						{
   404 							if (i == read - 1)
   405 							{
   406 								/* If we reached the end of the look-ahead, we need to re-check our input.
   407 								 * Breaking out will force this to be the new reparse point!
   408 								 */
   409 								reparse_point = &reparse_point[i];
   410 								break;
   411 							}
   412 							if (parse_point[i + 1] == '/')
   413 							{
   414 								/* standard comment */
   415 								ignoring = true;
   416 								eol_comment = true;
   417 								restage = true;
   418 							} else if (parse_point[i+1] == '*') {
   419 								/* inline comment - start ignoring */
   420 								ignoring = true;
   421 								ml_comment = true;
   422 								/* yes, we restage, meaning that:
   423 								 * STR/ *stuff* /ING  (space because ml comments don't nest in C++)
   424 								 * will not generate 'STRING', but rather 'STR' and 'ING'.
   425 								 * This should be a rare occurrence and is done here for convenience.
   426 								 */
   427 								restage = true;
   428 							}
   429 						} else {
   430 							ignoring = true;
   431 							eol_comment = true;
   432 							restage = true;
   433 						}
   434 					} else if (c == '{') {
   435 						/* If we are staging a string, we must rotate here */
   436 						if (strings[0].ptr)
   437 						{
   438 							/* We have unacceptable tokens on this line */
   439 							if (rotate(strings) != NULL)
   440 							{
   441 								err = SMCParse_InvalidSection1;
   442 								goto failed;
   443 							}
   444 						}
   445 						/* Sections must always be alone */
   446 						if (strings[2].ptr != NULL)
   447 						{
   448 							err = SMCParse_InvalidSection1;
   449 							goto failed;
   450 						} else if (strings[1].ptr == NULL) {
   451 							err = SMCParse_InvalidSection2;
   452 							goto failed;
   453 						}
   454 						if ((res=smc->ReadSMC_NewSection(FixupString(strings[1]), strings[1].quoted))
   455 							!= SMCParse_Continue)
   456 						{
   457 							err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
   458 							goto failed;
   459 						}
   460 						strings[1] = emptystring;
   461 						curlevel++;
   462 					} else if (c == '}') {
   463 						/* Unlike our matching friend, this can be on the same line as something prior */
   464 						if (rotate(strings) != NULL)
   465 						{
   466 							err = SMCParse_InvalidSection3;
   467 							goto failed;
   468 						}
   469 						if (strings[2].ptr)
   470 						{
   471 							if (!curlevel)
   472 							{
   473 								err = SMCParse_InvalidProperty1;
   474 								goto failed;
   475 							}
   476 							if ((res=smc->ReadSMC_KeyValue(
   477 											FixupString(strings[2]),
   478 											FixupString(strings[1]),
   479 											strings[2].quoted,
   480 											strings[1].quoted))
   481 								!= SMCParse_Continue)
   482 							{
   483 								err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
   484 								goto failed;
   485 							}
   486 						} else if (strings[1].ptr) {
   487 							err = SMCParse_InvalidSection3;
   488 							goto failed;
   489 						} else if (!curlevel) {
   490 							err = SMCParse_InvalidSection4;
   491 							goto failed;
   492 						}
   493 						/* Now it's safe to leave the section */
   494 						scrap(strings);
   495 						if ((res=smc->ReadSMC_LeavingSection()) != SMCParse_Continue)
   496 						{
   497 							err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
   498 							goto failed;
   499 						}
   500 						curlevel--;
   501 					} else if (c == '"') {
   502 						/* If we get a quote mark, we always restage, but we need to do it beforehand */
   503 						if (strings[0].ptr)
   504 						{
   505 							strings[0].end = &parse_point[i];
   506 							if (rotate(strings) != NULL)
   507 							{
   508 								err = SMCParse_InvalidTokens;
   509 								goto failed;
   510 							}
   511 						}
   512 						strings[0].ptr = &parse_point[i];
   513 						in_quote = true;
   514 						ignoring = true;
   515 					} else if (!strings[0].ptr) {
   516 						/* If we have no string, we must start one */
   517 						strings[0].ptr = &parse_point[i];
   518 					}
   519 					if (restage && strings[0].ptr)
   520 					{
   521 						strings[0].end = &parse_point[i];
   522 						if (rotate(strings) != NULL)
   523 						{
   524 							err = SMCParse_InvalidTokens;
   525 							goto failed;
   526 						}
   527 					}
   528 				} else {
   529 					/* If we're eating a string and get whitespace, we need to restage.
   530 					 * (Note that if we are quoted, this is being ignored)
   531 					 */
   532 					if (strings[0].ptr)
   533 					{
   534 						/*
   535 						 * The specification says the second string in a pair does not need to be quoted.
   536 						 * Thus, we check if there's already a string on the stack.
   537 						 * If there's a newline, we always rotate so the newline has an empty starter.
   538 						 */
   539 						if (!strings[1].ptr)
   540 						{
   541 							/* There's no string, so we must move this one down and eat up another */
   542 							strings[0].end = &parse_point[i];
   543 							rotate(strings);
   544 						} else if (!strings[1].quoted) {
   545 							err = SMCParse_InvalidTokens;
   546 							goto failed;
   547 						}
   548 					}
   549 				}
   550 			}
   551 
   552 			/* Advance which token we're on */
   553 			curtok++;
   554 		}
   555 
   556 		if (line_begin != in_buf)
   557 		{
   558 			/* The line buffer has advanced, so it's safe to copy N bytes back to the beginning.
   559 			 * What's N?  N is the lowest point we're currently relying on.
   560 			 */
   561 			char *stage = lowstring(strings);
   562 			if (!stage || stage > line_begin)
   563 			{
   564 				stage = line_begin;
   565 			}
   566 			unsigned int bytes = read - (stage - parse_point);
   567 
   568 			/* It is now safe to delete everything before the staged point */
   569 			memmove(in_buf, stage, bytes);
   570 
   571 			/* Calculate the number of bytes in the new buffer */
   572 			bytes = stage - in_buf;
   573 			/* Relocate all the cached pointers to our new base */
   574 			line_begin -= bytes;
   575 			reloc(strings[0], bytes);
   576 			reloc(strings[1], bytes);
   577 			reloc(strings[2], bytes);
   578 			if (reparse_point)
   579 			{
   580 				reparse_point -= bytes;
   581 			}
   582 			if (parse_point)
   583 			{
   584 				parse_point = &parse_point[read];
   585 				parse_point -= bytes;
   586 			}
   587 		} else if (read == sizeof(in_buf) - 1) {
   588 			err = SMCParse_TokenOverflow;
   589 			goto failed;
   590 		}
   591 	}
   592 
   593 	/* If we're done parsing and there are tokens left over... */
   594 	if (curlevel)
   595 	{
   596 		err = SMCParse_InvalidSection5;
   597 		goto failed;
   598 	} else if (strings[0].ptr || strings[1].ptr) {
   599 		err = SMCParse_InvalidTokens;
   600 		goto failed;
   601 	}
   602 	
   603 	smc->ReadSMC_ParseEnd(false, false);
   604 
   605 	return SMCParse_Okay;
   606 
   607 failed:
   608 	if (line)
   609 	{
   610 		*line = curline;
   611 	}
   612 
   613 	smc->ReadSMC_ParseEnd(true, (err == SMCParse_Custom));
   614 
   615 	if (col)
   616 	{
   617 		*col = curtok;
   618 	}
   619 
   620 	return err;
   621 }
   622 
   623 
   624 /**
   625  * INI parser 
   626  */
   627 
   628 bool TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col)
   629 {
   630 	FILE *fp = fopen(file, "rt");
   631 	unsigned int curline = 0;
   632 	unsigned int curtok;
   633 	size_t len;
   634 
   635 	if (!fp)
   636 	{
   637 		if (line)
   638 		{
   639 			*line = 0;
   640 		}
   641 
   642 		return false;
   643 	}
   644 
   645 	char buffer[2048];
   646 	char *ptr, *save_ptr;
   647 	bool in_quote;
   648 
   649 	while (!feof(fp))
   650 	{
   651 		curline++;
   652 		curtok = 0;
   653 		buffer[0] = '\0';
   654 		if (fgets(buffer, sizeof(buffer), fp) == NULL)
   655 		{
   656 			break;
   657 		}
   658 
   659 		//:TODO: this will only run once, so find a nice way to move it out of the while loop
   660 		/* If this is the first line, check the first three bytes for BOM */
   661 		if (curline == 1 && 
   662 			buffer[0] == (char)0xEF && 
   663 			buffer[1] == (char)0xBB && 
   664 			buffer[2] == (char)0xBF)
   665 		{
   666 			/* We have a UTF-8 marked file... skip these bytes */
   667 			ptr = &buffer[3];
   668 		} else {
   669 			ptr = buffer;
   670 		}
   671 
   672 		/***************************************************
   673 		 * We preprocess the string before parsing tokens! *
   674 		 ***************************************************/
   675 
   676 		/* First strip beginning whitespace */
   677 		while (*ptr != '\0' && g_ws_chartable[(unsigned)*ptr] != 0)
   678 		{
   679 			ptr++;
   680 		}
   681 
   682 		len = strlen(ptr);
   683 
   684 		if (!len)
   685 		{
   686 			continue;
   687 		}
   688 
   689 		/* Now search for comment characters */
   690 		in_quote = false;
   691 		save_ptr = ptr;
   692 		for (size_t i=0; i<len; i++,ptr++)
   693 		{
   694 			if (!in_quote)
   695 			{
   696 				switch (*ptr)
   697 				{
   698 				case '"':
   699 					{
   700 						in_quote = true;
   701 						break;
   702 					}
   703 				case ';':
   704 					{
   705 						/* Stop the loop */
   706 						len = i;
   707 						/* Terminate the string here */
   708 						*ptr = '\0';
   709 						break;
   710 					}
   711 				}
   712 			} else {
   713 				if (*ptr == '"')
   714 				{
   715 					in_quote = false;
   716 				}
   717 			}
   718 		}
   719 
   720 		if (!len)
   721 		{
   722 			continue;
   723 		}
   724 
   725 		ptr = save_ptr;
   726 
   727 		/* Lastly, strip ending whitespace off */
   728 		for (size_t i=len-1; i>=0 && i<len; i--)
   729 		{
   730 			if (g_ws_chartable[(unsigned)ptr[i]])
   731 			{
   732 				ptr[i] = '\0';
   733 				len--;
   734 			} else {
   735 				break;
   736 			}
   737 		}
   738 
   739 		if (!len)
   740 		{
   741 			continue;
   742 		}
   743 
   744 		if (!ini_listener->ReadINI_RawLine(ptr, &curtok))
   745 		{
   746 			goto event_failed;
   747 		}
   748 
   749 		if (*ptr == '[')
   750 		{
   751 			bool invalid_tokens = false;
   752 			bool got_bracket = false;
   753 			bool extra_tokens = false;
   754 			char c;
   755 			bool alnum;
   756 			wchar_t wc;
   757 
   758 			for (size_t i=1; i<len; i++)
   759 			{
   760 				c = ptr[i];
   761 				alnum = false;
   762 
   763 				if (c & (1<<7))
   764 				{
   765 					if (mbtowc(&wc, &ptr[i], len-i) != -1)
   766 					{
   767 						alnum = (iswalnum(wc) != 0);
   768 						i += _GetUTF8CharBytes(&ptr[i]) - 1;
   769 					}
   770 				} else {
   771 					alnum = (isalnum(c) != 0) || (g_ini_chartable1[(unsigned)c] != 0);
   772 				}
   773 				if (!alnum)
   774 				{
   775 					/* First check - is this a bracket? */
   776 					if (c == ']')
   777 					{
   778 						/* Yes! */
   779 						got_bracket = true;
   780 						/* If this isn't the last character... */
   781 						if (i != len - 1)
   782 						{
   783 							extra_tokens = true;
   784 						}
   785 						/* terminate */
   786 						ptr[i] = '\0';
   787 						break;
   788 					} else {
   789 						/* n...No! Continue copying. */
   790 						invalid_tokens = true;
   791 					}
   792 				}
   793 			}
   794 
   795 			/* Tell the handler */
   796 			if (!ini_listener->ReadINI_NewSection(&ptr[1], invalid_tokens, got_bracket, extra_tokens, &curtok))
   797 			{
   798 				goto event_failed;
   799 			}
   800 		} else {
   801 			char *key_ptr = ptr;
   802 			char *val_ptr = NULL;
   803 			char c;
   804 			size_t first_space = 0;
   805 			bool invalid_tokens = false;
   806 			bool equal_token = false;
   807 			bool quotes = false;
   808 			bool alnum;
   809 			wchar_t wc;
   810 
   811 			for (size_t i=0; i<len; i++)
   812 			{
   813 				c = ptr[i];
   814 				alnum = false;
   815 				/* is this an invalid char? */
   816 				if (c & (1<<7))
   817 				{
   818 					if (mbtowc(&wc, &ptr[i], len-i) != -1)
   819 					{
   820 						alnum = (iswalnum(wc) != 0);
   821 						i += _GetUTF8CharBytes(&ptr[i]) - 1;
   822 					}
   823 				} else {
   824 					alnum = (isalnum(c) != 0) || (g_ini_chartable1[(unsigned)c] != 0);
   825 				}
   826 
   827 				if (!alnum)
   828 				{
   829 					if (g_ws_chartable[(unsigned)c])
   830 					{
   831 						/* if it's a space, keep track of the first occurring space */
   832 						if (!first_space)
   833 						{
   834 							first_space = i;
   835 						}
   836 					} else {
   837 						if (c == '=')
   838 						{
   839 							/* if it's an equal sign, we're done with the key */
   840 							if (first_space)
   841 							{
   842 								/* remove excess whitespace */
   843 								key_ptr[first_space] = '\0';
   844 							} else {
   845 								/* remove the equal sign */
   846 								key_ptr[i] = '\0';
   847 							}
   848 							if (ptr[++i] != '\0')
   849 							{
   850 								/* If this isn't the end, set next pointer */
   851 								val_ptr = &ptr[i];
   852 							}
   853 							equal_token = true;
   854 							break;
   855 						} else {
   856 							/* Mark that we got something invalid! */
   857 							invalid_tokens = true;
   858 							first_space = 0;
   859 						}
   860 					}
   861 				}
   862 			}
   863 
   864 			/* Now we need to parse the value, if any */
   865 			if (val_ptr)
   866 			{
   867 				/* eat up spaces! there shouldn't be any h*/
   868 				while ((*val_ptr != '\0') && g_ws_chartable[(unsigned)*val_ptr] != 0)
   869 				{
   870 					val_ptr++;
   871 				}
   872 				if (*val_ptr == '\0')
   873 				{
   874 					val_ptr = NULL;
   875 					goto skip_value;
   876 				}
   877 				/* Do we have an initial quote? If so, the parsing rules change! */
   878 				if (*val_ptr == '"' && *val_ptr != '\0')
   879 				{
   880 					len = strlen(val_ptr);
   881 					if (val_ptr[len-1] == '"')
   882 					{
   883 						/* Strip quotes! */
   884 						val_ptr[--len] = '\0';
   885 						val_ptr++;
   886 						quotes = true;
   887 					}
   888 				}
   889 			}
   890 skip_value:
   891 			/* We're done! */
   892 			curtok = val_ptr - buffer;
   893 			if (!ini_listener->ReadINI_KeyValue(key_ptr, val_ptr, invalid_tokens, equal_token, quotes, &curtok))
   894 			{
   895 				curtok = 0;
   896 				goto event_failed;
   897 			}
   898 		}
   899 	}
   900 
   901 	if (line)
   902 	{
   903 		*line = curline;
   904 	}
   905 
   906 	fclose(fp);
   907 
   908 	return true;
   909 
   910 event_failed:
   911 	if (line)
   912 	{
   913 		*line = curline;
   914 	}
   915 
   916 	if (col)
   917 	{
   918 		*col = curtok;
   919 	}
   920 
   921 	fclose(fp);
   922 
   923 	return false;
   924 }
   925 
   926 const char *TextParsers::GetSMCErrorString(SMCParseError err)
   927 {
   928 	static const char *s_errors[] = 
   929 	{
   930 		NULL,
   931 		"Stream failed to open",
   932 		"Stream returned read error",
   933 		NULL,
   934 		"Un-quoted section has invalid tokens",
   935 		"Section declared without header",
   936 		"Section declared with unknown tokens",
   937 		"Section ending without a matching section beginning",
   938 		"Section beginning without a matching ending",
   939 		"Line contained too many invalid tokens",
   940 		"Token buffer overflowed",
   941 		"A property was declared outside of a section",
   942 	};
   943 
   944 	if (err < SMCParse_Okay || err > SMCParse_InvalidProperty1)
   945 	{
   946 		return NULL;
   947 	}
   948 
   949 	return s_errors[err];
   950 }