]> Dogcows Code - chaz/openbox/blob - obt/ddparse.c
stat() can give an error, handle that
[chaz/openbox] / obt / ddparse.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3 obt/ddparse.c for the Openbox window manager
4 Copyright (c) 2009 Dana Jansens
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 #include "obt/ddparse.h"
20 #include "obt/link.h"
21 #ifdef HAVE_STRING_H
22 #include <string.h>
23 #endif
24 #ifdef HAVE_STDIO_H
25 #include <stdio.h>
26 #endif
27
28 typedef struct _ObtDDParse ObtDDParse;
29
30 /* Parses the value and adds it to the group's key_hash, with the given
31 key
32 Return TRUE if it is added to the hash table, and FALSE if not.
33 */
34 typedef gboolean (*ObtDDParseValueFunc)(gchar *key, const gchar *val,
35 ObtDDParse *parse, gboolean *error);
36
37
38 struct _ObtDDParse {
39 gchar *filename;
40 gulong lineno;
41 ObtDDParseGroup *group;
42 /* the key is a group name, the value is a ObtDDParseGroup */
43 GHashTable *group_hash;
44 };
45
46 struct _ObtDDParseGroup {
47 gchar *name;
48 gboolean seen;
49 ObtDDParseValueFunc value_func;
50 /* the key is a string (a key inside the group in the .desktop).
51 the value is an ObtDDParseValue */
52 GHashTable *key_hash;
53 };
54
55 /* Displays a warning message including the file name and line number, and
56 sets the boolean @error to true if it points to a non-NULL address.
57 */
58 static void parse_error(const gchar *m, const ObtDDParse *const parse,
59 gboolean *error)
60 {
61 if (!parse->filename)
62 g_warning("%s at line %lu of input", m, parse->lineno);
63 else
64 g_warning("%s at line %lu of file %s",
65 m, parse->lineno, parse->filename);
66 if (error) *error = TRUE;
67 }
68
69 static void parse_value_free(ObtDDParseValue *v)
70 {
71 switch (v->type) {
72 case OBT_DDPARSE_STRING:
73 case OBT_DDPARSE_LOCALESTRING:
74 g_free(v->value.string); break;
75 case OBT_DDPARSE_STRINGS:
76 case OBT_DDPARSE_LOCALESTRINGS:
77 g_free(v->value.strings.s);
78 v->value.strings.n = 0;
79 break;
80 case OBT_DDPARSE_BOOLEAN:
81 case OBT_DDPARSE_NUMERIC:
82 case OBT_DDPARSE_ENUM_APPLICATION:
83 break;
84 default:
85 g_assert_not_reached();
86 }
87 g_slice_free(ObtDDParseValue, v);
88 }
89
90 static ObtDDParseGroup* parse_group_new(gchar *name, ObtDDParseValueFunc f)
91 {
92 ObtDDParseGroup *g = g_slice_new(ObtDDParseGroup);
93 g->name = name;
94 g->value_func = f;
95 g->seen = FALSE;
96 g->key_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
97 g_free,
98 (GDestroyNotify)parse_value_free);
99 return g;
100 }
101
102 static void parse_group_free(ObtDDParseGroup *g)
103 {
104 g_free(g->name);
105 g_hash_table_destroy(g->key_hash);
106 g_slice_free(ObtDDParseGroup, g);
107 }
108
109 /*! Reads an input string, strips out invalid stuff, and parses
110 backslash-stuff.
111 If @nstrings is not NULL, then it splits the output string at ';'
112 characters. They are all returned in the same string with null zeros
113 between them, @nstrings is set to the number of such strings.
114 */
115 static gchar* parse_value_string(const gchar *in,
116 gboolean locale,
117 gulong *nstrings,
118 const ObtDDParse *const parse,
119 gboolean *error)
120 {
121 const gint bytes = strlen(in);
122 gboolean backslash;
123 gchar *out, *o;
124 const gchar *end, *i;
125
126 g_return_val_if_fail(in != NULL, NULL);
127
128 if (!locale) {
129 end = in + bytes;
130 for (i = in; i < end; ++i) {
131 if ((guchar)*i >= 127 || (guchar)*i < 32) {
132 /* non-control character ascii */
133 end = i;
134 parse_error("Invalid bytes in string", parse, error);
135 break;
136 }
137 }
138 }
139 else if (!g_utf8_validate(in, bytes, &end))
140 parse_error("Invalid bytes in localestring", parse, error);
141
142 if (nstrings) *nstrings = 1;
143
144 out = g_new(char, bytes + 1);
145 i = in; o = out;
146 backslash = FALSE;
147 while (i < end) {
148 const gchar *next = locale ? g_utf8_find_next_char(i, end) : i+1;
149 if (backslash) {
150 switch(*i) {
151 case 's': *o++ = ' '; break;
152 case 'n': *o++ = '\n'; break;
153 case 't': *o++ = '\t'; break;
154 case 'r': *o++ = '\r'; break;
155 case ';': *o++ = ';'; break;
156 case '\\': *o++ = '\\'; break;
157 default:
158 parse_error((locale ?
159 "Invalid escape sequence in localestring" :
160 "Invalid escape sequence in string"),
161 parse, error);
162 }
163 backslash = FALSE;
164 }
165 else if (*i == '\\')
166 backslash = TRUE;
167 else if (*i == ';' && nstrings) {
168 ++nstrings;
169 *o = '\0';
170 }
171 else if ((guchar)*i == 127 || (guchar)*i < 32) {
172 /* avoid ascii control characters */
173 parse_error("Found control character in string", parse, error);
174 break;
175 }
176 else {
177 memcpy(o, i, next-i);
178 o += next-i;
179 }
180 i = next;
181 }
182 *o = '\0';
183 return o;
184 }
185
186 static gboolean parse_value_boolean(const gchar *in,
187 const ObtDDParse *const parse,
188 gboolean *error)
189 {
190 if (strcmp(in, "true") == 0)
191 return TRUE;
192 else if (strcmp(in, "false") != 0)
193 parse_error("Invalid boolean value", parse, error);
194 return FALSE;
195 }
196
197 static gfloat parse_value_numeric(const gchar *in,
198 const ObtDDParse *const parse,
199 gboolean *error)
200 {
201 gfloat out = 0;
202 if (sscanf(in, "%f", &out) == 0)
203 parse_error("Invalid numeric value", parse, error);
204 return out;
205 }
206
207 static gboolean parse_file_line(FILE *f, gchar **buf,
208 gulong *size, gulong *read,
209 ObtDDParse *parse, gboolean *error)
210 {
211 const gulong BUFMUL = 80;
212 size_t ret;
213 gulong i, null;
214
215 if (*size == 0) {
216 g_assert(*read == 0);
217 *size = BUFMUL;
218 *buf = g_new(char, *size);
219 }
220
221 /* remove everything up to a null zero already in the buffer and shift
222 the rest to the front */
223 null = *size;
224 for (i = 0; i < *read; ++i) {
225 if (null < *size)
226 (*buf)[i-null-1] = (*buf)[i];
227 else if ((*buf)[i] == '\0')
228 null = i;
229 }
230 if (null < *size)
231 *read -= null + 1;
232
233 /* is there already a newline in the buffer? */
234 for (i = 0; i < *read; ++i)
235 if ((*buf)[i] == '\n') {
236 /* turn it into a null zero and done */
237 (*buf)[i] = '\0';
238 return TRUE;
239 }
240
241 /* we need to read some more to find a newline */
242 while (TRUE) {
243 gulong eol;
244 gchar *newread;
245
246 newread = *buf + *read;
247 ret = fread(newread, sizeof(char), *size-*read, f);
248 if (ret < *size - *read && !feof(f)) {
249 parse_error("Error reading", parse, error);
250 return FALSE;
251 }
252 *read += ret;
253
254 /* strip out null zeros in the input and look for an endofline */
255 null = 0;
256 eol = *size;
257 for (i = newread-*buf; i < *read; ++i) {
258 if (null > 0)
259 (*buf)[i] = (*buf)[i+null];
260 if ((*buf)[i] == '\0') {
261 ++null;
262 --(*read);
263 --i; /* try again */
264 }
265 else if ((*buf)[i] == '\n' && eol == *size) {
266 eol = i;
267 /* turn it into a null zero */
268 (*buf)[i] = '\0';
269 }
270 }
271
272 if (eol != *size)
273 /* found an endofline, done */
274 break;
275 else if (feof(f) && *read < *size) {
276 /* found the endoffile, done (if there is space) */
277 if (*read > 0) {
278 /* stick a null zero on if there is test on the last line */
279 (*buf)[(*read)++] = '\0';
280 }
281 break;
282 }
283 else {
284 /* read more */
285 size += BUFMUL;
286 *buf = g_renew(char, *buf, *size);
287 }
288 }
289 return *read > 0;
290 }
291
292 static void parse_group(const gchar *buf, gulong len,
293 ObtDDParse *parse, gboolean *error)
294 {
295 ObtDDParseGroup *g;
296 gchar *group;
297 gulong i;
298
299 /* get the group name */
300 group = g_strndup(buf+1, len-2);
301 for (i = 0; i < len-2; ++i)
302 if ((guchar)group[i] < 32 || (guchar)group[i] >= 127) {
303 /* valid ASCII only */
304 parse_error("Invalid character found", parse, NULL);
305 group[i] = '\0'; /* stopping before this character */
306 break;
307 }
308
309 /* make sure it's a new group */
310 g = g_hash_table_lookup(parse->group_hash, group);
311 if (g && g->seen) {
312 parse_error("Duplicate group found", parse, error);
313 g_free(group);
314 return;
315 }
316 /* if it's the first group, make sure it's named Desktop Entry */
317 else if (!parse->group && strcmp(group, "Desktop Entry") != 0)
318 {
319 parse_error("Incorrect group found, "
320 "expected [Desktop Entry]",
321 parse, error);
322 g_free(group);
323 return;
324 }
325 else {
326 if (!g) {
327 g = parse_group_new(group, NULL);
328 g_hash_table_insert(parse->group_hash, g->name, g);
329 }
330 else
331 g_free(group);
332
333 g->seen = TRUE;
334 parse->group = g;
335 g_print("Found group %s\n", g->name);
336 }
337 }
338
339 static void parse_key_value(const gchar *buf, gulong len,
340 ObtDDParse *parse, gboolean *error)
341 {
342 gulong i, keyend, valstart, eq;
343 char *key;
344
345 /* find the end of the key */
346 for (i = 0; i < len; ++i)
347 if (!(((guchar)buf[i] >= 'A' && (guchar)buf[i] <= 'Z') ||
348 ((guchar)buf[i] >= 'a' && (guchar)buf[i] <= 'z') ||
349 ((guchar)buf[i] >= '0' && (guchar)buf[i] <= '9') ||
350 ((guchar)buf[i] == '-'))) {
351 /* not part of the key */
352 keyend = i;
353 break;
354 }
355 if (keyend < 1) {
356 parse_error("Empty key", parse, error);
357 return;
358 }
359 /* find the = character */
360 for (i = keyend; i < len; ++i) {
361 if (buf[i] == '=') {
362 eq = i;
363 break;
364 }
365 else if (buf[i] != ' ') {
366 parse_error("Invalid character in key name", parse, error);
367 return ;
368 }
369 }
370 if (i == len) {
371 parse_error("Key without value found", parse, error);
372 return;
373 }
374 /* find the start of the value */
375 for (i = eq+1; i < len; ++i)
376 if (buf[i] != ' ') {
377 valstart = i;
378 break;
379 }
380 if (i == len) {
381 parse_error("Empty value found", parse, error);
382 return;
383 }
384
385 key = g_strndup(buf, keyend);
386 if (g_hash_table_lookup(parse->group->key_hash, key)) {
387 parse_error("Duplicate key found", parse, error);
388 g_free(key);
389 return;
390 }
391 g_print("Found key/value %s=%s.\n", key, buf+valstart);
392 if (parse->group->value_func)
393 if (!parse->group->value_func(key, buf+valstart, parse, error)) {
394 parse_error("Unknown key", parse, error);
395 g_free(key);
396 }
397 }
398
399 static gboolean parse_file(FILE *f, ObtDDParse *parse)
400 {
401 gchar *buf = NULL;
402 gulong bytes = 0, read = 0;
403 gboolean error = FALSE;
404
405 while (!error && parse_file_line(f, &buf, &bytes, &read, parse, &error)) {
406 gulong len = strlen(buf);
407 if (buf[0] == '#' || buf[0] == '\0')
408 ; /* ignore comment lines */
409 else if (buf[0] == '[' && buf[len-1] == ']')
410 parse_group(buf, len, parse, &error);
411 else if (!parse->group)
412 /* just ignore keys outside of groups */
413 parse_error("Key found before group", parse, NULL);
414 else
415 /* ignore errors in key-value pairs and continue */
416 parse_key_value(buf, len, parse, NULL);
417 ++parse->lineno;
418 }
419
420 if (buf) g_free(buf);
421 return !error;
422 }
423
424 static gboolean parse_desktop_entry_value(gchar *key, const gchar *val,
425 ObtDDParse *parse, gboolean *error)
426 {
427 ObtDDParseValue v, *pv;
428
429 switch (key[0]) {
430 case 'C':
431 switch (key[1]) {
432 case 'a': /* Categories */
433 if (strcmp(key+2, "tegories")) return FALSE;
434 v.type = OBT_DDPARSE_STRINGS; break;
435 case 'o': /* Comment */
436 if (strcmp(key+2, "mment")) return FALSE;
437 v.type = OBT_DDPARSE_LOCALESTRING; break;
438 default:
439 return FALSE;
440 }
441 break;
442 case 'E': /* Exec */
443 if (strcmp(key+1, "xec")) return FALSE;
444 v.type = OBT_DDPARSE_STRING; break;
445 case 'G': /* GenericName */
446 if (strcmp(key+1, "enericName")) return FALSE;
447 v.type = OBT_DDPARSE_LOCALESTRING; break;
448 case 'I': /* Icon */
449 if (strcmp(key+1, "con")) return FALSE;
450 v.type = OBT_DDPARSE_LOCALESTRING; break;
451 case 'H': /* Hidden */
452 if (strcmp(key+1, "idden")) return FALSE;
453 v.type = OBT_DDPARSE_BOOLEAN; break;
454 case 'M': /* MimeType */
455 if (strcmp(key+1, "imeType")) return FALSE;
456 v.type = OBT_DDPARSE_STRINGS; break;
457 case 'N':
458 switch (key[1]) {
459 case 'a': /* Name */
460 if (strcmp(key+2, "me")) return FALSE;
461 v.type = OBT_DDPARSE_LOCALESTRING; break;
462 case 'o':
463 switch (key[2]) {
464 case 'D': /* NoDisplay */
465 if (strcmp(key+3, "isplay")) return FALSE;
466 v.type = OBT_DDPARSE_BOOLEAN; break;
467 case 't': /* NotShowIn */
468 if (strcmp(key+3, "ShowIn")) return FALSE;
469 v.type = OBT_DDPARSE_STRINGS; break;
470 default:
471 return FALSE;
472 }
473 break;
474 default:
475 return FALSE;
476 }
477 break;
478 case 'P': /* Path */
479 if (strcmp(key+1, "ath")) return FALSE;
480 v.type = OBT_DDPARSE_STRING; break;
481 case 'S': /* Path */
482 if (key[1] == 't' && key[2] == 'a' && key[3] == 'r' &&
483 key[4] == 't' && key[5] == 'u' && key[6] == 'p')
484 switch (key[7]) {
485 case 'N': /* StartupNotify */
486 if (strcmp(key+8, "otify")) return FALSE;
487 v.type = OBT_DDPARSE_BOOLEAN; break;
488 case 'W': /* StartupWMClass */
489 if (strcmp(key+8, "MClass")) return FALSE;
490 v.type = OBT_DDPARSE_STRING; break;
491 default:
492 return FALSE;
493 }
494 else
495 return FALSE;
496 break;
497 case 'T':
498 switch (key[1]) {
499 case 'e': /* Terminal */
500 if (strcmp(key+2, "rminal")) return FALSE;
501 v.type = OBT_DDPARSE_BOOLEAN; break;
502 case 'r': /* TryExec */
503 if (strcmp(key+2, "yExec")) return FALSE;
504 v.type = OBT_DDPARSE_STRING; break;
505 case 'y': /* Type */
506 if (strcmp(key+2, "pe")) return FALSE;
507 v.type = OBT_DDPARSE_STRING; break;
508 default:
509 return FALSE;
510 }
511 break;
512 case 'U': /* URL */
513 if (strcmp(key+1, "RL")) return FALSE;
514 v.type = OBT_DDPARSE_STRING; break;
515 case 'V': /* MimeType */
516 if (strcmp(key+1, "ersion")) return FALSE;
517 v.type = OBT_DDPARSE_STRING; break;
518 default:
519 return FALSE;
520 }
521
522 /* parse the value */
523 switch (v.type) {
524 case OBT_DDPARSE_STRING:
525 v.value.string = parse_value_string(val, FALSE, NULL, parse, error);
526 g_assert(v.value.string);
527 break;
528 case OBT_DDPARSE_LOCALESTRING:
529 v.value.string = parse_value_string(val, TRUE, NULL, parse, error);
530 g_assert(v.value.string);
531 break;
532 case OBT_DDPARSE_STRINGS:
533 v.value.strings.s = parse_value_string(val, FALSE, &v.value.strings.n,
534 parse, error);
535 g_assert(v.value.strings.s);
536 g_assert(v.value.strings.n);
537 break;
538 case OBT_DDPARSE_LOCALESTRINGS:
539 v.value.strings.s = parse_value_string(val, TRUE, &v.value.strings.n,
540 parse, error);
541 g_assert(v.value.strings.s);
542 g_assert(v.value.strings.n);
543 break;
544 case OBT_DDPARSE_BOOLEAN:
545 v.value.boolean = parse_value_boolean(val, parse, error);
546 break;
547 case OBT_DDPARSE_NUMERIC:
548 v.value.numeric = parse_value_numeric(val, parse, error);
549 break;
550 case OBT_DDPARSE_ENUM_APPLICATION:
551 if (val[0] == 'A' && strcmp(val+1, "pplication") == 0)
552 v.value.enumerable = OBT_LINK_TYPE_APPLICATION;
553 else if (val[0] == 'L' && strcmp(val+1, "ink") == 0)
554 v.value.enumerable = OBT_LINK_TYPE_URL;
555 else if (val[0] == 'D' && strcmp(val+1, "irectory") == 0)
556 v.value.enumerable = OBT_LINK_TYPE_DIRECTORY;
557 else {
558 parse_error("Unknown Type", parse, error);
559 return FALSE;
560 }
561 break;
562 default:
563 g_assert_not_reached();
564 }
565
566 pv = g_slice_new(ObtDDParseValue);
567 *pv = v;
568 g_hash_table_insert(parse->group->key_hash, key, pv);
569 return TRUE;
570 }
571
572 GHashTable* obt_ddparse_file(const gchar *name, GSList *paths)
573 {
574 ObtDDParse parse;
575 ObtDDParseGroup *desktop_entry;
576 GSList *it;
577 FILE *f;
578 gboolean success;
579
580 parse.filename = NULL;
581 parse.lineno = 0;
582 parse.group = NULL;
583 parse.group_hash = g_hash_table_new_full(g_str_hash,
584 g_str_equal,
585 NULL,
586 (GDestroyNotify)parse_group_free);
587
588 /* set up the groups (there's only one right now) */
589 desktop_entry = parse_group_new(g_strdup("Desktop Entry"),
590 parse_desktop_entry_value);
591 g_hash_table_insert(parse.group_hash, desktop_entry->name, desktop_entry);
592
593 success = FALSE;
594 for (it = paths; it && !success; it = g_slist_next(it)) {
595 gchar *path = g_strdup_printf("%s/%s", (char*)it->data, name);
596 if ((f = fopen(path, "r"))) {
597 parse.filename = path;
598 parse.lineno = 1;
599 success = parse_file(f, &parse);
600 fclose(f);
601 }
602 g_free(path);
603 }
604 if (!success) {
605 g_hash_table_destroy(parse.group_hash);
606 return NULL;
607 }
608 else
609 return parse.group_hash;
610 }
611
612 GHashTable* obt_ddparse_group_keys(ObtDDParseGroup *g)
613 {
614 return g->key_hash;
615 }
This page took 0.054757 seconds and 4 git commands to generate.