Skip to content

Commit

Permalink
Add support for escaped quotes, semicolon and hash in quoted values
Browse files Browse the repository at this point in the history
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
  • Loading branch information
lmoellendorf committed Apr 20, 2024
1 parent 068b66a commit 8854c48
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 18 deletions.
78 changes: 71 additions & 7 deletions src/iniparser.c
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,26 @@ void iniparser_dump(const dictionary * d, FILE * f)
return ;
}

static void escape_value(char *escaped, char *value) {
char c;
int v = 0;
int e = 0;

if(!escaped || !value)
return;

while((c = value[v]) != '\0') {
if(c == '\\' || c == '"') {
escaped[e] = '\\';
e++;
}
escaped[e] = c;
v++;
e++;
}
escaped[e] = '\0';
}

/*-------------------------------------------------------------------------*/
/**
@brief Save a dictionary to a loadable ini file
Expand All @@ -263,6 +283,7 @@ void iniparser_dump_ini(const dictionary * d, FILE * f)
size_t i ;
size_t nsec ;
const char * secname ;
char escaped[ASCIILINESZ+1] = "";

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

Expand All @@ -272,7 +293,8 @@ void iniparser_dump_ini(const dictionary * d, FILE * f)
for (i=0 ; i<d->size ; i++) {
if (d->key[i]==NULL)
continue ;
fprintf(f, "%s = %s\n", d->key[i], d->val[i]);
escape_value(escaped, d->val[i]);
fprintf(f, "%s = \"%s\"\n", d->key[i], escaped);
}
return ;
}
Expand Down Expand Up @@ -301,6 +323,7 @@ void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f)
size_t j ;
char keym[ASCIILINESZ+1];
int seclen ;
char escaped[ASCIILINESZ+1] = "";

if (d==NULL || f==NULL) return ;
if (! iniparser_find_entry(d, s)) return ;
Expand All @@ -312,10 +335,8 @@ void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f)
if (d->key[j]==NULL)
continue ;
if (!strncmp(d->key[j], keym, seclen+1)) {
fprintf(f,
"%-30s = %s\n",
d->key[j]+seclen+1,
d->val[j] ? d->val[j] : "");
escape_value(escaped, d->val[j]);
fprintf(f, "%-30s = \"%s\"\n", d->key[j]+seclen+1, escaped);
}
}
fprintf(f, "\n");
Expand Down Expand Up @@ -642,6 +663,44 @@ void iniparser_unset(dictionary * ini, const char * entry)
dictionary_unset(ini, strlwc(entry, tmp_str, sizeof(tmp_str)));
}

static void parse_quoted_value(char *value, char quote) {
char c;
char *quoted;
int q = 0, v = 0;
int esc = 0;

if(!value)
return;

quoted = xstrdup(value);

if(!quoted) {
iniparser_error_callback("iniparser: memory allocation failure\n");
goto end_of_value;
}

while((c = quoted[q]) != '\0') {
if(!esc) {
if(c == '\\') {
esc = 1;
q++;
continue;
}

if(c == quote) {
goto end_of_value;
}
}
esc = 0;
value[v] = c;
v++;
q++;
}
end_of_value:
value[v] = '\0';
free(quoted);
}

/*-------------------------------------------------------------------------*/
/**
@brief Load a single line from an INI file
Expand All @@ -661,6 +720,7 @@ static line_status iniparser_line(
line_status sta ;
char * line = NULL;
size_t len ;
int d_quote;

line = xstrdup(input_line);
len = strstrip(line);
Expand All @@ -678,11 +738,15 @@ static line_status iniparser_line(
strstrip(section);
strlwc(section, section, len);
sta = LINE_SECTION ;
} else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
|| sscanf (line, "%[^=] = '%[^\']'", key, value) == 2) {
} else if ((d_quote = sscanf (line, "%[^=] = \"%[^\n]\"", key, value)) == 2
|| sscanf (line, "%[^=] = '%[^\n]'", key, value) == 2) {
/* Usual key=value with quotes, with or without comments */
strstrip(key);
strlwc(key, key, len);
if(d_quote == 2)
parse_quoted_value(value, '"');
else
parse_quoted_value(value, '\'');
/* Don't strip spaces from values surrounded with quotes */
sta = LINE_VALUE ;
} else if (sscanf (line, "%[^=] = %[^;#]", key, value) == 2) {
Expand Down
18 changes: 18 additions & 0 deletions src/iniparser.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ const char * iniparser_getsecname(const dictionary * d, int n);
This function dumps a given dictionary into a loadable ini file.
It is Ok to specify @c stderr or @c stdout as output files.
All values are quoted these charecters are escaped:
” - the quote character (e.g. “String with ”Quotes””)
\ - the backslash character (e.g. “C:\tmp”)
*/
/*--------------------------------------------------------------------------*/

Expand Down Expand Up @@ -382,6 +389,17 @@ int iniparser_find_entry(const dictionary * ini, const char * entry) ;
should not be accessed directly, but through accessor functions
instead.
Iff the value is a quoted string it supports some escape sequences:
” - the quote character (e.g. “String with ”Quotes””)
\ - the backslash character (e.g. “C:\tmp”)
Escape sequences always start with a backslash. Additional escape sequences
might be added in the future. Backslash characters must be escaped. Any other
sequence then those outlined above is invalid and may lead to unpredictable
results.
The returned dictionary must be freed using iniparser_freedict().
*/
/*--------------------------------------------------------------------------*/
Expand Down
58 changes: 47 additions & 11 deletions test/test_iniparser.c
Original file line number Diff line number Diff line change
Expand Up @@ -1066,11 +1066,7 @@ void Test_iniparser_quotes(CuTest *tc)
goto rm_ini;
}

/*
* iniparser_load() does not support escaped quotes in values if they are
* quoted
*/
CuAssertStrEquals(tc, "str\\", iniparser_getstring(dic,
CuAssertStrEquals(tc, QUOTES_INI_VAL0, iniparser_getstring(dic,
QUOTES_INI_SEC ":" QUOTES_INI_ATTR0, NULL));
/*
* iniparser_load() supports semicolon and hash in values if they are
Expand All @@ -1089,7 +1085,7 @@ void Test_iniparser_quotes(CuTest *tc)
/*
* iniparser_load() supports quotes in attributes
*/
CuAssertStrEquals(tc, "str\\", iniparser_getstring(dic,
CuAssertStrEquals(tc, QUOTES_INI_VAL0, iniparser_getstring(dic,
QUOTES_INI_SEC ":" "str\"ing", NULL));
/*
* iniparser_load() does not support semicolon or hash in attributes
Expand All @@ -1101,8 +1097,50 @@ void Test_iniparser_quotes(CuTest *tc)
/*
* iniparser_load() does support colon in attributes
*/
CuAssertStrEquals(tc, "str\\", iniparser_getstring(dic,
CuAssertStrEquals(tc, QUOTES_INI_VAL0, iniparser_getstring(dic,
QUOTES_INI_SEC ":" "str:ing", NULL));
ini = fopen(TMP_INI_PATH, "w+");

if (!ini) {
fprintf(stderr, "iniparser: cannot open %s\n", TMP_INI_PATH);
goto del_dic;
}

/**
* Test iniparser_dump_ini()
*/
iniparser_dump_ini(dic, ini);
fclose(ini);
dictionary_del(dic);
dic = iniparser_load(TMP_INI_PATH);

CuAssertStrEquals(tc, QUOTES_INI_VAL0, iniparser_getstring(dic,
QUOTES_INI_SEC ":" QUOTES_INI_ATTR0, NULL));
CuAssertStrEquals(tc, QUOTES_INI_VAL1, iniparser_getstring(dic,
QUOTES_INI_SEC ":" QUOTES_INI_ATTR1, NULL));
CuAssertStrEquals(tc, QUOTES_INI_VAL2, iniparser_getstring(dic,
QUOTES_INI_SEC ":" QUOTES_INI_ATTR2, NULL));
CuAssertStrEquals(tc, QUOTES_INI_VAL0, iniparser_getstring(dic,
QUOTES_INI_SEC ":" QUOTES_INI_ATTR3, NULL));
CuAssertStrEquals(tc, "str", iniparser_getstring(dic,
QUOTES_INI_SEC ":" QUOTES_INI_ATTR4, NULL));
CuAssertStrEquals(tc, "str", iniparser_getstring(dic,
QUOTES_INI_SEC ":" QUOTES_INI_ATTR5, NULL));
CuAssertStrEquals(tc, QUOTES_INI_VAL0, iniparser_getstring(dic,
QUOTES_INI_SEC ":" "str\"ing", NULL));
CuAssertStrEquals(tc, NULL, iniparser_getstring(dic,
QUOTES_INI_SEC ":" "str;ing", NULL));
CuAssertStrEquals(tc, NULL, iniparser_getstring(dic,
QUOTES_INI_SEC ":" "str#ing", NULL));
CuAssertStrEquals(tc, QUOTES_INI_VAL0, iniparser_getstring(dic,
QUOTES_INI_SEC ":" "str:ing", NULL));
dictionary_del(dic);
ret = remove(TMP_INI_PATH);

if (ret) {
fprintf(stderr, "cannot remove file: %s\n", TMP_INI_PATH);
return;
}
/**
* test iniparser_set()
*/
Expand Down Expand Up @@ -1197,8 +1235,7 @@ void Test_iniparser_quotes(CuTest *tc)
goto rm_ini;
}

/* iniparser_load() does not support ; in values */
CuAssertStrEquals(tc, "str", iniparser_getstring(dic,
CuAssertStrEquals(tc, QUOTES_INI_VAL1, iniparser_getstring(dic,
QUOTES_INI_SEC ":" QUOTES_INI_ATTR1, NULL));
dictionary_del(dic);
ret = remove(TMP_INI_PATH);
Expand Down Expand Up @@ -1250,8 +1287,7 @@ void Test_iniparser_quotes(CuTest *tc)
goto rm_ini;
}

/* iniparser_load() does not support ; in values */
CuAssertStrEquals(tc, "str", iniparser_getstring(dic,
CuAssertStrEquals(tc, QUOTES_INI_VAL2, iniparser_getstring(dic,
QUOTES_INI_SEC ":" QUOTES_INI_ATTR2, NULL));
del_dic:
dictionary_del(dic);
Expand Down

0 comments on commit 8854c48

Please sign in to comment.