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