wmmenugen: Add file name validator function
[wmaker-crm.git] / util / wmmenugen_parse_xdg.c
blob2c90a3a3eaf9ed0efe2390ce484a3ded3c339ce1
1 /*
2 * wmmenugen - Window Maker PropList menu generator
4 * Desktop Entry Specification parser functions
6 * Copyright (c) 2010. Tamas Tevesz <ice@extreme.hu>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.1.html
26 * We will only deal with Type == "Application" entries in [Desktop Entry]
27 * groups. Since there is no passing of file name arguments or anything of
28 * the sort to applications from the menu, execname is determined as follows:
29 * - If `TryExec' is present, use that;
30 * - else use `Exec' with any switches stripped
31 * XXX: Only the first item of `Categories' is taken into consideration,
32 * which will be used as the sole category to put the entry into.
34 * Basic validation of the .desktop file is done.
37 #if __GLIBC__ && \
38 (_XOPEN_SOURCE && _XOPEN_SOURCE < 500) || \
39 !_XOPEN_SOURCE
40 #define _XOPEN_SOURCE 500 /* nftw */
41 #endif
43 #include <sys/types.h>
44 #include <sys/stat.h>
46 #include <ctype.h>
47 #include <ftw.h>
48 #if DEBUG
49 #include <errno.h>
50 #endif
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
55 #include "wmmenugen.h"
57 /* LocaleString match levels */
58 enum {
59 MATCH_DEFAULT,
60 MATCH_LANG,
61 MATCH_LANG_MODIFIER,
62 MATCH_LANG_COUNTRY,
63 MATCH_LANG_COUNTRY_MODIFIER
66 typedef struct {
67 char *Name; /* Name */ /* localestring */
68 int MatchLevel; /* LocaleString match type */ /* int */
69 char *TryExec; /* TryExec */ /* string */
70 char *Exec; /* Exec */ /* string */
71 char *Path; /* Path */ /* string */
72 int Flags; /* Flags */
73 char *Category; /* Categories (first item only) */ /* string */
74 } XDGMenuEntry;
76 static void getKey(char **target, const char *line);
77 static void getStringValue(char **target, const char *line);
78 static void getLocalizedStringValue(char **target, const char *line, int *match_level);
79 static int getBooleanValue(const char *line);
80 static int compare_matchlevel(int *current_level, const char *found_locale);
81 static Bool xdg_to_wm(XDGMenuEntry **xdg, WMMenuEntry **wmentry);
82 static void init_xdg_storage(XDGMenuEntry **xdg);
83 static void init_wm_storage(WMMenuEntry **wm);
85 void parse_xdg(const char *file, void (*addWMMenuEntryCallback)(WMMenuEntry *aEntry))
87 FILE *fp;
88 char buf[1024];
89 char *p, *tmp, *key;
90 WMMenuEntry *wm;
91 XDGMenuEntry *xdg;
92 int InGroup;
94 fp = fopen(file, "r");
95 if (!fp) {
96 #if DEBUG
97 fprintf(stderr, "Error opening file %s: %s\n", file, strerror(errno));
98 #endif
99 return;
102 xdg = (XDGMenuEntry *)wmalloc(sizeof(XDGMenuEntry));
103 wm = (WMMenuEntry *)wmalloc(sizeof(WMMenuEntry));
104 InGroup = 0;
105 memset(buf, 0, sizeof(buf));
107 while (fgets(buf, sizeof(buf), fp)) {
109 p = buf;
111 /* skip whitespaces */
112 while (isspace(*p))
113 p++;
114 /* skip comments, empty lines */
115 if (*p == '\r' || *p == '\n' || *p == '#') {
116 memset(buf, 0, sizeof(buf));
117 continue;
119 /* trim crlf */
120 buf[strcspn(buf, "\r\n")] = '\0';
121 if (strlen(buf) == 0)
122 continue;
124 if (strcmp(p, "[Desktop Entry]") == 0) {
125 if (InGroup) {
126 /* if currently processing a group, we've just hit the
127 * end of its definition, try processing it
129 if (xdg_to_wm(&xdg, &wm)) {
130 (*addWMMenuEntryCallback)(wm);
133 init_xdg_storage(&xdg);
134 init_wm_storage(&wm);
135 InGroup = 1;
136 /* start processing group */
137 memset(buf, 0, sizeof(buf));
138 continue;
141 if (!InGroup) {
142 memset(buf, 0, sizeof(buf));
143 continue;
146 getKey(&key, p);
147 if (key == NULL) { /* not `key' = `value' */
148 memset(buf, 0, sizeof(buf));
149 continue;
152 if (strcmp(key, "Type") == 0) {
153 getStringValue(&tmp, p);
154 if (strcmp(tmp, "Application") != 0)
155 InGroup = 0; /* if not application, skip current group */
156 wfree(tmp);
157 tmp = NULL;
158 } else if (strcmp(key, "Name") == 0) {
159 getLocalizedStringValue(&xdg->Name, p, &xdg->MatchLevel);
160 } else if (strcmp(key, "NoDisplay") == 0) {
161 if (getBooleanValue(p)) /* if nodisplay, skip current group */
162 InGroup = 0;
163 } else if (strcmp(key, "Hidden") == 0) {
164 if (getBooleanValue(p))
165 InGroup = 0; /* if hidden, skip current group */
166 } else if (strcmp(key, "TryExec") == 0) {
167 getStringValue(&xdg->TryExec, p);
168 } else if (strcmp(key, "Exec") == 0) {
169 getStringValue(&xdg->Exec, p);
170 } else if (strcmp(key, "Path") == 0) {
171 getStringValue(&xdg->Path, p);
172 } else if (strcmp(key, "Terminal") == 0) {
173 if (getBooleanValue(p))
174 xdg->Flags |= F_TERMINAL;
175 } else if (strcmp(key, "Categories") == 0) {
176 /* use only the first item */
177 getStringValue(&tmp, p);
178 if (tmp != NULL) {
179 tmp[strcspn(tmp, ";")] = '\0';
180 tmp[strcspn(tmp, "\t ")] = '\0';
181 xdg->Category = wstrdup(tmp);
182 wfree(tmp);
183 tmp = NULL;
187 wfree(key);
188 key = NULL;
191 fclose(fp);
193 /* at the end of the file, might as well try to menuize what we have */
194 if (xdg_to_wm(&xdg, &wm))
195 (*addWMMenuEntryCallback)(wm);
200 /* coerce an xdg entry type into a wm entry type
202 static Bool xdg_to_wm(XDGMenuEntry **xdg, WMMenuEntry **wm)
204 if (!*xdg)
205 return False;
207 if ((*xdg)->Exec || (*xdg)->TryExec) {
208 (*wm)->Flags = (*xdg)->Flags;
209 if ((*xdg)->Name) {
210 (*wm)->Name = (*xdg)->Name;
212 if ((*xdg)->Exec) {
213 if (!(*wm)->Name)
214 (*wm)->Name = (*xdg)->Exec;
215 (*wm)->CmdLine = (*xdg)->Exec;
217 if ((*xdg)->TryExec) {
218 if (!(*wm)->Name)
219 (*wm)->Name = (*xdg)->TryExec;
220 if (!(*wm)->CmdLine)
221 (*wm)->CmdLine = (*xdg)->TryExec;
224 if ((*wm)->Name && (*wm)->CmdLine)
225 return True;
227 return False;
230 /* (re-)initialize a XDGMenuEntry storage
232 static void init_xdg_storage(XDGMenuEntry **xdg)
235 if ((*xdg)->Name)
236 wfree((*xdg)->Name);
237 if ((*xdg)->TryExec)
238 wfree((*xdg)->TryExec);
239 if ((*xdg)->Exec)
240 wfree((*xdg)->Exec);
241 if ((*xdg)->Category)
242 wfree((*xdg)->Category);
243 if ((*xdg)->Path)
244 wfree((*xdg)->Path);
246 (*xdg)->Name = NULL;
247 (*xdg)->TryExec = NULL;
248 (*xdg)->Exec = NULL;
249 (*xdg)->Category = NULL;
250 (*xdg)->Path = NULL;
251 (*xdg)->Flags = 0;
252 (*xdg)->MatchLevel = -1;
255 /* (re-)initialize a WMMenuEntry storage
257 static void init_wm_storage(WMMenuEntry **wm)
259 (*wm)->Name = NULL;
260 (*wm)->CmdLine = NULL;
261 (*wm)->Flags = 0;
264 /* get a key from line. allocates target, which must be wfreed later */
265 static void getKey(char **target, const char *line)
267 const char *p;
268 int kstart, kend;
270 p = line;
272 if (strchr(p, '=') == NULL) { /* not `key' = `value' */
273 *target = NULL;
274 return;
277 kstart = 0;
279 /* skip whitespace */
280 while (isspace(*(p + kstart)))
281 kstart++;
283 /* skip up until first whitespace or '[' (localestring) or '=' */
284 kend = kstart + 1;
285 while (!isspace(*(p + kend)) && *(p + kend) != '=' && *(p + kend) != '[')
286 kend++;
288 *target = wstrndup(p + kstart, kend - kstart);
291 /* get a string value from line. allocates target, which must be wfreed later. */
292 static void getStringValue(char **target, const char *line)
294 const char *p;
295 int kstart;
297 p = line;
298 kstart = 0;
300 /* skip until after '=' */
301 while (*(p + kstart) != '=')
302 kstart++;
303 kstart++;
305 /* skip whitespace */
306 while (isspace(*(p + kstart)))
307 kstart++;
309 *target = wstrdup(p + kstart);
312 /* get a localized string value from line. allocates target, which must be wfreed later.
313 * matching is dependent on the current value of target as well as on the
314 * level the current value is matched on. guts matching algorithm is in
315 * compare_matchlevel().
317 static void getLocalizedStringValue(char **target, const char *line, int *match_level)
319 const char *p;
320 char *locale;
321 int kstart;
322 int sqbstart, sqbend;
324 p = line;
325 kstart = 0;
326 sqbstart = 0;
327 locale = NULL;
329 /* skip until after '=', mark if '[' and ']' is found */
330 while (*(p + kstart) != '=') {
331 switch (*(p + kstart)) {
332 case '[': sqbstart = kstart + 1;break;
333 case ']': sqbend = kstart; break;
334 default : break;
336 kstart++;
338 kstart++;
340 /* skip whitespace */
341 while (isspace(*(p + kstart)))
342 kstart++;
344 if (sqbstart > 0 && sqbend > sqbstart)
345 locale = wstrndup(p + sqbstart, sqbend - sqbstart);
347 /* if there is no value yet and this is the default key, return */
348 if (!*target && !locale) {
349 *match_level = MATCH_DEFAULT;
350 *target = wstrdup(p + kstart);
351 return;
354 if (compare_matchlevel(match_level, locale)) {
355 wfree(locale);
356 *target = wstrdup(p + kstart);
357 return;
360 return;
363 /* get a boolean value from line */
364 static Bool getBooleanValue(const char *line)
366 char *p;
367 int ret;
369 ret = 0;
370 getStringValue(&p, line);
371 ret = strcmp(p, "true") == 0 ? True : False;
372 wfree(p);
374 return ret;
377 /* perform locale matching by implementing the algorithm specified in
378 * xdg desktop entry specification, section "localized values for keys".
380 static Bool compare_matchlevel(int *current_level, const char *found_locale)
382 /* current key locale */
383 char *key_lang, *key_ctry, *key_enc, *key_mod;
385 parse_locale(found_locale, &key_lang, &key_ctry, &key_enc, &key_mod);
387 if (env_lang && key_lang && /* Shortcut: if key and env languages don't match, */
388 strcmp(env_lang, key_lang) != 0) /* don't even bother. This takes care of the great */
389 return False; /* majority of the cases without having to go through */
390 /* the more theoretical parts of the spec'd algo. */
392 if (!env_mod && key_mod) /* If LC_MESSAGES does not have a MODIFIER field, */
393 return False; /* then no key with a modifier will be matched. */
395 if (!env_ctry && key_ctry) /* Similarly, if LC_MESSAGES does not have a COUNTRY field, */
396 return False; /* then no key with a country specified will be matched. */
398 /* LC_MESSAGES value: lang_COUNTRY@MODIFIER */
399 if (env_lang && env_ctry && env_mod) { /* lang_COUNTRY@MODIFIER */
400 if (key_lang && key_ctry && key_mod &&
401 strcmp(env_lang, key_lang) == 0 &&
402 strcmp(env_ctry, key_ctry) == 0 &&
403 strcmp(env_mod, key_mod) == 0) {
404 *current_level = MATCH_LANG_COUNTRY_MODIFIER;
405 return True;
406 } else if (key_lang && key_ctry && /* lang_COUNTRY */
407 strcmp(env_lang, key_lang) == 0 &&
408 strcmp(env_ctry, key_ctry) == 0 &&
409 *current_level < MATCH_LANG_COUNTRY) {
410 *current_level = MATCH_LANG_COUNTRY;
411 return True;
412 } else if (key_lang && key_mod && /* lang@MODIFIER */
413 strcmp(env_lang, key_lang) == 0 &&
414 strcmp(env_mod, key_mod) == 0 &&
415 *current_level < MATCH_LANG_MODIFIER) {
416 *current_level = MATCH_LANG_MODIFIER;
417 return True;
418 } else if (key_lang && /* lang */
419 strcmp(env_lang, key_lang) == 0 &&
420 *current_level < MATCH_LANG) {
421 *current_level = MATCH_LANG;
422 return True;
423 } else {
424 return False;
428 /* LC_MESSAGES value: lang_COUNTRY */
429 if (env_lang && env_ctry) { /* lang_COUNTRY */
430 if (key_lang && key_ctry &&
431 strcmp(env_lang, key_lang) == 0 &&
432 strcmp(env_ctry, key_ctry) == 0 &&
433 *current_level < MATCH_LANG_COUNTRY) {
434 *current_level = MATCH_LANG_COUNTRY;
435 return True;
436 } else if (key_lang && /* lang */
437 strcmp(env_lang, key_lang) == 0 &&
438 *current_level < MATCH_LANG) {
439 *current_level = MATCH_LANG;
440 return True;
441 } else {
442 return False;
446 /* LC_MESSAGES value: lang@MODIFIER */
447 if (env_lang && env_mod) { /* lang@MODIFIER */
448 if (key_lang && key_mod &&
449 strcmp(env_lang, key_lang) == 0 &&
450 strcmp(env_mod, key_mod) == 0 &&
451 *current_level < MATCH_LANG_MODIFIER) {
452 *current_level = MATCH_LANG_MODIFIER;
453 return True;
454 } else if (key_lang && /* lang */
455 strcmp(env_lang, key_lang) == 0 &&
456 *current_level < MATCH_LANG) {
457 *current_level = MATCH_LANG;
458 return True;
459 } else {
460 return False;
464 /* LC_MESSAGES value: lang */
465 if (env_lang) { /* lang */
466 if (key_lang &&
467 strcmp(env_lang, key_lang) == 0 &&
468 *current_level < MATCH_LANG) {
469 *current_level = MATCH_LANG;
470 return True;
471 } else {
472 return False;
476 /* MATCH_DEFAULT is handled in getLocalizedStringValue */
478 return False;