Compare commits

...

5 Commits
master ... next

Author SHA1 Message Date
Nikos Tsipinakis
3330c988fc Merge pull request #298 from LukeShu/fix-option-parser
option_parser: Allow comments on lines with quoted strings
2017-07-14 14:05:59 +03:00
Luke Shumaker
a7b3ff1ea8 Handle "\n" -> newline expansion in the option_parser.
It makes more sense to handle escapes in configuration strings when we
parse them, rather than after-the-fact when we handle notifications that
use the relevant config options.

Do this in the proper backslash handler; rather than a naive string
replace which doesn't allow the backslash to be escaped (`\\n` => 0x5C0x0A
rather than 0x5C0x6E).
it to the proper backslash handler
2017-02-28 16:49:09 -05:00
Luke Shumaker
b83363e351 option_parser: Simplify backslash code.
Treating unrecognized sequences as invalid means that the backslash itself
is never propagated; so we can hoist the handling of it outside of the
switch; simplifying the code a bit.
2017-02-26 09:35:10 -05:00
Luke Shumaker
446d6afc58 option_parser.c: Treat unrecognized backslash-escapes as errors. 2017-02-26 09:35:10 -05:00
Luke Shumaker
82fa79c786 option_parser.c: Allow comments on lines with quoted strings.
The current behavior is
 - If the value contains a double-quote:
   - 1. Verify that it must contains at least two quotes.
   - 2. If one of the quotes is the first character, trim it.
   - 3. If one of the quotes is the last character, trim it.
 - Else:
   - 1. Trim a trailing comment from the value.

This has the effect that

    `key = "value" # comment` => `value" #comment`

This is surprising and almost certainly not what the user wants.

However, it allows simple nested quotes like:

    `key = "A string "with quotes""` => `A string "with quotes"`

Fix the brokenness of the first example at the expense of breaking the
second.  A user seeking that value will now have to type:

    key = "A string \"with quotes\""

Do this by treating double-quote as a toggle that simply changes whether
`;` and `#` start comments (not too different than Bash using it to toggle
field separation).

In order to have strings that contain a literal double-quote, add
rudimentary support for backslash-escaping.  For now, only recognize
double-quote and backslash-itself; anything else is undefined; and the
program is free to do whatever it likes with them; for now, silently treat
the backslash as an ordinary character.

Note that this formulation of quoting implies that backslash-escaping works
identically both inside and outside of quotes.
2017-02-26 09:35:06 -05:00
4 changed files with 84 additions and 33 deletions

View File

@ -304,7 +304,7 @@ int notification_init(notification * n, int id)
n->urls = notification_extract_markup_urls(&(n->body));
n->msg = string_replace_all("\\n", "\n", g_strdup(n->format));
n->msg = g_strdup(n->format);
n->msg = notification_replace_format("%a", n->appname, n->msg,
MARKUP_NO);
n->msg = notification_replace_format("%s", n->summary, n->msg,

View File

@ -29,7 +29,7 @@ static section_t *new_section(char *name);
static section_t *get_section(char *name);
static void add_entry(char *section_name, char *key, char *value);
static char *get_value(char *section, char *key);
static char *clean_value(char *value);
static char *clean_value(char *value, int line_num);
static int cmdline_argc;
static char **cmdline_argv;
@ -91,7 +91,7 @@ void add_entry(char *section_name, char *key, char *value)
int len = s->entry_count;
s->entries = g_realloc(s->entries, sizeof(entry_t) * len);
s->entries[s->entry_count - 1].key = g_strdup(key);
s->entries[s->entry_count - 1].value = clean_value(value);
s->entries[s->entry_count - 1].value = g_strdup(value);
}
char *get_value(char *section, char *key)
@ -186,20 +186,55 @@ int ini_get_bool(char *section, char *key, int def)
}
}
char *clean_value(char *value)
char *clean_value(char *value, int line_num)
{
char *s;
char *unparsed = value;
if (value[0] == '"')
s = g_strdup(value + 1);
else
s = g_strdup(value);
if (s[strlen(s) - 1] == '"')
s[strlen(s) - 1] = '\0';
return s;
bool in_quote = false;
while ((unparsed = strpbrk(unparsed, "\"\\#;")) != NULL) {
switch (*unparsed) {
case '"':
memmove(unparsed, unparsed + 1, strlen(unparsed));
in_quote = !in_quote;
break;
case '\\':
memmove(unparsed, unparsed + 1, strlen(unparsed));
switch (*unparsed) {
case '\\':
case '"':
break;
case 'n':
*unparsed = '\n';
break;
default:
fprintf(stderr,
"Warning: invalid config file at line %d\n",
line_num);
fprintf(stderr,
"Unrecognized backslash sequence '\\%c'\n",
*unparsed);
return NULL;
}
unparsed++;
break;
case '#':
case ';':
if (in_quote)
unparsed++;
else
*unparsed = '\0';
break;
}
}
if (in_quote) {
fprintf(stderr,
"Warning: invalid config file at line %d\n",
line_num);
fprintf(stderr, "Missing '\"'\n");
return NULL;
}
return g_strstrip(value);
}
int load_ini_file(FILE * fp)
@ -250,24 +285,8 @@ int load_ini_file(FILE * fp)
*equal = '\0';
char *key = g_strstrip(start);
char *value = g_strstrip(equal + 1);
char *quote = strchr(value, '"');
if (quote) {
char *closing_quote = strchr(quote + 1, '"');
if (!closing_quote) {
fprintf(stderr,
"Warning: invalid config file at line %d\n",
line_num);
fprintf(stderr, "Missing '\"'\n");
continue;
}
} else {
char *comment = strpbrk(value, "#;");
if (comment)
*comment = '\0';
}
value = g_strstrip(value);
char *value = clean_value(equal + 1, line_num);
if (!value) continue;
if (!current_section) {
fprintf(stderr,

View File

@ -20,8 +20,18 @@
[string]
simple = A simple string
simple_with_hcomment = A simple string # a comment
simple_with_scomment = A simple string ; a comment
simple_with_nl = A simple string\nwith newline
quoted = "A quoted string"
quoted_with_quotes = "A string "with quotes""
quoted_with_hcomment = "A quoted string" # a comment
quoted_with_scomment = "A quoted string" ; a comment
quoted_with_nl = "A quoted string\nwith newline"
quoted_with_quotes = "A string \"with quotes\""
quoted_with_escapes = "A string \\\"with escapes\\"
quoted_with_cchar = "A string; with #comment characters" # a comment
quoted_in_middle = A string"; with #comment" characters # a comment
escaped_quotes = String \"with quotes\"
[int]
simple = 5

View File

@ -45,13 +45,35 @@ TEST test_ini_get_string(void)
{
char *string_section = "string";
char *ptr;
ASSERT_STR_EQ("A simple string", (ptr = ini_get_string(string_section, "simple", "")));
free(ptr);
ASSERT_STR_EQ("A simple string", (ptr = ini_get_string(string_section, "simple_with_hcomment", "")));
free(ptr);
ASSERT_STR_EQ("A simple string", (ptr = ini_get_string(string_section, "simple_with_scomment", "")));
free(ptr);
ASSERT_STR_EQ("A simple string\nwith newline", (ptr = ini_get_string(string_section, "simple_with_nl", "")));
free(ptr);
ASSERT_STR_EQ("A quoted string", (ptr = ini_get_string(string_section, "quoted", "")));
free(ptr);
ASSERT_STR_EQ("A quoted string", (ptr = ini_get_string(string_section, "quoted_with_hcomment", "")));
free(ptr);
ASSERT_STR_EQ("A quoted string", (ptr = ini_get_string(string_section, "quoted_with_scomment", "")));
free(ptr);
ASSERT_STR_EQ("A quoted string\nwith newline", (ptr = ini_get_string(string_section, "quoted_with_nl", "")));
free(ptr);
ASSERT_STR_EQ("A string \"with quotes\"", (ptr = ini_get_string(string_section, "quoted_with_quotes", "")));
free(ptr);
ASSERT_STR_EQ("A string \\\"with escapes\\", (ptr = ini_get_string(string_section, "quoted_with_escapes", "")));
free(ptr);
ASSERT_STR_EQ("A string; with #comment characters", (ptr = ini_get_string(string_section, "quoted_with_cchar", "")));
free(ptr);
ASSERT_STR_EQ("A string; with #comment characters", (ptr = ini_get_string(string_section, "quoted_in_middle", "")));
free(ptr);
ASSERT_STR_EQ("String \"with quotes\"", (ptr = ini_get_string(string_section, "escaped_quotes", "")));
free(ptr);
ASSERT_STR_EQ("default value", (ptr = ini_get_string(string_section, "nonexistent", "default value")));
free(ptr);