tray: better errors when parsing index.theme

This commit is contained in:
Ian Fan 2019-06-15 09:30:22 +01:00 committed by Simon Ser
parent b54f5e170a
commit 66f0c91bb8

View file

@ -14,6 +14,10 @@
#include "log.h" #include "log.h"
#include "stringop.h" #include "stringop.h"
static int cmp_id(const void *item, const void *cmp_to) {
return strcmp(item, cmp_to);
}
static bool dir_exists(char *path) { static bool dir_exists(char *path) {
struct stat sb; struct stat sb;
return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode); return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode);
@ -78,35 +82,37 @@ static void destroy_theme(struct icon_theme *theme) {
free(theme); free(theme);
} }
static int cmp_group(const void *item, const void *cmp_to) { static const char *group_handler(char *old_group, char *new_group,
return strcmp(item, cmp_to);
}
static bool validate_icon_theme(struct icon_theme *theme) {
return theme && theme->name && theme->comment && theme->directories;
}
static bool group_handler(char *old_group, char *new_group,
struct icon_theme *theme) { struct icon_theme *theme) {
if (!old_group) { // first group must be "Icon Theme" if (!old_group) {
if (!new_group) { return new_group && strcmp(new_group, "Icon Theme") == 0 ? NULL :
return true; "first group must be 'Icon Theme'";
}
return strcmp(new_group, "Icon Theme") != 0;
} }
if (strcmp(old_group, "Icon Theme") == 0) { if (strcmp(old_group, "Icon Theme") == 0) {
if (!validate_icon_theme(theme)) { if (!theme->name) {
return true; return "missing required key 'Name'";
} else if (!theme->comment) {
return "missing required key 'Comment'";
} else if (!theme->directories) {
return "missing required key 'Directories'";
} else {
for (char *c = theme->name; *c; ++c) {
if (*c == ',' || *c == ' ') {
return "malformed theme name";
}
}
} }
} else { } else {
if (theme->subdirs->length == 0) { // skip if (theme->subdirs->length == 0) { // skip
return false; return NULL;
} }
struct icon_theme_subdir *subdir = struct icon_theme_subdir *subdir =
theme->subdirs->items[theme->subdirs->length - 1]; theme->subdirs->items[theme->subdirs->length - 1];
if (!subdir->size) return true; if (!subdir->size) {
return "missing required key 'Size'";
}
switch (subdir->type) { switch (subdir->type) {
case FIXED: subdir->max_size = subdir->min_size = subdir->size; case FIXED: subdir->max_size = subdir->min_size = subdir->size;
@ -122,20 +128,20 @@ static bool group_handler(char *old_group, char *new_group,
} }
} }
if (new_group && list_seq_find(theme->directories, cmp_group, new_group) != -1) { if (new_group && list_seq_find(theme->directories, cmp_id, new_group) != -1) {
struct icon_theme_subdir *subdir = calloc(1, sizeof(struct icon_theme_subdir)); struct icon_theme_subdir *subdir = calloc(1, sizeof(struct icon_theme_subdir));
if (!subdir) { if (!subdir) {
return true; return "out of memory";
} }
subdir->name = strdup(new_group); subdir->name = strdup(new_group);
subdir->threshold = 2; subdir->threshold = 2;
list_add(theme->subdirs, subdir); list_add(theme->subdirs, subdir);
} }
return false; return NULL;
} }
static int entry_handler(char *group, char *key, char *value, static const char *entry_handler(char *group, char *key, char *value,
struct icon_theme *theme) { struct icon_theme *theme) {
if (strcmp(group, "Icon Theme") == 0) { if (strcmp(group, "Icon Theme") == 0) {
if (strcmp(key, "Name") == 0) { if (strcmp(key, "Name") == 0) {
@ -149,20 +155,17 @@ static int entry_handler(char *group, char *key, char *value,
} // Ignored: ScaledDirectories, Hidden, Example } // Ignored: ScaledDirectories, Hidden, Example
} else { } else {
if (theme->subdirs->length == 0) { // skip if (theme->subdirs->length == 0) { // skip
return false; return NULL;
} }
struct icon_theme_subdir *subdir = struct icon_theme_subdir *subdir =
theme->subdirs->items[theme->subdirs->length - 1]; theme->subdirs->items[theme->subdirs->length - 1];
if (strcmp(subdir->name, group) != 0) { // skip if (strcmp(subdir->name, group) != 0) { // skip
return false; return NULL;
} }
char *end; if (strcmp(key, "Context") == 0) {
int n = strtol(value, &end, 10); return NULL; // ignored, but explicitly handled to not fail parsing
if (strcmp(key, "Size") == 0) {
subdir->size = n;
return *end != '\0';
} else if (strcmp(key, "Type") == 0) { } else if (strcmp(key, "Type") == 0) {
if (strcmp(value, "Fixed") == 0) { if (strcmp(value, "Fixed") == 0) {
subdir->type = FIXED; subdir->type = FIXED;
@ -171,20 +174,28 @@ static int entry_handler(char *group, char *key, char *value,
} else if (strcmp(value, "Threshold") == 0) { } else if (strcmp(value, "Threshold") == 0) {
subdir->type = THRESHOLD; subdir->type = THRESHOLD;
} else { } else {
return true; return "invalid value - expected 'Fixed', 'Scalable' or 'Threshold'";
} }
return NULL;
}
char *end;
int n = strtol(value, &end, 10);
if (*end != '\0') {
return "invalid value - expected a number";
}
if (strcmp(key, "Size") == 0) {
subdir->size = n;
} else if (strcmp(key, "MaxSize") == 0) { } else if (strcmp(key, "MaxSize") == 0) {
subdir->max_size = n; subdir->max_size = n;
return *end != '\0';
} else if (strcmp(key, "MinSize") == 0) { } else if (strcmp(key, "MinSize") == 0) {
subdir->min_size = n; subdir->min_size = n;
return *end != '\0';
} else if (strcmp(key, "Threshold") == 0) { } else if (strcmp(key, "Threshold") == 0) {
subdir->threshold = n; subdir->threshold = n;
return *end != '\0'; } // Ignored: Scale
} // Ignored: Scale, Applications
} }
return false; return NULL;
} }
/* /*
@ -215,12 +226,16 @@ static struct icon_theme *read_theme_file(char *basedir, char *theme_name) {
} }
theme->subdirs = create_list(); theme->subdirs = create_list();
bool error = false; list_t *groups = create_list();
char *group = NULL;
const char *error = NULL;
int line_no = 0;
char *full_line = NULL; char *full_line = NULL;
size_t full_len = 0; size_t full_len = 0;
ssize_t nread; ssize_t nread;
while ((nread = getline(&full_line, &full_len, theme_file)) != -1) { while ((nread = getline(&full_line, &full_len, theme_file)) != -1) {
++line_no;
char *line = full_line - 1; char *line = full_line - 1;
while (isspace(*++line)) {} // remove leading whitespace while (isspace(*++line)) {} // remove leading whitespace
if (!*line || line[0] == '#') continue; // ignore blank lines & comments if (!*line || line[0] == '#') continue; // ignore blank lines & comments
@ -231,37 +246,42 @@ static struct icon_theme *read_theme_file(char *basedir, char *theme_name) {
if (line[0] == '[') { // group header if (line[0] == '[') { // group header
// check well-formed // check well-formed
if (line[--len] != ']') {
error = true;
break;
}
int i = 1; int i = 1;
for (; !iscntrl(line[i]) && line[i] != '[' && line[i] != ']'; ++i) {} for (; !iscntrl(line[i]) && line[i] != '[' && line[i] != ']'; ++i) {}
if (i < len) { if (i != --len || line[i] != ']') {
error = true; error = "malformed group header";
break;
}
line[len] = '\0';
// check group is not duplicate
if (list_seq_find(groups, cmp_id, &line[1]) != -1) {
error = "duplicate group";
break; break;
} }
// call handler // call handler
line[len] = '\0'; char *last_group = groups->length > 0 ? groups->items[groups->length - 1] : NULL;
error = group_handler(group, &line[1], theme); error = group_handler(last_group, &line[1], theme);
if (error) { if (error) {
break; break;
} }
free(group);
group = strdup(&line[1]); list_add(groups, strdup(&line[1]));
} else { // key-value pair } else { // key-value pair
if (!group) { if (groups->length == 0) {
error = true; error = "unexpected content before first header";
break; break;
} }
// check well-formed // check well-formed
int eok = 0; int eok = 0;
for (; isalnum(line[eok]) || line[eok] == '-'; ++eok) {} // TODO locale? for (; isalnum(line[eok]) || line[eok] == '-'; ++eok) {} // TODO locale?
int i = eok - 1; int i = eok - 1;
while (isspace(line[++i])) {} while (isspace(line[++i])) {}
if (line[i] != '=') { if (line[i] != '=') {
error = true; error = "malformed key-value pair";
break; break;
} }
@ -269,30 +289,40 @@ static struct icon_theme *read_theme_file(char *basedir, char *theme_name) {
char *value = &line[i]; char *value = &line[i];
while (isspace(*++value)) {} while (isspace(*++value)) {}
// TODO unescape value // TODO unescape value
error = entry_handler(group, line, value, theme);
error = entry_handler(groups->items[groups->length - 1], line,
value, theme);
if (error) { if (error) {
break; break;
} }
} }
} }
if (!error && group) { if (!error) {
error = group_handler(group, NULL, theme); if (groups->length > 0) {
} error = group_handler(groups->items[groups->length - 1], NULL, theme);
free(group);
free(full_line);
fclose(theme_file);
if (!error && validate_icon_theme(theme)) {
theme->dir = strdup(theme_name);
return theme;
} else { } else {
destroy_theme(theme); error = "empty file";
return NULL;
} }
} }
if (!error) {
theme->dir = strdup(theme_name);
} else {
char *last_group = groups->length > 0 ? groups->items[groups->length-1] : "n/a";
sway_log(SWAY_DEBUG, "Failed to load theme '%s' - parsing of file "
"'%s/%s/index.theme' failed on line %d (group '%s'): %s",
theme_name, basedir, theme_name, line_no, last_group, error);
destroy_theme(theme);
theme = NULL;
}
free(full_line);
list_free_items_and_destroy(groups);
fclose(theme_file);
return theme;
}
static list_t *load_themes_in_dir(char *basedir) { static list_t *load_themes_in_dir(char *basedir) {
DIR *dir; DIR *dir;
if (!(dir = opendir(basedir))) { if (!(dir = opendir(basedir))) {