Skip to content

Commit 8854c48

Browse files
committed
Add support for escaped quotes, semicolon and hash in quoted values
This commit will fix an inconsistency in the behavior of `iniparser_set()` and `iniparser_load()`. It will also make sure that dumping working dictionary results in an INI file which provides the same dictionary after loading this INI. The inconsistency is: - `iniparser_set()` supports escaped quotes, semicolon and hash in values: ``` "str\"ing" => str"ing "str;ing" => str;ing "str#ing" => str#ing ``` -`iniparser_load()` does not support semicolon and hash in unquoted values: ``` str;ing => str str#ing => str ``` Which is correct, because in this case semicolon and hash indicate that the rest of the line is a comment. - `iniparser_load()` supports quotes in unquoted values ``` str"ing => str"ing ``` - `iniparser_load()` supports semicolon and hash in quoted values: ``` "str;ing" => str;ing "str#ing" => str#ing ``` However, before this commit: - `iniparser_load()` did not support escaped quotes in quoted values: ``` "str\"ing" => str\ ``` And: - `iniparser_dump_ini()` wrote all values without quotes The implemented fix is: - `iniparser_load()` supports escaping iff the value is quoted - `iniparser_dump_ini()` will quote all values and escape '\' and '"' These changes do not break existing test cases. refs #97
1 parent 068b66a commit 8854c48

File tree

3 files changed

+136
-18
lines changed

3 files changed

+136
-18
lines changed

src/iniparser.c

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,26 @@ void iniparser_dump(const dictionary * d, FILE * f)
247247
return ;
248248
}
249249

250+
static void escape_value(char *escaped, char *value) {
251+
char c;
252+
int v = 0;
253+
int e = 0;
254+
255+
if(!escaped || !value)
256+
return;
257+
258+
while((c = value[v]) != '\0') {
259+
if(c == '\\' || c == '"') {
260+
escaped[e] = '\\';
261+
e++;
262+
}
263+
escaped[e] = c;
264+
v++;
265+
e++;
266+
}
267+
escaped[e] = '\0';
268+
}
269+
250270
/*-------------------------------------------------------------------------*/
251271
/**
252272
@brief Save a dictionary to a loadable ini file
@@ -263,6 +283,7 @@ void iniparser_dump_ini(const dictionary * d, FILE * f)
263283
size_t i ;
264284
size_t nsec ;
265285
const char * secname ;
286+
char escaped[ASCIILINESZ+1] = "";
266287

267288
if (d==NULL || f==NULL) return ;
268289

@@ -272,7 +293,8 @@ void iniparser_dump_ini(const dictionary * d, FILE * f)
272293
for (i=0 ; i<d->size ; i++) {
273294
if (d->key[i]==NULL)
274295
continue ;
275-
fprintf(f, "%s = %s\n", d->key[i], d->val[i]);
296+
escape_value(escaped, d->val[i]);
297+
fprintf(f, "%s = \"%s\"\n", d->key[i], escaped);
276298
}
277299
return ;
278300
}
@@ -301,6 +323,7 @@ void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f)
301323
size_t j ;
302324
char keym[ASCIILINESZ+1];
303325
int seclen ;
326+
char escaped[ASCIILINESZ+1] = "";
304327

305328
if (d==NULL || f==NULL) return ;
306329
if (! iniparser_find_entry(d, s)) return ;
@@ -312,10 +335,8 @@ void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f)
312335
if (d->key[j]==NULL)
313336
continue ;
314337
if (!strncmp(d->key[j], keym, seclen+1)) {
315-
fprintf(f,
316-
"%-30s = %s\n",
317-
d->key[j]+seclen+1,
318-
d->val[j] ? d->val[j] : "");
338+
escape_value(escaped, d->val[j]);
339+
fprintf(f, "%-30s = \"%s\"\n", d->key[j]+seclen+1, escaped);
319340
}
320341
}
321342
fprintf(f, "\n");
@@ -642,6 +663,44 @@ void iniparser_unset(dictionary * ini, const char * entry)
642663
dictionary_unset(ini, strlwc(entry, tmp_str, sizeof(tmp_str)));
643664
}
644665

666+
static void parse_quoted_value(char *value, char quote) {
667+
char c;
668+
char *quoted;
669+
int q = 0, v = 0;
670+
int esc = 0;
671+
672+
if(!value)
673+
return;
674+
675+
quoted = xstrdup(value);
676+
677+
if(!quoted) {
678+
iniparser_error_callback("iniparser: memory allocation failure\n");
679+
goto end_of_value;
680+
}
681+
682+
while((c = quoted[q]) != '\0') {
683+
if(!esc) {
684+
if(c == '\\') {
685+
esc = 1;
686+
q++;
687+
continue;
688+
}
689+
690+
if(c == quote) {
691+
goto end_of_value;
692+
}
693+
}
694+
esc = 0;
695+
value[v] = c;
696+
v++;
697+
q++;
698+
}
699+
end_of_value:
700+
value[v] = '\0';
701+
free(quoted);
702+
}
703+
645704
/*-------------------------------------------------------------------------*/
646705
/**
647706
@brief Load a single line from an INI file
@@ -661,6 +720,7 @@ static line_status iniparser_line(
661720
line_status sta ;
662721
char * line = NULL;
663722
size_t len ;
723+
int d_quote;
664724

665725
line = xstrdup(input_line);
666726
len = strstrip(line);
@@ -678,11 +738,15 @@ static line_status iniparser_line(
678738
strstrip(section);
679739
strlwc(section, section, len);
680740
sta = LINE_SECTION ;
681-
} else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
682-
|| sscanf (line, "%[^=] = '%[^\']'", key, value) == 2) {
741+
} else if ((d_quote = sscanf (line, "%[^=] = \"%[^\n]\"", key, value)) == 2
742+
|| sscanf (line, "%[^=] = '%[^\n]'", key, value) == 2) {
683743
/* Usual key=value with quotes, with or without comments */
684744
strstrip(key);
685745
strlwc(key, key, len);
746+
if(d_quote == 2)
747+
parse_quoted_value(value, '"');
748+
else
749+
parse_quoted_value(value, '\'');
686750
/* Don't strip spaces from values surrounded with quotes */
687751
sta = LINE_VALUE ;
688752
} else if (sscanf (line, "%[^=] = %[^;#]", key, value) == 2) {

src/iniparser.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ const char * iniparser_getsecname(const dictionary * d, int n);
8282
8383
This function dumps a given dictionary into a loadable ini file.
8484
It is Ok to specify @c stderr or @c stdout as output files.
85+
86+
All values are quoted these charecters are escaped:
87+
88+
” - the quote character (e.g. “String with ”Quotes””)
89+
90+
\ - the backslash character (e.g. “C:\tmp”)
91+
8592
*/
8693
/*--------------------------------------------------------------------------*/
8794

@@ -382,6 +389,17 @@ int iniparser_find_entry(const dictionary * ini, const char * entry) ;
382389
should not be accessed directly, but through accessor functions
383390
instead.
384391
392+
Iff the value is a quoted string it supports some escape sequences:
393+
394+
” - the quote character (e.g. “String with ”Quotes””)
395+
396+
\ - the backslash character (e.g. “C:\tmp”)
397+
398+
Escape sequences always start with a backslash. Additional escape sequences
399+
might be added in the future. Backslash characters must be escaped. Any other
400+
sequence then those outlined above is invalid and may lead to unpredictable
401+
results.
402+
385403
The returned dictionary must be freed using iniparser_freedict().
386404
*/
387405
/*--------------------------------------------------------------------------*/

test/test_iniparser.c

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,11 +1066,7 @@ void Test_iniparser_quotes(CuTest *tc)
10661066
goto rm_ini;
10671067
}
10681068

1069-
/*
1070-
* iniparser_load() does not support escaped quotes in values if they are
1071-
* quoted
1072-
*/
1073-
CuAssertStrEquals(tc, "str\\", iniparser_getstring(dic,
1069+
CuAssertStrEquals(tc, QUOTES_INI_VAL0, iniparser_getstring(dic,
10741070
QUOTES_INI_SEC ":" QUOTES_INI_ATTR0, NULL));
10751071
/*
10761072
* iniparser_load() supports semicolon and hash in values if they are
@@ -1089,7 +1085,7 @@ void Test_iniparser_quotes(CuTest *tc)
10891085
/*
10901086
* iniparser_load() supports quotes in attributes
10911087
*/
1092-
CuAssertStrEquals(tc, "str\\", iniparser_getstring(dic,
1088+
CuAssertStrEquals(tc, QUOTES_INI_VAL0, iniparser_getstring(dic,
10931089
QUOTES_INI_SEC ":" "str\"ing", NULL));
10941090
/*
10951091
* iniparser_load() does not support semicolon or hash in attributes
@@ -1101,8 +1097,50 @@ void Test_iniparser_quotes(CuTest *tc)
11011097
/*
11021098
* iniparser_load() does support colon in attributes
11031099
*/
1104-
CuAssertStrEquals(tc, "str\\", iniparser_getstring(dic,
1100+
CuAssertStrEquals(tc, QUOTES_INI_VAL0, iniparser_getstring(dic,
11051101
QUOTES_INI_SEC ":" "str:ing", NULL));
1102+
ini = fopen(TMP_INI_PATH, "w+");
1103+
1104+
if (!ini) {
1105+
fprintf(stderr, "iniparser: cannot open %s\n", TMP_INI_PATH);
1106+
goto del_dic;
1107+
}
1108+
1109+
/**
1110+
* Test iniparser_dump_ini()
1111+
*/
1112+
iniparser_dump_ini(dic, ini);
1113+
fclose(ini);
1114+
dictionary_del(dic);
1115+
dic = iniparser_load(TMP_INI_PATH);
1116+
1117+
CuAssertStrEquals(tc, QUOTES_INI_VAL0, iniparser_getstring(dic,
1118+
QUOTES_INI_SEC ":" QUOTES_INI_ATTR0, NULL));
1119+
CuAssertStrEquals(tc, QUOTES_INI_VAL1, iniparser_getstring(dic,
1120+
QUOTES_INI_SEC ":" QUOTES_INI_ATTR1, NULL));
1121+
CuAssertStrEquals(tc, QUOTES_INI_VAL2, iniparser_getstring(dic,
1122+
QUOTES_INI_SEC ":" QUOTES_INI_ATTR2, NULL));
1123+
CuAssertStrEquals(tc, QUOTES_INI_VAL0, iniparser_getstring(dic,
1124+
QUOTES_INI_SEC ":" QUOTES_INI_ATTR3, NULL));
1125+
CuAssertStrEquals(tc, "str", iniparser_getstring(dic,
1126+
QUOTES_INI_SEC ":" QUOTES_INI_ATTR4, NULL));
1127+
CuAssertStrEquals(tc, "str", iniparser_getstring(dic,
1128+
QUOTES_INI_SEC ":" QUOTES_INI_ATTR5, NULL));
1129+
CuAssertStrEquals(tc, QUOTES_INI_VAL0, iniparser_getstring(dic,
1130+
QUOTES_INI_SEC ":" "str\"ing", NULL));
1131+
CuAssertStrEquals(tc, NULL, iniparser_getstring(dic,
1132+
QUOTES_INI_SEC ":" "str;ing", NULL));
1133+
CuAssertStrEquals(tc, NULL, iniparser_getstring(dic,
1134+
QUOTES_INI_SEC ":" "str#ing", NULL));
1135+
CuAssertStrEquals(tc, QUOTES_INI_VAL0, iniparser_getstring(dic,
1136+
QUOTES_INI_SEC ":" "str:ing", NULL));
1137+
dictionary_del(dic);
1138+
ret = remove(TMP_INI_PATH);
1139+
1140+
if (ret) {
1141+
fprintf(stderr, "cannot remove file: %s\n", TMP_INI_PATH);
1142+
return;
1143+
}
11061144
/**
11071145
* test iniparser_set()
11081146
*/
@@ -1197,8 +1235,7 @@ void Test_iniparser_quotes(CuTest *tc)
11971235
goto rm_ini;
11981236
}
11991237

1200-
/* iniparser_load() does not support ; in values */
1201-
CuAssertStrEquals(tc, "str", iniparser_getstring(dic,
1238+
CuAssertStrEquals(tc, QUOTES_INI_VAL1, iniparser_getstring(dic,
12021239
QUOTES_INI_SEC ":" QUOTES_INI_ATTR1, NULL));
12031240
dictionary_del(dic);
12041241
ret = remove(TMP_INI_PATH);
@@ -1250,8 +1287,7 @@ void Test_iniparser_quotes(CuTest *tc)
12501287
goto rm_ini;
12511288
}
12521289

1253-
/* iniparser_load() does not support ; in values */
1254-
CuAssertStrEquals(tc, "str", iniparser_getstring(dic,
1290+
CuAssertStrEquals(tc, QUOTES_INI_VAL2, iniparser_getstring(dic,
12551291
QUOTES_INI_SEC ":" QUOTES_INI_ATTR2, NULL));
12561292
del_dic:
12571293
dictionary_del(dic);

0 commit comments

Comments
 (0)