diff options
author | Ian Fan <ianfan0@gmail.com> | 2019-06-15 09:30:22 +0100 |
---|---|---|
committer | Simon Ser <contact@emersion.fr> | 2020-03-30 17:31:00 +0200 |
commit | 66f0c91bb846472a8fb5edb2dde1b67f3a636ccf (patch) | |
tree | 32b27e76117cb686a86133d2b4a75244f71ee158 | |
parent | swaybar: fix memory leaks (diff) | |
download | sway-66f0c91bb846472a8fb5edb2dde1b67f3a636ccf.tar.gz sway-66f0c91bb846472a8fb5edb2dde1b67f3a636ccf.tar.zst sway-66f0c91bb846472a8fb5edb2dde1b67f3a636ccf.zip |
tray: better errors when parsing index.theme
-rw-r--r-- | swaybar/tray/icon.c | 150 |
1 files changed, 90 insertions, 60 deletions
diff --git a/swaybar/tray/icon.c b/swaybar/tray/icon.c index d0c3fa56..9c62911d 100644 --- a/swaybar/tray/icon.c +++ b/swaybar/tray/icon.c | |||
@@ -14,6 +14,10 @@ | |||
14 | #include "log.h" | 14 | #include "log.h" |
15 | #include "stringop.h" | 15 | #include "stringop.h" |
16 | 16 | ||
17 | static int cmp_id(const void *item, const void *cmp_to) { | ||
18 | return strcmp(item, cmp_to); | ||
19 | } | ||
20 | |||
17 | static bool dir_exists(char *path) { | 21 | static bool dir_exists(char *path) { |
18 | struct stat sb; | 22 | struct stat sb; |
19 | return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode); | 23 | return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode); |
@@ -78,35 +82,37 @@ static void destroy_theme(struct icon_theme *theme) { | |||
78 | free(theme); | 82 | free(theme); |
79 | } | 83 | } |
80 | 84 | ||
81 | static int cmp_group(const void *item, const void *cmp_to) { | 85 | static const char *group_handler(char *old_group, char *new_group, |
82 | return strcmp(item, cmp_to); | ||
83 | } | ||
84 | |||
85 | static bool validate_icon_theme(struct icon_theme *theme) { | ||
86 | return theme && theme->name && theme->comment && theme->directories; | ||
87 | } | ||
88 | |||
89 | static bool group_handler(char *old_group, char *new_group, | ||
90 | struct icon_theme *theme) { | 86 | struct icon_theme *theme) { |
91 | if (!old_group) { // first group must be "Icon Theme" | 87 | if (!old_group) { |
92 | if (!new_group) { | 88 | return new_group && strcmp(new_group, "Icon Theme") == 0 ? NULL : |
93 | return true; | 89 | "first group must be 'Icon Theme'"; |
94 | } | ||
95 | return strcmp(new_group, "Icon Theme") != 0; | ||
96 | } | 90 | } |
97 | 91 | ||
98 | if (strcmp(old_group, "Icon Theme") == 0) { | 92 | if (strcmp(old_group, "Icon Theme") == 0) { |
99 | if (!validate_icon_theme(theme)) { | 93 | if (!theme->name) { |
100 | return true; | 94 | return "missing required key 'Name'"; |
95 | } else if (!theme->comment) { | ||
96 | return "missing required key 'Comment'"; | ||
97 | } else if (!theme->directories) { | ||
98 | return "missing required key 'Directories'"; | ||
99 | } else { | ||
100 | for (char *c = theme->name; *c; ++c) { | ||
101 | if (*c == ',' || *c == ' ') { | ||
102 | return "malformed theme name"; | ||
103 | } | ||
104 | } | ||
101 | } | 105 | } |
102 | } else { | 106 | } else { |
103 | if (theme->subdirs->length == 0) { // skip | 107 | if (theme->subdirs->length == 0) { // skip |
104 | return false; | 108 | return NULL; |
105 | } | 109 | } |
106 | 110 | ||
107 | struct icon_theme_subdir *subdir = | 111 | struct icon_theme_subdir *subdir = |
108 | theme->subdirs->items[theme->subdirs->length - 1]; | 112 | theme->subdirs->items[theme->subdirs->length - 1]; |
109 | if (!subdir->size) return true; | 113 | if (!subdir->size) { |
114 | return "missing required key 'Size'"; | ||
115 | } | ||
110 | 116 | ||
111 | switch (subdir->type) { | 117 | switch (subdir->type) { |
112 | case FIXED: subdir->max_size = subdir->min_size = subdir->size; | 118 | case FIXED: subdir->max_size = subdir->min_size = subdir->size; |
@@ -122,20 +128,20 @@ static bool group_handler(char *old_group, char *new_group, | |||
122 | } | 128 | } |
123 | } | 129 | } |
124 | 130 | ||
125 | if (new_group && list_seq_find(theme->directories, cmp_group, new_group) != -1) { | 131 | if (new_group && list_seq_find(theme->directories, cmp_id, new_group) != -1) { |
126 | struct icon_theme_subdir *subdir = calloc(1, sizeof(struct icon_theme_subdir)); | 132 | struct icon_theme_subdir *subdir = calloc(1, sizeof(struct icon_theme_subdir)); |
127 | if (!subdir) { | 133 | if (!subdir) { |
128 | return true; | 134 | return "out of memory"; |
129 | } | 135 | } |
130 | subdir->name = strdup(new_group); | 136 | subdir->name = strdup(new_group); |
131 | subdir->threshold = 2; | 137 | subdir->threshold = 2; |
132 | list_add(theme->subdirs, subdir); | 138 | list_add(theme->subdirs, subdir); |
133 | } | 139 | } |
134 | 140 | ||
135 | return false; | 141 | return NULL; |
136 | } | 142 | } |
137 | 143 | ||
138 | static int entry_handler(char *group, char *key, char *value, | 144 | static const char *entry_handler(char *group, char *key, char *value, |
139 | struct icon_theme *theme) { | 145 | struct icon_theme *theme) { |
140 | if (strcmp(group, "Icon Theme") == 0) { | 146 | if (strcmp(group, "Icon Theme") == 0) { |
141 | if (strcmp(key, "Name") == 0) { | 147 | if (strcmp(key, "Name") == 0) { |
@@ -149,20 +155,17 @@ static int entry_handler(char *group, char *key, char *value, | |||
149 | } // Ignored: ScaledDirectories, Hidden, Example | 155 | } // Ignored: ScaledDirectories, Hidden, Example |
150 | } else { | 156 | } else { |
151 | if (theme->subdirs->length == 0) { // skip | 157 | if (theme->subdirs->length == 0) { // skip |
152 | return false; | 158 | return NULL; |
153 | } | 159 | } |
154 | 160 | ||
155 | struct icon_theme_subdir *subdir = | 161 | struct icon_theme_subdir *subdir = |
156 | theme->subdirs->items[theme->subdirs->length - 1]; | 162 | theme->subdirs->items[theme->subdirs->length - 1]; |
157 | if (strcmp(subdir->name, group) != 0) { // skip | 163 | if (strcmp(subdir->name, group) != 0) { // skip |
158 | return false; | 164 | return NULL; |
159 | } | 165 | } |
160 | 166 | ||
161 | char *end; | 167 | if (strcmp(key, "Context") == 0) { |
162 | int n = strtol(value, &end, 10); | 168 | return NULL; // ignored, but explicitly handled to not fail parsing |
163 | if (strcmp(key, "Size") == 0) { | ||
164 | subdir->size = n; | ||
165 | return *end != '\0'; | ||
166 | } else if (strcmp(key, "Type") == 0) { | 169 | } else if (strcmp(key, "Type") == 0) { |
167 | if (strcmp(value, "Fixed") == 0) { | 170 | if (strcmp(value, "Fixed") == 0) { |
168 | subdir->type = FIXED; | 171 | subdir->type = FIXED; |
@@ -171,20 +174,28 @@ static int entry_handler(char *group, char *key, char *value, | |||
171 | } else if (strcmp(value, "Threshold") == 0) { | 174 | } else if (strcmp(value, "Threshold") == 0) { |
172 | subdir->type = THRESHOLD; | 175 | subdir->type = THRESHOLD; |
173 | } else { | 176 | } else { |
174 | return true; | 177 | return "invalid value - expected 'Fixed', 'Scalable' or 'Threshold'"; |
175 | } | 178 | } |
179 | return NULL; | ||
180 | } | ||
181 | |||
182 | char *end; | ||
183 | int n = strtol(value, &end, 10); | ||
184 | if (*end != '\0') { | ||
185 | return "invalid value - expected a number"; | ||
186 | } | ||
187 | |||
188 | if (strcmp(key, "Size") == 0) { | ||
189 | subdir->size = n; | ||
176 | } else if (strcmp(key, "MaxSize") == 0) { | 190 | } else if (strcmp(key, "MaxSize") == 0) { |
177 | subdir->max_size = n; | 191 | subdir->max_size = n; |
178 | return *end != '\0'; | ||
179 | } else if (strcmp(key, "MinSize") == 0) { | 192 | } else if (strcmp(key, "MinSize") == 0) { |
180 | subdir->min_size = n; | 193 | subdir->min_size = n; |
181 | return *end != '\0'; | ||
182 | } else if (strcmp(key, "Threshold") == 0) { | 194 | } else if (strcmp(key, "Threshold") == 0) { |
183 | subdir->threshold = n; | 195 | subdir->threshold = n; |
184 | return *end != '\0'; | 196 | } // Ignored: Scale |
185 | } // Ignored: Scale, Applications | ||
186 | } | 197 | } |
187 | return false; | 198 | return NULL; |
188 | } | 199 | } |
189 | 200 | ||
190 | /* | 201 | /* |
@@ -215,12 +226,16 @@ static struct icon_theme *read_theme_file(char *basedir, char *theme_name) { | |||
215 | } | 226 | } |
216 | theme->subdirs = create_list(); | 227 | theme->subdirs = create_list(); |
217 | 228 | ||
218 | bool error = false; | 229 | list_t *groups = create_list(); |
219 | char *group = NULL; | 230 | |
231 | const char *error = NULL; | ||
232 | int line_no = 0; | ||
220 | char *full_line = NULL; | 233 | char *full_line = NULL; |
221 | size_t full_len = 0; | 234 | size_t full_len = 0; |
222 | ssize_t nread; | 235 | ssize_t nread; |
223 | while ((nread = getline(&full_line, &full_len, theme_file)) != -1) { | 236 | while ((nread = getline(&full_line, &full_len, theme_file)) != -1) { |
237 | ++line_no; | ||
238 | |||
224 | char *line = full_line - 1; | 239 | char *line = full_line - 1; |
225 | while (isspace(*++line)) {} // remove leading whitespace | 240 | while (isspace(*++line)) {} // remove leading whitespace |
226 | if (!*line || line[0] == '#') continue; // ignore blank lines & comments | 241 | 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) { | |||
231 | 246 | ||
232 | if (line[0] == '[') { // group header | 247 | if (line[0] == '[') { // group header |
233 | // check well-formed | 248 | // check well-formed |
234 | if (line[--len] != ']') { | ||
235 | error = true; | ||
236 | break; | ||
237 | } | ||
238 | int i = 1; | 249 | int i = 1; |
239 | for (; !iscntrl(line[i]) && line[i] != '[' && line[i] != ']'; ++i) {} | 250 | for (; !iscntrl(line[i]) && line[i] != '[' && line[i] != ']'; ++i) {} |
240 | if (i < len) { | 251 | if (i != --len || line[i] != ']') { |
241 | error = true; | 252 | error = "malformed group header"; |
242 | break; | 253 | break; |
243 | } | 254 | } |
244 | 255 | ||
245 | // call handler | ||
246 | line[len] = '\0'; | 256 | line[len] = '\0'; |
247 | error = group_handler(group, &line[1], theme); | 257 | |
258 | // check group is not duplicate | ||
259 | if (list_seq_find(groups, cmp_id, &line[1]) != -1) { | ||
260 | error = "duplicate group"; | ||
261 | break; | ||
262 | } | ||
263 | |||
264 | // call handler | ||
265 | char *last_group = groups->length > 0 ? groups->items[groups->length - 1] : NULL; | ||
266 | error = group_handler(last_group, &line[1], theme); | ||
248 | if (error) { | 267 | if (error) { |
249 | break; | 268 | break; |
250 | } | 269 | } |
251 | free(group); | 270 | |
252 | group = strdup(&line[1]); | 271 | list_add(groups, strdup(&line[1])); |
253 | } else { // key-value pair | 272 | } else { // key-value pair |
254 | if (!group) { | 273 | if (groups->length == 0) { |
255 | error = true; | 274 | error = "unexpected content before first header"; |
256 | break; | 275 | break; |
257 | } | 276 | } |
277 | |||
258 | // check well-formed | 278 | // check well-formed |
259 | int eok = 0; | 279 | int eok = 0; |
260 | for (; isalnum(line[eok]) || line[eok] == '-'; ++eok) {} // TODO locale? | 280 | for (; isalnum(line[eok]) || line[eok] == '-'; ++eok) {} // TODO locale? |
261 | int i = eok - 1; | 281 | int i = eok - 1; |
262 | while (isspace(line[++i])) {} | 282 | while (isspace(line[++i])) {} |
263 | if (line[i] != '=') { | 283 | if (line[i] != '=') { |
264 | error = true; | 284 | error = "malformed key-value pair"; |
265 | break; | 285 | break; |
266 | } | 286 | } |
267 | 287 | ||
@@ -269,28 +289,38 @@ static struct icon_theme *read_theme_file(char *basedir, char *theme_name) { | |||
269 | char *value = &line[i]; | 289 | char *value = &line[i]; |
270 | while (isspace(*++value)) {} | 290 | while (isspace(*++value)) {} |
271 | // TODO unescape value | 291 | // TODO unescape value |
272 | error = entry_handler(group, line, value, theme); | 292 | |
293 | error = entry_handler(groups->items[groups->length - 1], line, | ||
294 | value, theme); | ||
273 | if (error) { | 295 | if (error) { |
274 | break; | 296 | break; |
275 | } | 297 | } |
276 | } | 298 | } |
277 | } | 299 | } |
278 | 300 | ||
279 | if (!error && group) { | 301 | if (!error) { |
280 | error = group_handler(group, NULL, theme); | 302 | if (groups->length > 0) { |
303 | error = group_handler(groups->items[groups->length - 1], NULL, theme); | ||
304 | } else { | ||
305 | error = "empty file"; | ||
306 | } | ||
281 | } | 307 | } |
282 | 308 | ||
283 | free(group); | 309 | if (!error) { |
284 | free(full_line); | ||
285 | fclose(theme_file); | ||
286 | |||
287 | if (!error && validate_icon_theme(theme)) { | ||
288 | theme->dir = strdup(theme_name); | 310 | theme->dir = strdup(theme_name); |
289 | return theme; | ||
290 | } else { | 311 | } else { |
312 | char *last_group = groups->length > 0 ? groups->items[groups->length-1] : "n/a"; | ||
313 | sway_log(SWAY_DEBUG, "Failed to load theme '%s' - parsing of file " | ||
314 | "'%s/%s/index.theme' failed on line %d (group '%s'): %s", | ||
315 | theme_name, basedir, theme_name, line_no, last_group, error); | ||
291 | destroy_theme(theme); | 316 | destroy_theme(theme); |
292 | return NULL; | 317 | theme = NULL; |
293 | } | 318 | } |
319 | |||
320 | free(full_line); | ||
321 | list_free_items_and_destroy(groups); | ||
322 | fclose(theme_file); | ||
323 | return theme; | ||
294 | } | 324 | } |
295 | 325 | ||
296 | static list_t *load_themes_in_dir(char *basedir) { | 326 | static list_t *load_themes_in_dir(char *basedir) { |