1 /**
2  * WinPR: Windows Portable Runtime
3  * .ini config file
4  *
5  * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #include <errno.h>
29 #include <winpr/wtypes.h>
30 #include <winpr/crt.h>
31 #include <winpr/file.h>
32 
33 #include <winpr/ini.h>
34 
35 struct _wIniFileKey
36 {
37 	char* name;
38 	char* value;
39 };
40 typedef struct _wIniFileKey wIniFileKey;
41 
42 struct _wIniFileSection
43 {
44 	char* name;
45 	size_t nKeys;
46 	size_t cKeys;
47 	wIniFileKey** keys;
48 };
49 typedef struct _wIniFileSection wIniFileSection;
50 
51 struct _wIniFile
52 {
53 	FILE* fp;
54 	char* line;
55 	char* nextLine;
56 	size_t lineLength;
57 	char* tokctx;
58 	char* buffer;
59 	char* filename;
60 	BOOL readOnly;
61 	size_t nSections;
62 	size_t cSections;
63 	wIniFileSection** sections;
64 };
65 
IniFile_Load_NextLine(wIniFile * ini,char * str)66 static BOOL IniFile_Load_NextLine(wIniFile* ini, char* str)
67 {
68 	size_t length = 0;
69 	ini->nextLine = strtok_s(str, "\n", &ini->tokctx);
70 
71 	if (ini->nextLine)
72 		length = strlen(ini->nextLine);
73 
74 	if (length > 0)
75 	{
76 		if (ini->nextLine[length - 1] == '\r')
77 		{
78 			ini->nextLine[length - 1] = '\0';
79 			length--;
80 		}
81 
82 		if (length < 1)
83 			ini->nextLine = NULL;
84 	}
85 
86 	return (ini->nextLine) ? TRUE : FALSE;
87 }
88 
IniFile_Load_String(wIniFile * ini,const char * iniString)89 static BOOL IniFile_Load_String(wIniFile* ini, const char* iniString)
90 {
91 	size_t fileSize;
92 	if (!ini || !iniString)
93 		return FALSE;
94 
95 	ini->line = NULL;
96 	ini->nextLine = NULL;
97 	ini->buffer = NULL;
98 	fileSize = strlen(iniString);
99 
100 	if (fileSize < 1)
101 		return FALSE;
102 
103 	ini->buffer = (char*)malloc(fileSize + 2);
104 
105 	if (!ini->buffer)
106 		return FALSE;
107 
108 	CopyMemory(ini->buffer, iniString, fileSize);
109 	ini->buffer[fileSize] = '\n';
110 	ini->buffer[fileSize + 1] = '\0';
111 	IniFile_Load_NextLine(ini, ini->buffer);
112 	return TRUE;
113 }
114 
IniFile_Open_File(wIniFile * ini,const char * filename)115 static BOOL IniFile_Open_File(wIniFile* ini, const char* filename)
116 {
117 	if (!ini || !filename)
118 		return FALSE;
119 
120 	if (ini->readOnly)
121 		ini->fp = winpr_fopen(filename, "rb");
122 	else
123 		ini->fp = winpr_fopen(filename, "w+b");
124 
125 	if (!ini->fp)
126 		return FALSE;
127 
128 	return TRUE;
129 }
130 
IniFile_Load_File(wIniFile * ini,const char * filename)131 static BOOL IniFile_Load_File(wIniFile* ini, const char* filename)
132 {
133 	INT64 fileSize;
134 
135 	if (!IniFile_Open_File(ini, filename))
136 		return FALSE;
137 
138 	if (_fseeki64(ini->fp, 0, SEEK_END) < 0)
139 		goto out_file;
140 
141 	fileSize = _ftelli64(ini->fp);
142 
143 	if (fileSize < 0)
144 		goto out_file;
145 
146 	if (_fseeki64(ini->fp, 0, SEEK_SET) < 0)
147 		goto out_file;
148 
149 	ini->line = NULL;
150 	ini->nextLine = NULL;
151 	ini->buffer = NULL;
152 
153 	if (fileSize < 1)
154 		goto out_file;
155 
156 	ini->buffer = (char*)malloc((size_t)fileSize + 2);
157 
158 	if (!ini->buffer)
159 		goto out_file;
160 
161 	if (fread(ini->buffer, (size_t)fileSize, 1, ini->fp) != 1)
162 		goto out_buffer;
163 
164 	fclose(ini->fp);
165 	ini->fp = NULL;
166 	ini->buffer[fileSize] = '\n';
167 	ini->buffer[fileSize + 1] = '\0';
168 	IniFile_Load_NextLine(ini, ini->buffer);
169 	return TRUE;
170 out_buffer:
171 	free(ini->buffer);
172 	ini->buffer = NULL;
173 out_file:
174 	fclose(ini->fp);
175 	ini->fp = NULL;
176 	return FALSE;
177 }
178 
IniFile_Load_Finish(wIniFile * ini)179 static void IniFile_Load_Finish(wIniFile* ini)
180 {
181 	if (!ini)
182 		return;
183 
184 	if (ini->buffer)
185 	{
186 		free(ini->buffer);
187 		ini->buffer = NULL;
188 	}
189 }
190 
IniFile_Load_HasNextLine(wIniFile * ini)191 static BOOL IniFile_Load_HasNextLine(wIniFile* ini)
192 {
193 	if (!ini)
194 		return FALSE;
195 
196 	return (ini->nextLine) ? TRUE : FALSE;
197 }
198 
IniFile_Load_GetNextLine(wIniFile * ini)199 static char* IniFile_Load_GetNextLine(wIniFile* ini)
200 {
201 	if (!ini)
202 		return NULL;
203 
204 	ini->line = ini->nextLine;
205 	ini->lineLength = strlen(ini->line);
206 	IniFile_Load_NextLine(ini, NULL);
207 	return ini->line;
208 }
209 
IniFile_Key_Free(wIniFileKey * key)210 static void IniFile_Key_Free(wIniFileKey* key)
211 {
212 	if (!key)
213 		return;
214 
215 	free(key->name);
216 	free(key->value);
217 	free(key);
218 }
219 
IniFile_Key_New(const char * name,const char * value)220 static wIniFileKey* IniFile_Key_New(const char* name, const char* value)
221 {
222 	wIniFileKey* key;
223 
224 	if (!name || !value)
225 		return NULL;
226 
227 	key = malloc(sizeof(wIniFileKey));
228 
229 	if (key)
230 	{
231 		key->name = _strdup(name);
232 		key->value = _strdup(value);
233 
234 		if (!key->name || !key->value)
235 		{
236 			IniFile_Key_Free(key);
237 			return NULL;
238 		}
239 	}
240 
241 	return key;
242 }
243 
IniFile_Section_New(const char * name)244 static wIniFileSection* IniFile_Section_New(const char* name)
245 {
246 	wIniFileSection* section;
247 
248 	if (!name)
249 		return NULL;
250 
251 	section = malloc(sizeof(wIniFileSection));
252 
253 	if (!section)
254 		return NULL;
255 
256 	section->name = _strdup(name);
257 
258 	if (!section->name)
259 	{
260 		free(section);
261 		return NULL;
262 	}
263 
264 	section->nKeys = 0;
265 	section->cKeys = 64;
266 	section->keys = (wIniFileKey**)calloc(section->cKeys, sizeof(wIniFileKey*));
267 
268 	if (!section->keys)
269 	{
270 		free(section->name);
271 		free(section);
272 		return NULL;
273 	}
274 
275 	return section;
276 }
277 
IniFile_Section_Free(wIniFileSection * section)278 static void IniFile_Section_Free(wIniFileSection* section)
279 {
280 	size_t index;
281 
282 	if (!section)
283 		return;
284 
285 	free(section->name);
286 
287 	for (index = 0; index < section->nKeys; index++)
288 	{
289 		IniFile_Key_Free(section->keys[index]);
290 	}
291 
292 	free(section->keys);
293 	free(section);
294 }
295 
IniFile_GetSection(wIniFile * ini,const char * name)296 static wIniFileSection* IniFile_GetSection(wIniFile* ini, const char* name)
297 {
298 	size_t index;
299 	wIniFileSection* section = NULL;
300 
301 	if (!ini || !name)
302 		return NULL;
303 
304 	for (index = 0; index < ini->nSections; index++)
305 	{
306 		if (_stricmp(name, ini->sections[index]->name) == 0)
307 		{
308 			section = ini->sections[index];
309 			break;
310 		}
311 	}
312 
313 	return section;
314 }
315 
IniFile_AddSection(wIniFile * ini,const char * name)316 static wIniFileSection* IniFile_AddSection(wIniFile* ini, const char* name)
317 {
318 	wIniFileSection* section;
319 
320 	if (!ini || !name)
321 		return NULL;
322 
323 	section = IniFile_GetSection(ini, name);
324 
325 	if (!section)
326 	{
327 		if ((ini->nSections + 1) >= (ini->cSections))
328 		{
329 			size_t new_size;
330 			wIniFileSection** new_sect;
331 			new_size = ini->cSections * 2;
332 			new_sect =
333 			    (wIniFileSection**)realloc(ini->sections, sizeof(wIniFileSection*) * new_size);
334 
335 			if (!new_sect)
336 				return NULL;
337 
338 			ini->cSections = new_size;
339 			ini->sections = new_sect;
340 		}
341 
342 		section = IniFile_Section_New(name);
343 		ini->sections[ini->nSections] = section;
344 		ini->nSections++;
345 	}
346 
347 	return section;
348 }
349 
IniFile_GetKey(wIniFile * ini,wIniFileSection * section,const char * name)350 static wIniFileKey* IniFile_GetKey(wIniFile* ini, wIniFileSection* section, const char* name)
351 {
352 	size_t index;
353 	wIniFileKey* key = NULL;
354 
355 	if (!ini || !section || !name)
356 		return NULL;
357 
358 	for (index = 0; index < section->nKeys; index++)
359 	{
360 		if (_stricmp(name, section->keys[index]->name) == 0)
361 		{
362 			key = section->keys[index];
363 			break;
364 		}
365 	}
366 
367 	return key;
368 }
369 
IniFile_AddKey(wIniFile * ini,wIniFileSection * section,const char * name,const char * value)370 static wIniFileKey* IniFile_AddKey(wIniFile* ini, wIniFileSection* section, const char* name,
371                                    const char* value)
372 {
373 	wIniFileKey* key;
374 
375 	if (!section || !name || !value)
376 		return NULL;
377 
378 	key = IniFile_GetKey(ini, section, name);
379 
380 	if (!key)
381 	{
382 		if ((section->nKeys + 1) >= (section->cKeys))
383 		{
384 			size_t new_size;
385 			wIniFileKey** new_key;
386 			new_size = section->cKeys * 2;
387 			new_key = (wIniFileKey**)realloc(section->keys, sizeof(wIniFileKey*) * new_size);
388 
389 			if (!new_key)
390 				return NULL;
391 
392 			section->cKeys = new_size;
393 			section->keys = new_key;
394 		}
395 
396 		key = IniFile_Key_New(name, value);
397 
398 		if (!key)
399 			return NULL;
400 
401 		section->keys[section->nKeys] = key;
402 		section->nKeys++;
403 	}
404 	else
405 	{
406 		free(key->value);
407 		key->value = _strdup(value);
408 
409 		if (!key->value)
410 			return NULL;
411 	}
412 
413 	return key;
414 }
415 
IniFile_Load(wIniFile * ini)416 static int IniFile_Load(wIniFile* ini)
417 {
418 	char* line;
419 	char* name;
420 	char* value;
421 	char* separator;
422 	char *beg, *end;
423 	wIniFileSection* section = NULL;
424 
425 	if (!ini)
426 		return -1;
427 
428 	while (IniFile_Load_HasNextLine(ini))
429 	{
430 		line = IniFile_Load_GetNextLine(ini);
431 
432 		if (line[0] == ';')
433 			continue;
434 
435 		if (line[0] == '[')
436 		{
437 			beg = &line[1];
438 			end = strchr(line, ']');
439 
440 			if (!end)
441 				return -1;
442 
443 			*end = '\0';
444 			IniFile_AddSection(ini, beg);
445 			section = ini->sections[ini->nSections - 1];
446 		}
447 		else
448 		{
449 			separator = strchr(line, '=');
450 
451 			if (separator == NULL)
452 				return -1;
453 
454 			end = separator;
455 
456 			while ((&end[-1] > line) && ((end[-1] == ' ') || (end[-1] == '\t')))
457 				end--;
458 
459 			*end = '\0';
460 			name = line;
461 			beg = separator + 1;
462 
463 			while (*beg && ((*beg == ' ') || (*beg == '\t')))
464 				beg++;
465 
466 			if (*beg == '"')
467 				beg++;
468 
469 			end = &line[ini->lineLength];
470 
471 			while ((end > beg) && ((end[-1] == ' ') || (end[-1] == '\t')))
472 				end--;
473 
474 			if (end[-1] == '"')
475 				end[-1] = '\0';
476 
477 			value = beg;
478 
479 			if (!IniFile_AddKey(ini, section, name, value))
480 				return -1;
481 		}
482 	}
483 
484 	IniFile_Load_Finish(ini);
485 	return 1;
486 }
487 
IniFile_ReadBuffer(wIniFile * ini,const char * buffer)488 int IniFile_ReadBuffer(wIniFile* ini, const char* buffer)
489 {
490 	BOOL status;
491 	if (!ini || !buffer)
492 		return -1;
493 
494 	ini->readOnly = TRUE;
495 	ini->filename = NULL;
496 	status = IniFile_Load_String(ini, buffer);
497 
498 	if (!status)
499 		return -1;
500 
501 	return IniFile_Load(ini);
502 }
503 
IniFile_ReadFile(wIniFile * ini,const char * filename)504 int IniFile_ReadFile(wIniFile* ini, const char* filename)
505 {
506 	ini->readOnly = TRUE;
507 	free(ini->filename);
508 	ini->filename = _strdup(filename);
509 
510 	if (!ini->filename)
511 		return -1;
512 
513 	if (!IniFile_Load_File(ini, filename))
514 		return -1;
515 
516 	return IniFile_Load(ini);
517 }
518 
IniFile_GetSectionNames(wIniFile * ini,int * count)519 char** IniFile_GetSectionNames(wIniFile* ini, int* count)
520 {
521 	char* p;
522 	size_t index;
523 	size_t length;
524 	size_t nameLength;
525 	char** sectionNames;
526 	wIniFileSection* section = NULL;
527 
528 	if (!ini || !count)
529 		return NULL;
530 
531 	if (ini->nSections > INT_MAX)
532 		return NULL;
533 
534 	length = (sizeof(char*) * ini->nSections) + sizeof(char);
535 
536 	for (index = 0; index < ini->nSections; index++)
537 	{
538 		section = ini->sections[index];
539 		nameLength = strlen(section->name);
540 		length += (nameLength + 1);
541 	}
542 
543 	sectionNames = (char**)malloc(length);
544 
545 	if (!sectionNames)
546 		return NULL;
547 
548 	p = (char*)&((BYTE*)sectionNames)[sizeof(char*) * ini->nSections];
549 
550 	for (index = 0; index < ini->nSections; index++)
551 	{
552 		sectionNames[index] = p;
553 		section = ini->sections[index];
554 		nameLength = strlen(section->name);
555 		CopyMemory(p, section->name, nameLength + 1);
556 		p += (nameLength + 1);
557 	}
558 
559 	*p = '\0';
560 	*count = (int)ini->nSections;
561 	return sectionNames;
562 }
563 
IniFile_GetSectionKeyNames(wIniFile * ini,const char * section,int * count)564 char** IniFile_GetSectionKeyNames(wIniFile* ini, const char* section, int* count)
565 {
566 	char* p;
567 	size_t index;
568 	size_t length;
569 	size_t nameLength;
570 	char** keyNames;
571 	wIniFileKey* pKey = NULL;
572 	wIniFileSection* pSection = NULL;
573 
574 	if (!ini || !section || !count)
575 		return NULL;
576 
577 	pSection = IniFile_GetSection(ini, section);
578 
579 	if (!pSection)
580 		return NULL;
581 
582 	if (pSection->nKeys > INT_MAX)
583 		return NULL;
584 
585 	length = (sizeof(char*) * pSection->nKeys) + sizeof(char);
586 
587 	for (index = 0; index < pSection->nKeys; index++)
588 	{
589 		pKey = pSection->keys[index];
590 		nameLength = strlen(pKey->name);
591 		length += (nameLength + 1);
592 	}
593 
594 	keyNames = (char**)malloc(length);
595 
596 	if (!keyNames)
597 		return NULL;
598 
599 	p = (char*)&((BYTE*)keyNames)[sizeof(char*) * pSection->nKeys];
600 
601 	for (index = 0; index < pSection->nKeys; index++)
602 	{
603 		keyNames[index] = p;
604 		pKey = pSection->keys[index];
605 		nameLength = strlen(pKey->name);
606 		CopyMemory(p, pKey->name, nameLength + 1);
607 		p += (nameLength + 1);
608 	}
609 
610 	*p = '\0';
611 	*count = (int)pSection->nKeys;
612 	return keyNames;
613 }
614 
IniFile_GetKeyValueString(wIniFile * ini,const char * section,const char * key)615 const char* IniFile_GetKeyValueString(wIniFile* ini, const char* section, const char* key)
616 {
617 	const char* value = NULL;
618 	wIniFileKey* pKey = NULL;
619 	wIniFileSection* pSection = NULL;
620 	pSection = IniFile_GetSection(ini, section);
621 
622 	if (!pSection)
623 		return NULL;
624 
625 	pKey = IniFile_GetKey(ini, pSection, key);
626 
627 	if (!pKey)
628 		return NULL;
629 
630 	value = (const char*)pKey->value;
631 	return value;
632 }
633 
IniFile_GetKeyValueInt(wIniFile * ini,const char * section,const char * key)634 int IniFile_GetKeyValueInt(wIniFile* ini, const char* section, const char* key)
635 {
636 	int err;
637 	long value = 0;
638 	wIniFileKey* pKey = NULL;
639 	wIniFileSection* pSection = NULL;
640 	pSection = IniFile_GetSection(ini, section);
641 
642 	if (!pSection)
643 		return 0;
644 
645 	pKey = IniFile_GetKey(ini, pSection, key);
646 
647 	if (!pKey)
648 		return 0;
649 
650 	err = errno;
651 	errno = 0;
652 	value = strtol(pKey->value, NULL, 0);
653 	if ((value < INT_MIN) || (value > INT_MAX) || (errno != 0))
654 	{
655 		errno = err;
656 		return 0;
657 	}
658 	return (int)value;
659 }
660 
IniFile_SetKeyValueString(wIniFile * ini,const char * section,const char * key,const char * value)661 int IniFile_SetKeyValueString(wIniFile* ini, const char* section, const char* key,
662                               const char* value)
663 {
664 	wIniFileKey* pKey = NULL;
665 	wIniFileSection* pSection = NULL;
666 	pSection = IniFile_GetSection(ini, section);
667 
668 	if (!pSection)
669 		pSection = IniFile_AddSection(ini, section);
670 
671 	if (!pSection)
672 		return -1;
673 
674 	pKey = IniFile_AddKey(ini, pSection, key, value);
675 
676 	if (!pKey)
677 		return -1;
678 
679 	return 1;
680 }
681 
IniFile_SetKeyValueInt(wIniFile * ini,const char * section,const char * key,int value)682 int IniFile_SetKeyValueInt(wIniFile* ini, const char* section, const char* key, int value)
683 {
684 	char strVal[128];
685 	wIniFileKey* pKey = NULL;
686 	wIniFileSection* pSection = NULL;
687 	sprintf_s(strVal, sizeof(strVal), "%d", value);
688 	pSection = IniFile_GetSection(ini, section);
689 
690 	if (!pSection)
691 		pSection = IniFile_AddSection(ini, section);
692 
693 	if (!pSection)
694 		return -1;
695 
696 	pKey = IniFile_AddKey(ini, pSection, key, strVal);
697 
698 	if (!pKey)
699 		return -1;
700 
701 	return 1;
702 }
703 
IniFile_WriteBuffer(wIniFile * ini)704 char* IniFile_WriteBuffer(wIniFile* ini)
705 {
706 	size_t i, j;
707 	size_t offset;
708 	size_t size;
709 	char* buffer;
710 	wIniFileKey* key;
711 	wIniFileSection* section;
712 	size = 0;
713 
714 	if (!ini)
715 		return NULL;
716 
717 	for (i = 0; i < ini->nSections; i++)
718 	{
719 		section = ini->sections[i];
720 		size += (strlen(section->name) + 3);
721 
722 		for (j = 0; j < section->nKeys; j++)
723 		{
724 			key = section->keys[j];
725 			size += (strlen(key->name) + strlen(key->value) + 2);
726 		}
727 
728 		size += 1;
729 	}
730 
731 	size += 1;
732 	buffer = malloc(size + 1);
733 
734 	if (!buffer)
735 		return NULL;
736 
737 	offset = 0;
738 
739 	for (i = 0; i < ini->nSections; i++)
740 	{
741 		section = ini->sections[i];
742 		sprintf_s(&buffer[offset], size - offset, "[%s]\n", section->name);
743 		offset += (strlen(section->name) + 3);
744 
745 		for (j = 0; j < section->nKeys; j++)
746 		{
747 			key = section->keys[j];
748 			sprintf_s(&buffer[offset], size - offset, "%s=%s\n", key->name, key->value);
749 			offset += (strlen(key->name) + strlen(key->value) + 2);
750 		}
751 
752 		sprintf_s(&buffer[offset], size - offset, "\n");
753 		offset += 1;
754 	}
755 
756 	buffer[offset] = '\0';
757 	return buffer;
758 }
759 
IniFile_WriteFile(wIniFile * ini,const char * filename)760 int IniFile_WriteFile(wIniFile* ini, const char* filename)
761 {
762 	size_t length;
763 	char* buffer;
764 	int ret = 1;
765 	buffer = IniFile_WriteBuffer(ini);
766 
767 	if (!buffer)
768 		return -1;
769 
770 	length = strlen(buffer);
771 	ini->readOnly = FALSE;
772 
773 	if (!filename)
774 		filename = ini->filename;
775 
776 	if (!IniFile_Open_File(ini, filename))
777 	{
778 		free(buffer);
779 		return -1;
780 	}
781 
782 	if (fwrite((void*)buffer, length, 1, ini->fp) != 1)
783 		ret = -1;
784 
785 	fclose(ini->fp);
786 	free(buffer);
787 	return ret;
788 }
789 
IniFile_New(void)790 wIniFile* IniFile_New(void)
791 {
792 	wIniFile* ini = (wIniFile*)calloc(1, sizeof(wIniFile));
793 
794 	if (ini)
795 	{
796 		ini->nSections = 0;
797 		ini->cSections = 64;
798 		ini->sections = (wIniFileSection**)calloc(ini->cSections, sizeof(wIniFileSection*));
799 
800 		if (!ini->sections)
801 		{
802 			free(ini);
803 			return NULL;
804 		}
805 	}
806 
807 	return ini;
808 }
809 
IniFile_Free(wIniFile * ini)810 void IniFile_Free(wIniFile* ini)
811 {
812 	size_t index;
813 
814 	if (!ini)
815 		return;
816 
817 	free(ini->filename);
818 
819 	for (index = 0; index < ini->nSections; index++)
820 		IniFile_Section_Free(ini->sections[index]);
821 
822 	free(ini->sections);
823 	free(ini->buffer);
824 	free(ini);
825 }
826