Imported Upstream version 0.24
[platform/upstream/desktop-file-utils.git] / src / validate.c
1 /* validate.c: validate a desktop entry file
2  * vim: set ts=2 sw=2 et: */
3
4 /*
5  * Copyright (C) 2007-2009 Vincent Untz <vuntz@gnome.org>
6  *
7  * A really small portion of this code comes from the old validate.c.
8  * The old validate.c was Copyright (C) 2002, 2004  Red Hat, Inc.
9  * It was written by:
10  *  Mark McLoughlin <mark@skynet.ie>
11  *  Havoc Pennington <hp@pobox.com>
12  *  Ray Strode <rstrode@redhat.com>
13  *
14  * A portion of this code comes from glib (gkeyfile.c)
15  * Authors of gkeyfile.c are:
16  *  Ray Strode
17  *  Matthias Clasen
18  *
19  * This program is free software; you can redistribute it and/or
20  * modify it under the terms of the GNU General Public License
21  * as published by the Free Software Foundation; either version 2
22  * of the License, or (at your option) any later version.
23  *
24  * This program is distributed in the hope that it will be useful,
25  * but WITHOUT ANY WARRANTY; without even the implied warranty of
26  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27  * GNU General Public License for more details.
28  *
29  * You should have received a copy of the GNU General Public License
30  * along with this program; if not, write to the Free Software
31  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
32  * USA.
33  */
34
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <stdio.h>
38 #include <string.h>
39 #include <sys/stat.h>
40 #include <unistd.h>
41
42 #include <glib.h>
43 #include <glib/gstdio.h>
44
45 #include "keyfileutils.h"
46 #include "mimeutils.h"
47 #include "validate.h"
48
49 /*FIXME: document where we are stricter than the spec
50  * + only UTF-8 (so no Legacy-Mixed encoding)
51  */
52
53 /*TODO:
54  * + Lecagy-Mixed Encoding (annexe D)
55  * + The escape sequences \s, \n, \t, \r, and \\ are supported for values of
56  *   type string and localestring, meaning ASCII space, newline, tab, carriage
57  *   return, and backslash, respectively.
58  */
59
60 typedef enum {
61   INVALID_TYPE = 0,
62
63   APPLICATION_TYPE,
64   LINK_TYPE,
65   DIRECTORY_TYPE,
66
67   /* Types reserved for KDE */
68   /* since 0.9.4 */
69   SERVICE_TYPE,
70   SERVICE_TYPE_TYPE,
71   /* since 0.9.6 */
72   FSDEVICE_TYPE,
73
74   /* Deprecated types */
75   /* since 0.9.4 */
76   MIMETYPE_TYPE,
77
78   LAST_TYPE
79 } DesktopType;
80
81 typedef enum {
82   DESKTOP_STRING_TYPE,
83   DESKTOP_LOCALESTRING_TYPE,
84   DESKTOP_BOOLEAN_TYPE,
85   DESKTOP_NUMERIC_TYPE,
86   DESKTOP_STRING_LIST_TYPE,
87   DESKTOP_LOCALESTRING_LIST_TYPE,
88   /* Deprecated types */
89   /* since 0.9.6 */
90   DESKTOP_REGEXP_LIST_TYPE
91 } DesktopKeyType;
92
93 typedef struct _kf_keyvalue kf_keyvalue;
94
95 struct _kf_keyvalue {
96   char *key;
97   char *value;
98 };
99
100 typedef struct _kf_validator kf_validator;
101
102 struct _kf_validator {
103   const char  *filename;
104
105   GString     *parse_buffer;
106   gboolean     utf8_warning;
107   gboolean     cr_error;
108
109   char        *current_group;
110   GHashTable  *groups;
111   GHashTable  *current_keys;
112
113   gboolean     kde_reserved_warnings;
114   gboolean     no_deprecated_warnings;
115   gboolean     no_hints;
116
117   char        *main_group;
118   DesktopType  type;
119   char        *type_string;
120
121   gboolean     show_in;
122   GList       *application_keys;
123   GList       *link_keys;
124   GList       *fsdevice_keys;
125   GList       *mimetype_keys;
126
127   GHashTable  *action_values;
128   GHashTable  *action_groups;
129
130   gboolean     fatal_error;
131 };
132
133 static gboolean
134 validate_string_key (kf_validator *kf,
135                      const char   *key,
136                      const char   *locale,
137                      const char   *value);
138 static gboolean
139 validate_localestring_key (kf_validator *kf,
140                            const char   *key,
141                            const char   *locale,
142                            const char   *value);
143 static gboolean
144 validate_boolean_key (kf_validator *kf,
145                       const char   *key,
146                       const char   *locale,
147                       const char   *value);
148 static gboolean
149 validate_numeric_key (kf_validator *kf,
150                       const char   *key,
151                       const char   *locale,
152                       const char   *value);
153 static gboolean
154 validate_string_list_key (kf_validator *kf,
155                           const char   *key,
156                           const char   *locale,
157                           const char   *value);
158 static gboolean
159 validate_regexp_list_key (kf_validator *kf,
160                           const char   *key,
161                           const char   *locale,
162                           const char   *value);
163 static gboolean
164 validate_localestring_list_key (kf_validator *kf,
165                                 const char   *key,
166                                 const char   *locale,
167                                 const char   *value);
168
169 static gboolean
170 handle_type_key (kf_validator *kf,
171                  const char   *locale_key,
172                  const char   *value);
173 static gboolean
174 handle_version_key (kf_validator *kf,
175                     const char   *locale_key,
176                     const char   *value);
177 static gboolean
178 handle_comment_key (kf_validator *kf,
179                     const char   *locale_key,
180                     const char   *value);
181 static gboolean
182 handle_icon_key (kf_validator *kf,
183                  const char   *locale_key,
184                  const char   *value);
185 static gboolean
186 handle_show_in_key (kf_validator *kf,
187                     const char   *locale_key,
188                     const char   *value);
189 static gboolean
190 handle_desktop_exec_key (kf_validator *kf,
191                          const char   *locale_key,
192                          const char   *value);
193 static gboolean
194 handle_exec_key (kf_validator *kf,
195                  const char   *locale_key,
196                  const char   *value);
197 static gboolean
198 handle_path_key (kf_validator *kf,
199                  const char   *locale_key,
200                  const char   *value);
201 static gboolean
202 handle_mime_key (kf_validator *kf,
203                  const char   *locale_key,
204                  const char   *value);
205 static gboolean
206 handle_categories_key (kf_validator *kf,
207                        const char   *locale_key,
208                        const char   *value);
209 static gboolean
210 handle_actions_key (kf_validator *kf,
211                     const char   *locale_key,
212                     const char   *value);
213 static gboolean
214 handle_dbus_activatable_key (kf_validator *kf,
215                              const char   *locale_key,
216                              const char   *value);
217 static gboolean
218 handle_dev_key (kf_validator *kf,
219                 const char   *locale_key,
220                 const char   *value);
221 static gboolean
222 handle_mountpoint_key (kf_validator *kf,
223                        const char   *locale_key,
224                        const char   *value);
225 static gboolean
226 handle_encoding_key (kf_validator *kf,
227                      const char   *locale_key,
228                      const char   *value);
229 static gboolean
230 handle_autostart_condition_key (kf_validator *kf,
231                                 const char   *locale_key,
232                                 const char   *value);
233 static gboolean
234 handle_key_for_application (kf_validator *kf,
235                             const char   *locale_key,
236                             const char   *value);
237 static gboolean
238 handle_key_for_link (kf_validator *kf,
239                      const char   *locale_key,
240                      const char   *value);
241 static gboolean
242 handle_key_for_fsdevice (kf_validator *kf,
243                          const char   *locale_key,
244                          const char   *value);
245 static gboolean
246 handle_key_for_mimetype (kf_validator *kf,
247                          const char   *locale_key,
248                          const char   *value);
249
250 static struct {
251   DesktopType  type;
252   char        *name;
253   gboolean     kde_reserved;
254   gboolean     deprecated;
255 } registered_types[] = {
256   { APPLICATION_TYPE,  "Application", FALSE, FALSE },
257   { LINK_TYPE,         "Link",        FALSE, FALSE },
258   { DIRECTORY_TYPE,    "Directory",   FALSE, FALSE },
259   { SERVICE_TYPE,      "Service",     TRUE,  FALSE },
260   { SERVICE_TYPE_TYPE, "ServiceType", TRUE,  FALSE },
261   { FSDEVICE_TYPE,     "FSDevice",    TRUE,  FALSE },
262   { MIMETYPE_TYPE,     "MimeType",    FALSE, TRUE  }
263 };
264
265 static struct {
266   DesktopKeyType type;
267   gboolean       (* validate) (kf_validator *kf,
268                                const char   *key,
269                                const char   *locale,
270                                const char   *value);
271 } validate_for_type[] = {
272   { DESKTOP_STRING_TYPE,            validate_string_key            },
273   { DESKTOP_LOCALESTRING_TYPE,      validate_localestring_key      },
274   { DESKTOP_BOOLEAN_TYPE,           validate_boolean_key           },
275   { DESKTOP_NUMERIC_TYPE,           validate_numeric_key           },
276   { DESKTOP_STRING_LIST_TYPE,       validate_string_list_key       },
277   { DESKTOP_REGEXP_LIST_TYPE,       validate_regexp_list_key       },
278   { DESKTOP_LOCALESTRING_LIST_TYPE, validate_localestring_list_key }
279 };
280
281 typedef struct {
282   DesktopKeyType  type;
283   char           *name;
284   gboolean        required;
285   gboolean        deprecated;
286   gboolean        kde_reserved;
287   gboolean        (* handle_and_validate) (kf_validator *kf,
288                                            const char   *locale_key,
289                                            const char   *value);
290 } DesktopKeyDefinition;
291
292 static DesktopKeyDefinition registered_desktop_keys[] = {
293   { DESKTOP_STRING_TYPE,            "Type",              TRUE,  FALSE, FALSE, handle_type_key },
294   /* it is numeric according to the spec, but it's not true in previous
295    * versions of the spec. handle_version_key() will manage this */
296   { DESKTOP_STRING_TYPE,            "Version",           FALSE, FALSE, FALSE, handle_version_key },
297   { DESKTOP_LOCALESTRING_TYPE,      "Name",              TRUE,  FALSE, FALSE, NULL },
298   { DESKTOP_LOCALESTRING_TYPE,      "GenericName",       FALSE, FALSE, FALSE, NULL },
299   { DESKTOP_BOOLEAN_TYPE,           "NoDisplay",         FALSE, FALSE, FALSE, NULL },
300   { DESKTOP_LOCALESTRING_TYPE,      "Comment",           FALSE, FALSE, FALSE, handle_comment_key },
301   { DESKTOP_LOCALESTRING_TYPE,      "Icon",              FALSE, FALSE, FALSE, handle_icon_key },
302   { DESKTOP_BOOLEAN_TYPE,           "Hidden",            FALSE, FALSE, FALSE, NULL },
303   { DESKTOP_STRING_LIST_TYPE,       "OnlyShowIn",        FALSE, FALSE, FALSE, handle_show_in_key },
304   { DESKTOP_STRING_LIST_TYPE,       "NotShowIn",         FALSE, FALSE, FALSE, handle_show_in_key },
305   { DESKTOP_STRING_TYPE,            "TryExec",           FALSE, FALSE, FALSE, handle_key_for_application },
306   { DESKTOP_STRING_TYPE,            "Exec",              FALSE, FALSE, FALSE, handle_desktop_exec_key },
307   { DESKTOP_STRING_TYPE,            "Path",              FALSE, FALSE, FALSE, handle_path_key },
308   { DESKTOP_BOOLEAN_TYPE,           "Terminal",          FALSE, FALSE, FALSE, handle_key_for_application },
309   { DESKTOP_STRING_LIST_TYPE,       "MimeType",          FALSE, FALSE, FALSE, handle_mime_key },
310   { DESKTOP_STRING_LIST_TYPE,       "Categories",        FALSE, FALSE, FALSE, handle_categories_key },
311   { DESKTOP_BOOLEAN_TYPE,           "StartupNotify",     FALSE, FALSE, FALSE, handle_key_for_application },
312   { DESKTOP_STRING_TYPE,            "StartupWMClass",    FALSE, FALSE, FALSE, handle_key_for_application },
313   { DESKTOP_STRING_TYPE,            "URL",               FALSE, FALSE, FALSE, handle_key_for_link },
314   /* since 1.1 (used to be a key reserved for KDE since 0.9.4) */
315   { DESKTOP_LOCALESTRING_LIST_TYPE, "Keywords",          FALSE, FALSE, FALSE, NULL },
316   /* since 1.1 (used to be in the spec before 1.0, but was not really
317    * specified) */
318   { DESKTOP_STRING_LIST_TYPE,       "Actions",           FALSE, FALSE, FALSE, handle_actions_key },
319
320   { DESKTOP_BOOLEAN_TYPE,           "DBusActivatable",   FALSE, FALSE, FALSE, handle_dbus_activatable_key },
321
322   /* Keys reserved for KDE */
323
324   /* since 0.9.4 */
325   { DESKTOP_STRING_TYPE,            "ServiceTypes",      FALSE, FALSE, TRUE,  NULL },
326   { DESKTOP_STRING_TYPE,            "DocPath",           FALSE, FALSE, TRUE,  NULL },
327   { DESKTOP_STRING_TYPE,            "InitialPreference", FALSE, FALSE, TRUE,  NULL },
328   /* since 0.9.6 */
329   { DESKTOP_STRING_TYPE,            "Dev",               FALSE, FALSE, TRUE,  handle_dev_key },
330   { DESKTOP_STRING_TYPE,            "FSType",            FALSE, FALSE, TRUE,  handle_key_for_fsdevice },
331   { DESKTOP_STRING_TYPE,            "MountPoint",        FALSE, FALSE, TRUE,  handle_mountpoint_key },
332   { DESKTOP_BOOLEAN_TYPE,           "ReadOnly",          FALSE, FALSE, TRUE,  handle_key_for_fsdevice },
333   { DESKTOP_STRING_TYPE,            "UnmountIcon",       FALSE, FALSE, TRUE,  handle_key_for_fsdevice },
334
335   /* Deprecated keys */
336
337   /* since 0.9.3 */
338   { DESKTOP_STRING_TYPE,            "Protocols",         FALSE, TRUE,  FALSE, NULL },
339   { DESKTOP_STRING_TYPE,            "Extensions",        FALSE, TRUE,  FALSE, NULL },
340   { DESKTOP_STRING_TYPE,            "BinaryPattern",     FALSE, TRUE,  FALSE, NULL },
341   { DESKTOP_STRING_TYPE,            "MapNotify",         FALSE, TRUE,  FALSE, NULL },
342   /* since 0.9.4 */
343   { DESKTOP_REGEXP_LIST_TYPE,       "Patterns",          FALSE, TRUE,  FALSE, handle_key_for_mimetype },
344   { DESKTOP_STRING_TYPE,            "DefaultApp",        FALSE, TRUE,  FALSE, handle_key_for_mimetype },
345   { DESKTOP_STRING_TYPE,            "MiniIcon",          FALSE, TRUE,  FALSE, NULL },
346   { DESKTOP_STRING_TYPE,            "TerminalOptions",   FALSE, TRUE,  FALSE, NULL },
347   /* since 0.9.5 */
348   { DESKTOP_STRING_TYPE,            "Encoding",          FALSE, TRUE,  FALSE, handle_encoding_key },
349   { DESKTOP_LOCALESTRING_TYPE,      "SwallowTitle",      FALSE, TRUE,  FALSE, NULL },
350   { DESKTOP_STRING_TYPE,            "SwallowExec",       FALSE, TRUE,  FALSE, NULL },
351   /* since 0.9.6 */
352   { DESKTOP_STRING_LIST_TYPE,       "SortOrder",         FALSE, TRUE,  FALSE, NULL },
353   { DESKTOP_REGEXP_LIST_TYPE,       "FilePattern",       FALSE, TRUE,  FALSE, NULL },
354
355   /* Keys from other specifications */
356
357   /* Autostart spec, currently proposed; adopted by GNOME */
358   { DESKTOP_STRING_TYPE,            "AutostartCondition", FALSE, FALSE, FALSE, handle_autostart_condition_key }
359 };
360
361 static DesktopKeyDefinition registered_action_keys[] = {
362   { DESKTOP_LOCALESTRING_TYPE,      "Name",               TRUE,  FALSE, FALSE, NULL },
363   { DESKTOP_LOCALESTRING_TYPE,      "Icon",               FALSE, FALSE, FALSE, handle_icon_key },
364   { DESKTOP_STRING_LIST_TYPE,       "OnlyShowIn",         FALSE, TRUE, FALSE, handle_show_in_key },
365   { DESKTOP_STRING_LIST_TYPE,       "NotShowIn",          FALSE, TRUE, FALSE, handle_show_in_key },
366   { DESKTOP_STRING_TYPE,            "Exec",               TRUE,  FALSE, FALSE, handle_exec_key }
367 };
368
369 /* This should be the same list as in xdg-specs/menu/menu-spec.xml */
370 static const char *show_in_registered[] = {
371     "GNOME", "KDE", "LXDE", "LXQt", "MATE", "Razor", "ROX", "TDE", "Unity", "XFCE", "EDE", "Cinnamon", "Pantheon", "Budgie", "Enlightenment", "Deepin", "Old"
372 };
373
374 static struct {
375   const char   *name;
376   const char   *first_arg[3];
377   unsigned int  additional_args;
378 } registered_autostart_condition[] = {
379   { "GNOME",     { NULL }, 1 },
380   { "GNOME3",    { "if-session", "unless-session", NULL }, 1},
381   { "GSettings", { NULL }, 2 }
382 };
383
384 static struct {
385   const char *name;
386   gboolean    main;
387   gboolean    require_only_show_in;
388   gboolean    deprecated;
389   const char *requires[2];
390   const char *suggests[4];
391 } registered_categories[] = {
392   { "AudioVideo",             TRUE,  FALSE, FALSE, { NULL }, { NULL } },
393   { "Audio",                  TRUE,  FALSE, FALSE, { "AudioVideo", NULL }, { NULL } },
394   { "Video",                  TRUE,  FALSE, FALSE, { "AudioVideo", NULL }, { NULL } },
395   { "Development",            TRUE,  FALSE, FALSE, { NULL }, { NULL } },
396   { "Education",              TRUE,  FALSE, FALSE, { NULL }, { NULL } },
397   { "Game",                   TRUE,  FALSE, FALSE, { NULL }, { NULL } },
398   { "Graphics",               TRUE,  FALSE, FALSE, { NULL }, { NULL } },
399   { "Network",                TRUE,  FALSE, FALSE, { NULL }, { NULL } },
400   { "Office",                 TRUE,  FALSE, FALSE, { NULL }, { NULL } },
401   { "Science",                TRUE,  FALSE, FALSE, { NULL }, { NULL } },
402   { "Settings",               TRUE,  FALSE, FALSE, { NULL }, { NULL } },
403   { "System",                 TRUE,  FALSE, FALSE, { NULL }, { NULL } },
404   { "Utility",                TRUE,  FALSE, FALSE, { NULL }, { NULL } },
405   { "Audio",                  FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
406   { "Video",                  FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
407   { "Building",               FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
408   { "Debugger",               FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
409   { "IDE",                    FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
410   { "GUIDesigner",            FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
411   { "Profiling",              FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
412   { "RevisionControl",        FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
413   { "Translation",            FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
414   { "Calendar",               FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
415   { "ContactManagement",      FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
416   { "Database",               FALSE, FALSE, FALSE, { NULL }, { "Office", "Development", "AudioVideo", NULL } },
417   { "Dictionary",             FALSE, FALSE, FALSE, { NULL }, { "Office", "TextTools", NULL } },
418   { "Chart",                  FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
419   { "Email",                  FALSE, FALSE, FALSE, { NULL }, { "Office", "Network", NULL } },
420   { "Finance",                FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
421   { "FlowChart",              FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
422   { "PDA",                    FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
423   { "ProjectManagement",      FALSE, FALSE, FALSE, { NULL }, { "Office", "Development", NULL } },
424   { "Presentation",           FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
425   { "Spreadsheet",            FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
426   { "WordProcessor",          FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
427   { "2DGraphics",             FALSE, FALSE, FALSE, { NULL }, { "Graphics", NULL } },
428   { "VectorGraphics",         FALSE, FALSE, FALSE, { NULL }, { "Graphics;2DGraphics", NULL } },
429   { "RasterGraphics",         FALSE, FALSE, FALSE, { NULL }, { "Graphics;2DGraphics", NULL } },
430   { "3DGraphics",             FALSE, FALSE, FALSE, { NULL }, { "Graphics", NULL } },
431   { "Scanning",               FALSE, FALSE, FALSE, { NULL }, { "Graphics", NULL } },
432   { "OCR",                    FALSE, FALSE, FALSE, { NULL }, { "Graphics;Scanning", NULL } },
433   { "Photography",            FALSE, FALSE, FALSE, { NULL }, { "Graphics", "Office", NULL } },
434   { "Publishing",             FALSE, FALSE, FALSE, { NULL }, { "Graphics", "Office", NULL } },
435   { "Viewer",                 FALSE, FALSE, FALSE, { NULL }, { "Graphics", "Office", NULL } },
436   { "TextTools",              FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
437   { "DesktopSettings",        FALSE, FALSE, FALSE, { NULL }, { "Settings", NULL } },
438   { "HardwareSettings",       FALSE, FALSE, FALSE, { NULL }, { "Settings", NULL } },
439   { "Printing",               FALSE, FALSE, FALSE, { NULL }, { "HardwareSettings;Settings", NULL } },
440   { "PackageManager",         FALSE, FALSE, FALSE, { NULL }, { "Settings", NULL } },
441   { "Dialup",                 FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
442   { "InstantMessaging",       FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
443   { "Chat",                   FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
444   { "IRCClient",              FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
445   { "Feed",                   FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
446   { "FileTransfer",           FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
447   { "HamRadio",               FALSE, FALSE, FALSE, { NULL }, { "Network", "Audio", NULL } },
448   { "News",                   FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
449   { "P2P",                    FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
450   { "RemoteAccess",           FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
451   { "Telephony",              FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
452   { "TelephonyTools",         FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
453   { "VideoConference",        FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
454   { "WebBrowser",             FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
455   { "WebDevelopment",         FALSE, FALSE, FALSE, { NULL }, { "Network", "Development", NULL } },
456   { "Midi",                   FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
457   { "Mixer",                  FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
458   { "Sequencer",              FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
459   { "Tuner",                  FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
460   { "TV",                     FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Video", NULL } },
461   { "AudioVideoEditing",      FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
462   { "Player",                 FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
463   { "Recorder",               FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
464   { "DiscBurning",            FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
465   { "ActionGame",             FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
466   { "AdventureGame",          FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
467   { "ArcadeGame",             FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
468   { "BoardGame",              FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
469   { "BlocksGame",             FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
470   { "CardGame",               FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
471   { "KidsGame",               FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
472   { "LogicGame",              FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
473   { "RolePlaying",            FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
474   { "Shooter",                FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
475   { "Simulation",             FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
476   { "SportsGame",             FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
477   { "StrategyGame",           FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
478   { "Art",                    FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
479   { "Construction",           FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
480   { "Music",                  FALSE, FALSE, FALSE, { NULL }, { "AudioVideo", "Education", NULL } },
481   { "Languages",              FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
482   { "ArtificialIntelligence", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
483   { "Astronomy",              FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
484   { "Biology",                FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
485   { "Chemistry",              FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
486   { "ComputerScience",        FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
487   { "DataVisualization",      FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
488   { "Economy",                FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
489   { "Electricity",            FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
490   { "Geography",              FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
491   { "Geology",                FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
492   { "Geoscience",             FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
493   { "History",                FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
494   { "Humanities",             FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
495   { "ImageProcessing",        FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
496   { "Literature",             FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
497   { "Maps",                   FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", "Utility", NULL } },
498   { "Math",                   FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
499   { "NumericalAnalysis",      FALSE, FALSE, FALSE, { NULL }, { "Education;Math", "Science;Math", NULL } },
500   { "MedicalSoftware",        FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
501   { "Physics",                FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
502   { "Robotics",               FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
503   { "Spirituality",           FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", "Utility", NULL } },
504   { "Sports",                 FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
505   { "ParallelComputing",      FALSE, FALSE, FALSE, { NULL }, { "Education;ComputerScience", "Science;ComputerScience", NULL } },
506   { "Amusement",              FALSE, FALSE, FALSE, { NULL }, { NULL } },
507   { "Archiving",              FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
508   { "Compression",            FALSE, FALSE, FALSE, { NULL }, { "Utility;Archiving", NULL } },
509   { "Electronics",            FALSE, FALSE, FALSE, { NULL }, { NULL } },
510   { "Emulator",               FALSE, FALSE, FALSE, { NULL }, { "System", "Game", NULL } },
511   { "Engineering",            FALSE, FALSE, FALSE, { NULL }, { NULL } },
512   { "FileTools",              FALSE, FALSE, FALSE, { NULL }, { "Utility", "System", NULL } },
513   { "FileManager",            FALSE, FALSE, FALSE, { NULL }, { "System;FileTools", NULL } },
514   { "TerminalEmulator",       FALSE, FALSE, FALSE, { NULL }, { "System", NULL } },
515   { "Filesystem",             FALSE, FALSE, FALSE, { NULL }, { "System", NULL } },
516   { "Monitor",                FALSE, FALSE, FALSE, { NULL }, { "System", "Network", NULL } },
517   { "Security",               FALSE, FALSE, FALSE, { NULL }, { "Settings", "System", NULL } },
518   { "Accessibility",          FALSE, FALSE, FALSE, { NULL }, { "Settings", "Utility", NULL } },
519   { "Calculator",             FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
520   { "Clock",                  FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
521   { "TextEditor",             FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
522   { "Documentation",          FALSE, FALSE, FALSE, { NULL }, { NULL } },
523   { "Adult",                  FALSE, FALSE, FALSE, { NULL }, { NULL } },
524   { "Core",                   FALSE, FALSE, FALSE, { NULL }, { NULL } },
525   { "KDE",                    FALSE, FALSE, FALSE, { NULL }, { "Qt", NULL } },
526   { "GNOME",                  FALSE, FALSE, FALSE, { NULL }, { "GTK", NULL } },
527   { "XFCE",                   FALSE, FALSE, FALSE, { NULL }, { "GTK", NULL } },
528   { "GTK",                    FALSE, FALSE, FALSE, { NULL }, { NULL } },
529   { "Qt",                     FALSE, FALSE, FALSE, { NULL }, { NULL } },
530   { "Motif",                  FALSE, FALSE, FALSE, { NULL }, { NULL } },
531   { "Java",                   FALSE, FALSE, FALSE, { NULL }, { NULL } },
532   { "ConsoleOnly",            FALSE, FALSE, FALSE, { NULL }, { NULL } },
533   { "Screensaver",            FALSE, TRUE,  FALSE, { NULL }, { NULL } },
534   { "TrayIcon",               FALSE, TRUE,  FALSE, { NULL }, { NULL } },
535   { "Applet",                 FALSE, TRUE,  FALSE, { NULL }, { NULL } },
536   { "Shell",                  FALSE, TRUE,  FALSE, { NULL }, { NULL } },
537   { "Application",            FALSE, FALSE, TRUE,  { NULL }, { NULL } },
538   { "Applications",           FALSE, FALSE, TRUE,  { NULL }, { NULL } }
539 };
540
541 static void
542 print_fatal (kf_validator *kf, const char *format, ...)
543 {
544   va_list args;
545   gchar *str;
546
547   g_return_if_fail (kf != NULL && format != NULL);
548
549   kf->fatal_error = TRUE;
550
551   va_start (args, format);
552   str = g_strdup_vprintf (format, args);
553   va_end (args);
554
555   g_print ("%s: error: %s", kf->filename, str);
556
557   g_free (str);
558 }
559
560 static void
561 print_future_fatal (kf_validator *kf, const char *format, ...)
562 {
563   va_list args;
564   gchar *str;
565
566   g_return_if_fail (kf != NULL && format != NULL);
567
568   va_start (args, format);
569   str = g_strdup_vprintf (format, args);
570   va_end (args);
571
572   g_print ("%s: error: (will be fatal in the future): %s", kf->filename, str);
573
574   g_free (str);
575 }
576
577 static void
578 print_warning (kf_validator *kf, const char *format, ...)
579 {
580   va_list args;
581   gchar *str;
582
583   g_return_if_fail (kf != NULL && format != NULL);
584
585   va_start (args, format);
586   str = g_strdup_vprintf (format, args);
587   va_end (args);
588
589   g_print ("%s: warning: %s", kf->filename, str);
590
591   g_free (str);
592 }
593
594 static void
595 print_hint (kf_validator *kf, const char *format, ...)
596 {
597   va_list args;
598   gchar *str;
599
600   g_return_if_fail (kf != NULL && format != NULL);
601
602   if (kf->no_hints)
603     return;
604
605   va_start (args, format);
606   str = g_strdup_vprintf (format, args);
607   va_end (args);
608
609   g_print ("%s: hint: %s", kf->filename, str);
610
611   g_free (str);
612 }
613
614 /* + Key names must contain only the characters A-Za-z0-9-.
615  *   Checked.
616  */
617 static gboolean
618 key_is_valid (const char *key,
619               int         len)
620 {
621   char c;
622   int i;
623
624   for (i = 0; i < len; i++) {
625     c = key[i];
626     if (!g_ascii_isalnum (c) && c != '-')
627       return FALSE;
628   }
629
630   return TRUE;
631 }
632
633 /* + Values of type string may contain all ASCII characters except for control
634  *   characters.
635  *   Checked.
636  */
637 static gboolean
638 validate_string_key (kf_validator *kf,
639                      const char   *key,
640                      const char   *locale,
641                      const char   *value)
642 {
643   int      i;
644   gboolean error;
645
646   error = FALSE;
647
648   for (i = 0; value[i] != '\0'; i++) {
649     if (g_ascii_iscntrl (value[i])) {
650       error = TRUE;
651       break;
652     }
653   }
654
655   if (error) {
656     print_fatal (kf, "value \"%s\" for string key \"%s\" in group \"%s\" "
657                      "contains invalid characters, string values may contain "
658                      "all ASCII characters except for control characters\n",
659                      value, key, kf->current_group);
660
661     return FALSE;
662   }
663
664   return TRUE;
665 }
666
667 /* + Values of type localestring are user displayable, and are encoded in
668  *   UTF-8.
669  *   Checked.
670  * + If a postfixed key occurs, the same key must be also present without the
671  *   postfix.
672  *   Checked.
673  */
674 static gboolean
675 validate_localestring_key (kf_validator *kf,
676                            const char   *key,
677                            const char   *locale,
678                            const char   *value)
679 {
680   char *locale_key;
681
682   if (locale)
683     locale_key = g_strdup_printf ("%s[%s]", key, locale);
684   else
685     locale_key = g_strdup_printf ("%s", key);
686
687   if (!g_utf8_validate (value, -1, NULL)) {
688     print_fatal (kf, "value \"%s\" for locale string key \"%s\" in group "
689                      "\"%s\" contains invalid UTF-8 characters, locale string "
690                      "values should be encoded in UTF-8\n",
691                      value, locale_key, kf->current_group);
692     g_free (locale_key);
693
694     return FALSE;
695   }
696
697   if (!g_hash_table_lookup (kf->current_keys, key)) {
698     print_fatal (kf, "key \"%s\" in group \"%s\" is a localized key, but "
699                      "there is no non-localized key \"%s\"\n",
700                      locale_key, kf->current_group, key);
701     g_free (locale_key);
702
703     return FALSE;
704   }
705
706   g_free (locale_key);
707
708   return TRUE;
709 }
710
711 /* + Values of type boolean must either be the string true or false.
712  *   Checked.
713  * + Historically some booleans have been represented by the numeric entries 0
714  *   or 1. With this version of the standard they are now to be represented as
715  *   a boolean string. However, if an implementation is reading a pre-1.0
716  *   desktop entry, it should interpret 0 and 1 as false and true,
717  *   respectively.
718  *   Checked.
719  */
720 static gboolean
721 validate_boolean_key (kf_validator *kf,
722                       const char   *key,
723                       const char   *locale,
724                       const char   *value)
725 {
726   if (strcmp (value, "true") && strcmp (value, "false") &&
727       strcmp (value, "0")    && strcmp (value, "1")) {
728     print_fatal (kf, "value \"%s\" for boolean key \"%s\" in group \"%s\" "
729                      "contains invalid characters, boolean values must be "
730                      "\"false\" or \"true\"\n",
731                      value, key, kf->current_group);
732     return FALSE;
733   }
734
735   if (!kf->no_deprecated_warnings &&
736       (!strcmp (value, "0") || !strcmp (value, "1")))
737     print_warning (kf, "boolean key \"%s\" in group \"%s\" has value \"%s\", "
738                        "which is deprecated: boolean values should be "
739                        "\"false\" or \"true\"\n",
740                        key, kf->current_group, value);
741
742   return TRUE;
743 }
744
745 /* + Values of type numeric must be a valid floating point number as recognized
746  *   by the %f specifier for scanf.
747  *   Checked.
748  */
749 static gboolean
750 validate_numeric_key (kf_validator *kf,
751                       const char   *key,
752                       const char   *locale,
753                       const char   *value)
754 {
755   float d;
756   int res;
757
758   res = sscanf (value, "%f", &d);
759   if (res == 0) {
760     print_fatal (kf, "value \"%s\" for numeric key \"%s\" in group \"%s\" "
761                      "contains invalid characters, numeric values must be "
762                      "valid floating point numbers\n",
763                      value, key, kf->current_group);
764     return FALSE;
765   }
766
767   return TRUE;
768 }
769
770 /* + Values of type string may contain all ASCII characters except for control
771  *   characters.
772  *   Checked.
773  * + FIXME: how should an empty list be handled?
774  */
775 static gboolean
776 validate_string_regexp_list_key (kf_validator *kf,
777                                  const char   *key,
778                                  const char   *locale,
779                                  const char   *value,
780                                  const char   *type)
781 {
782   int      i;
783   gboolean error;
784
785   error = FALSE;
786
787   for (i = 0; value[i] != '\0'; i++) {
788     if (g_ascii_iscntrl (value[i])) {
789       error = TRUE;
790       break;
791     }
792   }
793
794   if (error) {
795     print_fatal (kf, "value \"%s\" for %s list key \"%s\" in group \"%s\" "
796                      "contains invalid character '%c', %s list values may "
797                      "contain all ASCII characters except for control "
798                      "characters\n",
799                      value, type, key, kf->current_group, value[i], type);
800
801     return FALSE;
802   }
803
804   return TRUE;
805 }
806
807 static gboolean
808 validate_string_list_key (kf_validator *kf,
809                           const char   *key,
810                           const char   *locale,
811                           const char   *value)
812 {
813   return validate_string_regexp_list_key (kf, key, locale, value, "string");
814 }
815
816 static gboolean
817 validate_regexp_list_key (kf_validator *kf,
818                           const char   *key,
819                           const char   *locale,
820                           const char   *value)
821 {
822   return validate_string_regexp_list_key (kf, key, locale, value, "regexp");
823 }
824
825 /* + Values of type localestring are user displayable, and are encoded in
826  *   UTF-8.
827  *   FIXME: partly checked; we checked the whole value is encored in UTF-8, but
828  *   not that each value of the list is. Although this might be equivalent?
829  * + If a postfixed key occurs, the same key must be also present without the
830  *   postfix.
831  *   Checked.
832  * + FIXME: how should an empty list be handled?
833  */
834 static gboolean
835 validate_localestring_list_key (kf_validator *kf,
836                                 const char   *key,
837                                 const char   *locale,
838                                 const char   *value)
839 {
840   char     *locale_key;
841
842   if (locale)
843     locale_key = g_strdup_printf ("%s[%s]", key, locale);
844   else
845     locale_key = g_strdup_printf ("%s", key);
846
847
848   if (!g_utf8_validate (value, -1, NULL)) {
849     print_fatal (kf, "value \"%s\" for locale string list key \"%s\" in group "
850                      "\"%s\" contains invalid UTF-8 characters, locale string "
851                      "list values should be encoded in UTF-8\n",
852                      value, locale_key, kf->current_group);
853     g_free (locale_key);
854
855     return FALSE;
856   }
857
858   if (!g_hash_table_lookup (kf->current_keys, key)) {
859     print_fatal (kf, "key \"%s\" in group \"%s\" is a localized key, but "
860                      "there is no non-localized key \"%s\"\n",
861                      locale_key, kf->current_group, key);
862     g_free (locale_key);
863
864     return FALSE;
865   }
866
867   g_free (locale_key);
868
869   return TRUE;
870 }
871
872 /* + This specification defines 3 types of desktop entries: Application
873  *   (type 1), Link (type 2) and Directory (type 3). To allow the addition of
874  *   new types in the future, implementations should ignore desktop entries
875  *   with an unknown type.
876  *   Checked.
877  * + KDE specific types: ServiceType, Service and FSDevice
878  *   Checked.
879  */
880 static gboolean
881 handle_type_key (kf_validator *kf,
882                  const char   *locale_key,
883                  const char   *value)
884 {
885   unsigned int i;
886
887   for (i = 0; i < G_N_ELEMENTS (registered_types); i++) {
888     if (!strcmp (value, registered_types[i].name))
889       break;
890   }
891
892   if (i == G_N_ELEMENTS (registered_types)) {
893     /* force the type, since the key might be present multiple times... */
894     kf->type = INVALID_TYPE;
895
896     print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
897                      "is not a registered type value (\"Application\", "
898                      "\"Link\" and \"Directory\")\n",
899                      value, locale_key, kf->current_group);
900     return FALSE;
901   }
902
903   if (registered_types[i].kde_reserved && kf->kde_reserved_warnings)
904     print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
905                        "is a reserved value for KDE\n",
906                        value, locale_key, kf->current_group);
907
908   if (registered_types[i].deprecated && !kf->no_deprecated_warnings)
909     print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
910                        "is deprecated\n",
911                        value, locale_key, kf->current_group);
912
913   kf->type = registered_types[i].type;
914   kf->type_string = registered_types[i].name;
915
916   return TRUE;
917 }
918
919 /* + Entries that confirm with this version of the specification should use
920  *   1.0.
921  *   Checked.
922  * + Previous versions of the spec: 0.9.x where 3 <= x <= 8
923  *   Checked.
924  */
925 static gboolean
926 handle_version_key (kf_validator *kf,
927                     const char   *locale_key,
928                     const char   *value)
929 {
930   if (!strcmp (value, "1.2"))
931     return TRUE;
932
933   if (!strcmp (value, "1.1"))
934     return TRUE;
935
936   if (!strcmp (value, "1.0"))
937     return TRUE;
938
939   if (!strncmp (value, "0.9.", strlen ("0.9."))) {
940     char c;
941
942     c = value[strlen ("0.9.")];
943     if ('3' <= c && c <= '8' && value[strlen ("0.9.") + 1] == '\0')
944       return TRUE;
945   }
946
947   print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
948                    "is not a known version\n",
949                    value, locale_key, kf->current_group);
950   return FALSE;
951 }
952
953 /* + Tooltip for the entry, for example "View sites on the Internet", should
954  *   not be redundant with Name or GenericName.
955  *   Checked.
956  */
957 static gboolean
958 handle_comment_key (kf_validator *kf,
959                     const char   *locale_key,
960                     const char   *value)
961 {
962   char        *locale_compare_key;
963   kf_keyvalue *keyvalue;
964
965   locale_compare_key = g_strdup_printf ("Name%s",
966                                         locale_key + strlen ("Comment"));
967   keyvalue = g_hash_table_lookup (kf->current_keys, locale_compare_key);
968   g_free (locale_compare_key);
969
970   if (keyvalue && g_ascii_strcasecmp (value, keyvalue->value) == 0) {
971     print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
972                        "looks redundant with value \"%s\" of key \"%s\"\n",
973                        value, locale_key, kf->current_group,
974                        keyvalue->value, keyvalue->key);
975     return FALSE;
976   }
977
978   locale_compare_key = g_strdup_printf ("GenericName%s",
979                                         locale_key + strlen ("Comment"));
980   keyvalue = g_hash_table_lookup (kf->current_keys, locale_compare_key);
981   g_free (locale_compare_key);
982
983   if (keyvalue && g_ascii_strcasecmp (value, keyvalue->value) == 0) {
984     print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
985                        "looks redundant with value \"%s\" of key \"%s\"\n",
986                        value, locale_key, kf->current_group,
987                        keyvalue->value, keyvalue->key);
988     return FALSE;
989   }
990
991   return TRUE;
992 }
993
994 /* + If the name is an absolute path, the given file will be used.
995  *   Checked.
996  * + If the name is not an absolute path, the algorithm described in the Icon
997  *   Theme Specification will be used to locate the icon.
998  *   Checked.
999  *   FIXME: add clarification to desktop entry spec that the name doesn't
1000  *   contain an extension
1001  */
1002 static gboolean
1003 handle_icon_key (kf_validator *kf,
1004                  const char   *locale_key,
1005                  const char   *value)
1006 {
1007   if (g_path_is_absolute (value)) {
1008     if (g_str_has_suffix (value, "/")) {
1009       print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" is an "
1010                        "absolute path to a directory, instead of being an "
1011                        "absolute path to an icon or an icon name\n",
1012                        value, locale_key, kf->current_group);
1013       return FALSE;
1014     } else
1015       return TRUE;
1016   }
1017
1018   if (g_utf8_strchr (value, -1, G_DIR_SEPARATOR)) {
1019     print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" looks like "
1020                      "a relative path, instead of being an absolute path to "
1021                      "an icon or an icon name\n",
1022                      value, locale_key, kf->current_group);
1023     return FALSE;
1024   }
1025
1026   if (g_str_has_suffix (value, ".png") ||
1027       g_str_has_suffix (value, ".xpm") ||
1028       g_str_has_suffix (value, ".svg")) {
1029     print_future_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" is an "
1030                             "icon name with an extension, but there should be "
1031                             "no extension as described in the Icon Theme "
1032                             "Specification if the value is not an absolute "
1033                             "path\n",
1034                             value, locale_key, kf->current_group);
1035     return FALSE;
1036   }
1037
1038   return TRUE;
1039 }
1040
1041 /* + Only one of these keys, either OnlyShowIn or NotShowIn, may appear in a
1042  *   group.
1043  *   Checked.
1044  * + (for possible values see the Desktop Menu Specification)
1045  *   Checked.
1046  *   FIXME: this is not perfect because it could fail if a new value with
1047  *   a semicolon is registered.
1048  * + All values extending the format should start with "X-".
1049  *   Checked.
1050  * + FIXME: is this okay to have only ";"? (gnome-theme-installer.desktop does)
1051  */
1052 static gboolean
1053 handle_show_in_key (kf_validator *kf,
1054                     const char   *locale_key,
1055                     const char   *value)
1056 {
1057   gboolean       retval;
1058   char         **show;
1059   GHashTable    *hashtable;
1060   int            i;
1061   unsigned int   j;
1062
1063   retval = TRUE;
1064
1065   if (kf->show_in) {
1066     print_fatal (kf, "only one of \"OnlyShowIn\" and \"NotShowIn\" keys "
1067                      "may appear in group \"%s\"\n",
1068                      kf->current_group);
1069     retval = FALSE;
1070   }
1071   kf->show_in = TRUE;
1072
1073   hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
1074   show = g_strsplit (value, ";", 0);
1075
1076   for (i = 0; show[i]; i++) {
1077     /* since the value ends with a semicolon, we'll have an empty string
1078      * at the end */
1079     if (*show[i] == '\0' && show[i + 1] == NULL)
1080       break;
1081
1082     if (g_hash_table_lookup (hashtable, show[i])) {
1083       print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1084                          "contains \"%s\" more than once\n",
1085                          value, locale_key, kf->current_group, show[i]);
1086       continue;
1087     }
1088
1089     g_hash_table_insert (hashtable, show[i], show[i]);
1090
1091     if (!strncmp (show[i], "X-", 2))
1092       continue;
1093
1094     for (j = 0; j < G_N_ELEMENTS (show_in_registered); j++) {
1095       if (!strcmp (show[i], show_in_registered[j]))
1096         break;
1097     }
1098
1099     if (j == G_N_ELEMENTS (show_in_registered)) {
1100       print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1101                        "contains an unregistered value \"%s\"; values "
1102                        "extending the format should start with \"X-\"\n",
1103                        value, locale_key, kf->current_group, show[i]);
1104       retval = FALSE;
1105     }
1106   }
1107
1108   g_strfreev (show);
1109   g_hash_table_destroy (hashtable);
1110
1111   return retval;
1112 }
1113
1114 /* + A command line consists of an executable program optionally followed by
1115  *   one or more arguments. The executable program can either be specified with
1116  *   its full path or with the name of the executable only. If no full path is
1117  *   provided the executable is looked up in the $PATH used by the desktop
1118  *   environment. The name or path of the executable program may not contain
1119  *   the equal sign ("=").
1120  *   FIXME
1121  * + Arguments are separated by a space.
1122  *   FIXME
1123  * + Arguments may be quoted in whole.
1124  *   FIXME
1125  * + If an argument contains a reserved character the argument must be quoted.
1126  *   Checked.
1127  * + The rules for quoting of arguments is also applicable to the executable
1128  *   name or path of the executable program as provided.
1129  *   FIXME
1130  * + Quoting must be done by enclosing the argument between double quotes and
1131  *   escaping the double quote character, backtick character ("`"), dollar sign
1132  *   ("$") and backslash character ("\") by preceding it with an additional
1133  *   backslash character. Implementations must undo quoting before expanding
1134  *   field codes and before passing the argument to the executable program.
1135  *   Reserved characters are space (" "), tab, newline, double quote, single
1136  *   quote ("'"), backslash character ("\"), greater-than sign (">"), less-than
1137  *   sign ("<"), tilde ("~"), vertical bar ("|"), ampersand ("&"), semicolon
1138  *   (";"), dollar sign ("$"), asterisk ("*"), question mark ("?"), hash mark
1139  *   ("#"), parenthesis ("(") and (")") and backtick character ("`").
1140  *   Checked.
1141  * + Note that the general escape rule for values of type string states that
1142  *   the backslash character can be escaped as ("\\") as well and that this
1143  *   escape rule is applied before the quoting rule. As such, to unambiguously
1144  *   represent a literal backslash character in a quoted argument in a desktop
1145  *   entry file requires the use of four successive backslash characters
1146  *   ("\\\\"). Likewise, a literal dollar sign in a quoted argument in a
1147  *   desktop entry file is unambiguously represented with ("\\$").
1148  *   Checked.
1149  * + Field codes consist of the percentage character ("%") followed by an alpha
1150  *   character. Literal percentage characters must be escaped as %%.
1151  *   Checked.
1152  * + Command lines that contain a field code that is not listed in this
1153  *   specification are invalid and must not be processed, in particular
1154  *   implementations may not introduce support for field codes not listed in
1155  *   this specification. Extensions, if any, should be introduced by means of a
1156  *   new key.
1157  *   Checked.
1158  * + A command line may contain at most one %f, %u, %F or %U field code.
1159  *   Checked.
1160  * + The %F and %U field codes may only be used as an argument on their own.
1161  *   FIXME
1162  */
1163 static gboolean
1164 handle_exec_key (kf_validator *kf,
1165                  const char   *locale_key,
1166                  const char   *value)
1167 {
1168   gboolean    retval;
1169   gboolean    file_uri;
1170   gboolean    in_quote;
1171   gboolean    escaped;
1172   gboolean    flag;
1173   const char *c;
1174
1175   retval = TRUE;
1176
1177   file_uri = FALSE;
1178   in_quote = FALSE;
1179   escaped  = FALSE;
1180   flag     = FALSE;
1181
1182 #define PRINT_INVALID_IF_FLAG                                       \
1183   if (flag) {                                                       \
1184     print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " \
1185                      "contains an invalid field code \"%%%c\"\n",   \
1186                      value, locale_key, kf->current_group, *c);     \
1187     retval = FALSE;                                                 \
1188     flag = FALSE;                                                   \
1189     break;                                                          \
1190   }
1191
1192   c = value;
1193   while (*c) {
1194     switch (*c) {
1195       /* quotes and escaped characters in quotes */
1196       case '"':
1197         PRINT_INVALID_IF_FLAG;
1198         if (in_quote) {
1199           if (!escaped)
1200             in_quote = FALSE;
1201         } else {
1202           if (!escaped)
1203             in_quote = TRUE;
1204           else {
1205             print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1206                              "contains an escaped double quote (\\\\\") "
1207                              "outside of a quote, but the double quote is "
1208                              "a reserved character\n",
1209                              value, locale_key, kf->current_group);
1210             retval = FALSE;
1211
1212             escaped = FALSE;
1213           }
1214         }
1215         break;
1216       case '`':
1217       case '$':
1218         PRINT_INVALID_IF_FLAG;
1219         if (in_quote) {
1220           if (!escaped) {
1221             print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1222                              "contains a non-escaped character '%c' in a "
1223                              "quote, but it should be escaped with two "
1224                              "backslashes (\"\\\\%c\")\n",
1225                              value, locale_key, kf->current_group, *c, *c);
1226             retval = FALSE;
1227           } else
1228             escaped = FALSE;
1229         } else {
1230           print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1231                            "contains a reserved character '%c' outside of a "
1232                            "quote\n",
1233                            value, locale_key, kf->current_group, *c);
1234           retval = FALSE;
1235         }
1236         break;
1237       case '\\':
1238         PRINT_INVALID_IF_FLAG;
1239
1240         /* Escape character immediately followed by \0? */
1241         if (*(c + 1) == '\0') {
1242           print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1243                            "ends in an incomplete escape sequence\n",
1244                            value, locale_key, kf->current_group);
1245           retval = FALSE;
1246           break;
1247         }
1248
1249         c++;
1250         if (*c == '\\' && in_quote)
1251           escaped = !escaped;
1252         break;
1253
1254       /* reserved characters */
1255       case ' ':
1256         //FIXME
1257         break;
1258       case '\t':
1259       case '\n':
1260       case '\'':
1261       case '>':
1262       case '<':
1263       case '~':
1264       case '|':
1265       case '&':
1266       case ';':
1267       case '*':
1268       case '?':
1269       case '#':
1270       case '(':
1271       case ')':
1272         PRINT_INVALID_IF_FLAG;
1273         if (!in_quote) {
1274           print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1275                            "contains a reserved character '%c' outside of a "
1276                            "quote\n",
1277                            value, locale_key, kf->current_group, *c);
1278           retval = FALSE;
1279         }
1280         break;
1281
1282       /* flags */
1283       case '%':
1284         flag = !flag;
1285         break;
1286       case 'f':
1287       case 'u':
1288         if (flag) {
1289           if (file_uri) {
1290             print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1291                              "may contain at most one \"%f\", \"%u\", "
1292                              "\"%F\" or \"%U\" field code\n",
1293                              value, locale_key, kf->current_group);
1294             retval = FALSE;
1295           }
1296
1297           file_uri = TRUE;
1298           flag = FALSE;
1299         }
1300         break;
1301       case 'F':
1302       case 'U':
1303         if (flag) {
1304           if (file_uri) {
1305             print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1306                              "may contain at most one \"%f\", \"%u\", "
1307                              "\"%F\" or \"%U\" field code\n",
1308                              value, locale_key, kf->current_group);
1309             retval = FALSE;
1310           }
1311
1312           file_uri = TRUE;
1313           flag = FALSE;
1314         }
1315         break;
1316       case 'i':
1317       case 'c':
1318       case 'k':
1319         if (flag)
1320           flag = FALSE;
1321         break;
1322       case 'd':
1323       case 'D':
1324       case 'n':
1325       case 'N':
1326       case 'v':
1327       case 'm':
1328         if (flag) {
1329           if (!kf->no_deprecated_warnings)
1330             print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1331                                "contains a deprecated field code \"%%%c\"\n",
1332                                 value, locale_key, kf->current_group, *c);
1333           flag = FALSE;
1334         }
1335         break;
1336
1337       default:
1338         PRINT_INVALID_IF_FLAG;
1339         break;
1340     }
1341
1342     c++;
1343   }
1344
1345   if (in_quote) {
1346     print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" contains a "
1347                      "quote which is not closed\n",
1348                      value, locale_key, kf->current_group);
1349     retval = FALSE;
1350   }
1351
1352   if (flag) {
1353     print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" contains a "
1354                      "non-complete field code\n",
1355                      value, locale_key, kf->current_group);
1356     retval = FALSE;
1357   }
1358
1359   return retval;
1360 }
1361
1362 /* See checks for handle_exec_key().
1363  */
1364 static gboolean
1365 handle_desktop_exec_key (kf_validator *kf,
1366                          const char   *locale_key,
1367                          const char   *value)
1368 {
1369   handle_key_for_application (kf, locale_key, value);
1370
1371   return handle_exec_key (kf, locale_key, value);
1372 }
1373
1374 /* + If entry is of type Application, the working directory to run the program
1375  *   in. (probably implies an absolute path)
1376  *   Checked.
1377  * + FIXME: is it okay to have an empty string here? (wireshark.desktop does)
1378  */
1379 static gboolean
1380 handle_path_key (kf_validator *kf,
1381                  const char   *locale_key,
1382                  const char   *value)
1383 {
1384   handle_key_for_application (kf, locale_key, value);
1385
1386   if (!g_path_is_absolute (value))
1387     print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1388                        "does not look like an absolute path\n",
1389                        value, locale_key, kf->current_group);
1390
1391   return TRUE;
1392 }
1393
1394 /* + The MIME type(s) supported by this application. Check they are valid
1395  *   MIME types.
1396  *   Checked.
1397  */
1398 static gboolean
1399 handle_mime_key (kf_validator *kf,
1400                  const char   *locale_key,
1401                  const char   *value)
1402 {
1403   gboolean       retval;
1404   char         **types;
1405   GHashTable    *hashtable;
1406   int            i;
1407   char          *valid_error;
1408   MimeUtilsValidity valid;
1409
1410   handle_key_for_application (kf, locale_key, value);
1411
1412   retval = TRUE;
1413
1414   hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
1415   types = g_strsplit (value, ";", 0);
1416
1417   for (i = 0; types[i]; i++) {
1418     /* since the value ends with a semicolon, we'll have an empty string
1419      * at the end */
1420     if (*types[i] == '\0' && types[i + 1] == NULL)
1421       break;
1422
1423     if (g_hash_table_lookup (hashtable, types[i])) {
1424       print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1425                          "contains \"%s\" more than once\n",
1426                          value, locale_key, kf->current_group, types[i]);
1427       continue;
1428     }
1429
1430     g_hash_table_insert (hashtable, types[i], types[i]);
1431
1432     valid = mu_mime_type_is_valid (types[i], &valid_error);
1433     switch (valid) {
1434       case MU_VALID:
1435         break;
1436       case MU_DISCOURAGED:
1437         print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1438                            "contains value \"%s\" which is a MIME type that "
1439                            "should probably not be used: %s\n",
1440                            value, locale_key, kf->current_group,
1441                            types[i], valid_error);
1442
1443         g_free (valid_error);
1444         break;
1445       case MU_INVALID:
1446         print_future_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1447                                 "contains value \"%s\" which is an invalid "
1448                                 "MIME type: %s\n",
1449                                 value, locale_key, kf->current_group,
1450                                 types[i], valid_error);
1451
1452         retval = FALSE;
1453         g_free (valid_error);
1454         break;
1455       default:
1456         g_assert_not_reached ();
1457     }
1458   }
1459
1460   g_strfreev (types);
1461   g_hash_table_destroy (hashtable);
1462
1463   return retval;
1464 }
1465
1466 /* + FIXME: are there restrictions on how a category should be named?
1467  * + Categories in which the entry should be shown in a menu (for possible
1468  *   values see the Desktop Menu Specification).
1469  *   Checked.
1470  * + The table below describes Reserved Categories. Reserved Categories have a
1471  *   specific desktop specific meaning that has not been standardized (yet).
1472  *   Desktop entry files that use a reserved category MUST also include an
1473  *   appropriate OnlyShowIn= entry to restrict themselves to those environments
1474  *   that properly support the reserved category as used.
1475  *   Checked.
1476  * + Accept "Application" as a deprecated category.
1477  *   Checked.
1478  *   FIXME: it's not really deprecated, so the error message is wrong
1479  * + All categories extending the format should start with "X-".
1480  *   Checked.
1481  * + Using multiple main categories may lead to appearing more than once in
1482  *   application menu.
1483  *   Checked.
1484  * + One main category should be included, otherwise application will appear in
1485  *   "catch-all" section of application menu.
1486  *   Checked.
1487  *   FIXME: decide if it's okay to have an empty list of categories.
1488  * + Some categories, if included, require that another category is included.
1489  *   Eg: if Audio is there, AudioVideo must be there.
1490  *   Checked.
1491  * + Some categories, if included, suggest that another category is included.
1492  *   Eg: Debugger suggests Development.
1493  *   This is the case for most additional categories.
1494  *   Checked.
1495  */
1496 static gboolean
1497 handle_categories_key (kf_validator *kf,
1498                        const char   *locale_key,
1499                        const char   *value)
1500 {
1501   gboolean       retval;
1502   char         **categories;
1503   GHashTable    *hashtable;
1504   int            i;
1505   unsigned int   j;
1506   int            main_categories_nb;
1507
1508   handle_key_for_application (kf, locale_key, value);
1509
1510   retval = TRUE;
1511
1512   /* accept empty value as valid: this is like having no category at all */
1513   if (value[0] == '\0')
1514     return retval;
1515
1516   hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
1517   categories = g_strsplit (value, ";", 0);
1518
1519   /* this is a two-pass check: we first put the categories in a hash table so
1520    * that they are easy-to-find, and we then do many checks */
1521
1522   /* first pass */
1523   for (i = 0; categories[i]; i++) {
1524     /* since the value ends with a semicolon, we'll have an empty string
1525      * at the end */
1526     if (*categories[i] == '\0' && categories[i + 1] == NULL)
1527       break;
1528
1529     if (g_hash_table_lookup (hashtable, categories[i])) {
1530       print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1531                          "contains \"%s\" more than once\n",
1532                          value, locale_key, kf->current_group, categories[i]);
1533       continue;
1534     }
1535
1536     g_hash_table_insert (hashtable, categories[i], categories[i]);
1537   }
1538
1539   /* second pass */
1540   main_categories_nb = 0;
1541
1542   for (i = 0; categories[i]; i++) {
1543     unsigned int k;
1544
1545     /* since the value ends with a semicolon, we'll have an empty string
1546      * at the end */
1547     if (*categories[i] == '\0' && categories[i + 1] == NULL)
1548       break;
1549
1550     if (!strncmp (categories[i], "X-", 2))
1551       continue;
1552
1553     for (j = 0; j < G_N_ELEMENTS (registered_categories); j++) {
1554       if (!strcmp (categories[i], registered_categories[j].name))
1555         break;
1556     }
1557
1558     if (j == G_N_ELEMENTS (registered_categories)) {
1559       print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1560                        "contains an unregistered value \"%s\"; values "
1561                        "extending the format should start with \"X-\"\n",
1562                        value, locale_key, kf->current_group, categories[i]);
1563       retval = FALSE;
1564       continue;
1565     }
1566
1567     if (registered_categories[j].main) {
1568       /* only count it as a main category if none of the required categories
1569        * for this one is also a main category (and is present) */
1570       gboolean required_main_category_present = FALSE;
1571
1572       for (k = 0; registered_categories[j].requires[k] != NULL; k++) {
1573         char **required_categories;
1574         int    l;
1575
1576         required_categories = g_strsplit (registered_categories[j].requires[k],
1577                                           ";", 0);
1578
1579         for (l = 0; required_categories[l]; l++) {
1580           unsigned int m;
1581
1582           if (!g_hash_table_lookup (hashtable, required_categories[l]))
1583             continue;
1584
1585           for (m = 0; m < G_N_ELEMENTS (registered_categories); m++) {
1586             if (strcmp (required_categories[l],
1587                         registered_categories[m].name) != 0)
1588               continue;
1589
1590             if (registered_categories[m].main)
1591               required_main_category_present = TRUE;
1592
1593             break;
1594           }
1595
1596           if (required_main_category_present)
1597             break;
1598         }
1599
1600         if (required_main_category_present) {
1601           g_strfreev (required_categories);
1602           break;
1603         }
1604
1605         g_strfreev (required_categories);
1606       }
1607
1608       if (!required_main_category_present)
1609         main_categories_nb++;
1610     }
1611
1612     if (registered_categories[j].main && main_categories_nb > 1)
1613       print_hint (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1614                       "contains more than one main category; application "
1615                       "might appear more than once in the application menu\n",
1616                       value, locale_key, kf->current_group);
1617
1618
1619     if (registered_categories[j].deprecated) {
1620       if (!kf->no_deprecated_warnings)
1621         print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1622                            "contains a deprecated value \"%s\"\n",
1623                             value, locale_key, kf->current_group,
1624                             categories[i]);
1625     }
1626
1627     if (registered_categories[j].require_only_show_in) {
1628       if (!g_hash_table_lookup (kf->current_keys, "OnlyShowIn")) {
1629         print_fatal (kf, "value item \"%s\" in key \"%s\" in group \"%s\" "
1630                          "is a reserved category, so a \"OnlyShowIn\" key "
1631                          "must be included\n",
1632                          categories[i], locale_key, kf->current_group);
1633         retval = FALSE;
1634       }
1635     }
1636
1637     /* required categories */
1638
1639     for (k = 0; registered_categories[j].requires[k] != NULL; k++) {
1640       char **required_categories;
1641       int    l;
1642
1643       required_categories = g_strsplit (registered_categories[j].requires[k],
1644                                         ";", 0);
1645
1646       for (l = 0; required_categories[l]; l++) {
1647         if (!g_hash_table_lookup (hashtable, required_categories[l]))
1648           break;
1649       }
1650
1651       /* we've reached the end of a list of required categories, so
1652        * the condition is satisfied */
1653       if (required_categories[l] == NULL) {
1654         g_strfreev (required_categories);
1655         break;
1656       }
1657
1658       g_strfreev (required_categories);
1659     }
1660
1661     /* we've reached the end of a non-empty set of required categories; this
1662      * means none of the possible required category (or list of required
1663      * categories) was found */
1664     if (k != 0 && registered_categories[j].requires[k] == NULL) {
1665       GString *output_required;
1666
1667       output_required = g_string_new (registered_categories[j].requires[0]);
1668       for (k = 1; registered_categories[j].requires[k] != NULL; k++)
1669         g_string_append_printf (output_required, ", or %s",
1670                                 registered_categories[j].requires[k]);
1671
1672       print_future_fatal (kf, "value item \"%s\" in key \"%s\" in group \"%s\" "
1673                               "requires another category to be present among "
1674                               "the following categories: %s\n",
1675                               categories[i], locale_key, kf->current_group,
1676                               output_required->str);
1677
1678       g_string_free (output_required, TRUE);
1679       retval = FALSE;
1680     }
1681
1682     /* suggested categories */
1683
1684     for (k = 0; registered_categories[j].suggests[k] != NULL; k++) {
1685       char **suggested_categories;
1686       int    l;
1687
1688       suggested_categories = g_strsplit (registered_categories[j].suggests[k],
1689                                          ";", 0);
1690
1691       for (l = 0; suggested_categories[l]; l++) {
1692         if (!g_hash_table_lookup (hashtable, suggested_categories[l]))
1693           break;
1694       }
1695
1696       /* we've reached the end of a list of suggested categories, so
1697        * the condition is satisfied */
1698       if (suggested_categories[l] == NULL) {
1699         g_strfreev (suggested_categories);
1700         break;
1701       }
1702
1703       g_strfreev (suggested_categories);
1704     }
1705
1706     /* we've reached the end of a non-empty set of suggested categories; this
1707      * means none of the possible suggested category (or list of suggested
1708      * categories) was found */
1709     if (k != 0 && registered_categories[j].suggests[k] == NULL) {
1710       GString *output_suggested;
1711
1712       output_suggested = g_string_new (registered_categories[j].suggests[0]);
1713       for (k = 1; registered_categories[j].suggests[k] != NULL; k++)
1714         g_string_append_printf (output_suggested, ", or %s",
1715                                 registered_categories[j].suggests[k]);
1716
1717       print_hint (kf, "value item \"%s\" in key \"%s\" in group \"%s\" "
1718                       "can be extended with another category among the "
1719                       "following categories: %s\n",
1720                       categories[i], locale_key, kf->current_group,
1721                       output_suggested->str);
1722
1723       g_string_free (output_suggested, TRUE);
1724     }
1725
1726   }
1727
1728   g_strfreev (categories);
1729   g_hash_table_destroy (hashtable);
1730
1731   g_assert (main_categories_nb >= 0);
1732
1733   if (main_categories_nb == 0)
1734     print_hint (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1735                     "does not contain a registered main category; application "
1736                     "might only show up in a \"catch-all\" section of the "
1737                     "application menu\n",
1738                     value, locale_key, kf->current_group);
1739
1740   return retval;
1741 }
1742
1743 /* + Identifiers for application actions. Check they are using a valid format.
1744  *   Checked.
1745  *
1746  * Note that we will check later on (in * validate_actions()) that there is a
1747  * "Desktop Action foobar" group for each "foobar" identifier.
1748  */
1749 static gboolean
1750 handle_actions_key (kf_validator *kf,
1751                     const char   *locale_key,
1752                     const char   *value)
1753 {
1754   char **actions;
1755   char  *action;
1756   int    i;
1757   gboolean retval;
1758
1759   handle_key_for_application (kf, locale_key, value);
1760
1761   retval = TRUE;
1762   actions = g_strsplit (value, ";", 0);
1763
1764   for (i = 0; actions[i]; i++) {
1765     /* since the value ends with a semicolon, we'll have an empty string
1766      * at the end */
1767     if (*actions[i] == '\0') {
1768       if (actions[i + 1] != NULL) {
1769         print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1770                          "contains an empty action\n",
1771                          value, locale_key, kf->current_group);
1772         retval = FALSE;
1773         break;
1774       }
1775
1776       continue;
1777     }
1778
1779     if (g_hash_table_lookup (kf->action_values, actions[i])) {
1780       print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1781                          "contains action \"%s\" more than once\n",
1782                          value, locale_key, kf->current_group, actions[i]);
1783       continue;
1784     }
1785
1786     if (!key_is_valid (actions[i], strlen (actions[i]))) {
1787       print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1788                        "contains invalid action identifier \"%s\", only "
1789                        "alphanumeric characters and '-' are allowed\n",
1790                        value, locale_key, kf->current_group, actions[i]);
1791       retval = FALSE;
1792       break;
1793     }
1794
1795     action = g_strdup (actions[i]);
1796     g_hash_table_insert (kf->action_values, action, action);
1797   }
1798
1799   g_strfreev (actions);
1800
1801   return retval;
1802 }
1803
1804 /* + If the file describes a D-Bus activatable service, the filename must be in
1805  *   reverse-DNS notation, i.e. contain at least two dots including the dot
1806  *   in ".desktop".
1807  *   Checked.
1808  */
1809 static gboolean
1810 handle_dbus_activatable_key (kf_validator *kf,
1811                              const char   *locale_key,
1812                              const char   *value)
1813 {
1814   gchar *basename_utf8;
1815   gchar *basename;
1816   const gchar *p = NULL;
1817   gboolean retval = TRUE;
1818
1819   /* If DBusActivatable=false, don't check */
1820   if (strcmp (value, "true") && strcmp (value, "1"))
1821     return TRUE;
1822
1823   basename = g_path_get_basename (kf->filename);
1824   basename_utf8 = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL);
1825   if (!basename_utf8)
1826     goto out;
1827
1828   p = g_utf8_strchr (basename_utf8, -1, '.');
1829   if (!p)
1830     goto out;
1831   p = g_utf8_strchr (p + 1, -1, '.');
1832
1833 out:
1834   if (!p) {
1835     print_fatal (kf, "DBusActivatable filename must conform to reverse-DNS notation\n");
1836     retval = FALSE;
1837   }
1838
1839   g_free (basename_utf8);
1840   g_free (basename);
1841   return retval;
1842 }
1843
1844 /* + The device to mount. (probably implies an absolute path)
1845  *   Checked.
1846  */
1847 static gboolean
1848 handle_dev_key (kf_validator *kf,
1849                 const char   *locale_key,
1850                 const char   *value)
1851 {
1852   handle_key_for_fsdevice (kf, locale_key, value);
1853
1854   if (!g_path_is_absolute (value))
1855     print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1856                        "does not look like an absolute path\n",
1857                        value, locale_key, kf->current_group);
1858
1859   return TRUE;
1860 }
1861
1862 /* + The mount point of the device in question. (probably implies an absolute
1863  *   path)
1864  *   Checked.
1865  */
1866 static gboolean
1867 handle_mountpoint_key (kf_validator *kf,
1868                        const char   *locale_key,
1869                        const char   *value)
1870 {
1871   handle_key_for_fsdevice (kf, locale_key, value);
1872
1873   if (!g_path_is_absolute (value))
1874     print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1875                        "does not look like an absolute path\n",
1876                        value, locale_key, kf->current_group);
1877
1878   return TRUE;
1879 }
1880
1881 /* + Possible values are UTF-8 and Legacy-Mixed.
1882  *   Checked.
1883  */
1884 static gboolean
1885 handle_encoding_key (kf_validator *kf,
1886                      const char   *locale_key,
1887                      const char   *value)
1888 {
1889   if (!strcmp (value, "UTF-8") || !strcmp (value, "Legacy-Mixed"))
1890     return TRUE;
1891
1892   print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1893                    "is not a registered encoding value (\"UTF-8\", and "
1894                    "\"Legacy-Mixed\")\n",
1895                    value, locale_key, kf->current_group);
1896
1897   return FALSE;
1898 }
1899
1900 /* + See http://lists.freedesktop.org/archives/xdg/2007-January/007436.html
1901  * + Value is one of:
1902  *   - if-exists FILE
1903  *   - unless-exists FILE
1904  *   - DESKTOP-ENVIRONMENT-NAME [DESKTOP-SPECIFIC-TEST]
1905  *   - other known conditions (GNOME3, GSettings, etc.)
1906  *   Checked.
1907  * + FILE must be a path to a filename, relative to $XDG_CONFIG_HOME.
1908  *   Checked.
1909  * + DESKTOP-ENVIRONMENT-NAME should be a registered value (in Desktop Menu
1910  *   Specification) or start with "X-".
1911  *   Checked.
1912  * + [DESKTOP-SPECIFIC-TEST] is optional.
1913  *   Checked.
1914  */
1915 static gboolean
1916 handle_autostart_condition_key (kf_validator *kf,
1917                                 const char   *locale_key,
1918                                 const char   *value)
1919 {
1920   gboolean  retval;
1921   char     *condition;
1922   char     *argument;
1923
1924   handle_key_for_application (kf, locale_key, value);
1925
1926   retval = TRUE;
1927
1928   condition = g_strdup (value);
1929   argument = g_utf8_strchr (condition, -1, ' ');
1930
1931   if (argument) {
1932     /* make condition a 0-ended string */
1933     *argument = '\0';
1934
1935     /* skip the space(s) */
1936     argument++;
1937     while (*argument == ' ') {
1938       argument++;
1939     }
1940   }
1941
1942   if (!strcmp (condition, "if-exists") || !strcmp (condition, "unless-exists")) {
1943     if (!argument || argument[0] == '\0') {
1944       print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1945                        "does not contain a path to a file to test the "
1946                        "condition\n",
1947                        value, locale_key, kf->current_group);
1948       retval = FALSE;
1949     } else if (argument[0] == G_DIR_SEPARATOR) {
1950       print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1951                        "contains a path \"%s\" that is absolute, while it "
1952                        "should be relative (to $XDG_CONFIG_HOME)\n",
1953                        value, locale_key, kf->current_group, argument);
1954       retval = FALSE;
1955     } else if (argument[0] == '.' &&
1956                ((strlen (argument) == 2 &&
1957                  argument[1] == '.') ||
1958                 (strlen (argument) >= 3 &&
1959                  argument[1] == '.' &&
1960                  argument[2] == G_DIR_SEPARATOR))) {
1961       print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1962                          "contains a path \"%s\" that depends on the value "
1963                          "of $XDG_CONFIG_HOME (\"..\" should be avoided)\n",
1964                          value, locale_key, kf->current_group, argument);
1965     }
1966
1967   } else if (strncmp (condition, "X-", 2) == 0) {
1968     if (argument && argument[0] == '\0')
1969       print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1970                          "has trailing space(s)\n",
1971                          value, locale_key, kf->current_group);
1972   } else {
1973     unsigned int i;
1974     unsigned int j;
1975
1976     /* Look if it's a registered AutostartCondition */
1977
1978     for (i = 0; i < G_N_ELEMENTS (registered_autostart_condition); i++) {
1979
1980       if (strcmp (condition, registered_autostart_condition[i].name) != 0)
1981         continue;
1982
1983       /* check if first argument is one of the expected ones */
1984       for (j = 0; registered_autostart_condition[i].first_arg[j] != NULL; j++) {
1985         const char *first = registered_autostart_condition[i].first_arg[j];
1986         char       *after_first = argument;
1987
1988         if (argument && !strncmp (argument, first, strlen (first))) {
1989           after_first += strlen (first);
1990           if (after_first[0] == '\0' || after_first[0] == ' ') {
1991             /* find next argument */
1992             argument = after_first;
1993             while (*argument == ' ')
1994               argument++;
1995           }
1996
1997           break;
1998         }
1999       }
2000
2001       /* we've reached the end of a non-empty set of first arguments; this
2002        * means none of the possible first arguments was found */
2003       if (j != 0 && registered_autostart_condition[i].first_arg[j] == NULL) {
2004         GString *output;
2005
2006         output = g_string_new (registered_autostart_condition[i].first_arg[0]);
2007         for (j = 1; registered_autostart_condition[i].first_arg[j] != NULL; j++)
2008           g_string_append_printf (output, ", or %s",
2009                                   registered_autostart_condition[i].first_arg[j]);
2010
2011         print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2012                          "does not contain a valid first argument for "
2013                          "condition \"%s\"; valid first arguments are: %s\n",
2014                          value, locale_key, kf->current_group,
2015                          condition, output->str);
2016         retval = FALSE;
2017
2018         g_string_free (output, TRUE);
2019
2020       } else {
2021
2022         switch (registered_autostart_condition[i].additional_args) {
2023           case 0:
2024             if (argument && argument[0] != '\0') {
2025               print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2026                                "has too many arguments for condition \"%s\"\n",
2027                                value, locale_key, kf->current_group, condition);
2028               retval = FALSE;
2029             }
2030             break;
2031
2032           case 1:
2033             /* we handle the "one argument" case specially, as spaces might be
2034              * normal there, and therefore we don't want to split the string
2035              * based on spaces */
2036             if (!argument || argument[0] == '\0') {
2037               print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2038                                "is missing a last argument for condition "
2039                                "\"%s\"\n",
2040                                value, locale_key, kf->current_group, condition);
2041               retval = FALSE;
2042             }
2043             break;
2044
2045           default:
2046             {
2047               int argc_diff = -registered_autostart_condition[i].additional_args;
2048
2049               while (argument && argument[0] != '\0') {
2050                 argc_diff++;
2051                 argument = g_utf8_strchr (argument, -1, ' ');
2052                 while (argument && *argument == ' ')
2053                   argument++;
2054               }
2055
2056               if (argc_diff > 0) {
2057                 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2058                                  "has %d too many arguments for condition "
2059                                  "\"%s\"\n",
2060                                  value, locale_key, kf->current_group,
2061                                  argc_diff, condition);
2062                 retval = FALSE;
2063               } else if (argc_diff < 0) {
2064                 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2065                                  "has %d too few arguments for condition "
2066                                  "\"%s\"\n",
2067                                  value, locale_key, kf->current_group,
2068                                  -argc_diff, condition);
2069                 retval = FALSE;
2070               }
2071             }
2072             break;
2073         }
2074
2075       }
2076
2077       break;
2078
2079     }
2080
2081     /* Now, if we didn't find condition in list of registered
2082      * AutostartCondition... */
2083     if (i == G_N_ELEMENTS (registered_autostart_condition)) {
2084       /* Accept conditions with same name as OnlyShowIn values */
2085
2086       for (i = 0; i < G_N_ELEMENTS (show_in_registered); i++) {
2087         if (!strcmp (condition, show_in_registered[i]))
2088           break;
2089       }
2090
2091       if (i == G_N_ELEMENTS (show_in_registered)) {
2092         print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2093                          "contains an unregistered value \"%s\" for the "
2094                          "condition; values extending the format should "
2095                          "start with \"X-\"\n",
2096                          value, locale_key, kf->current_group, condition);
2097         retval = FALSE;
2098       }
2099
2100       if (argument && argument[0] == '\0')
2101         print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2102                            "has trailing space(s)\n",
2103                            value, locale_key, kf->current_group);
2104     }
2105   }
2106
2107   g_free (condition);
2108
2109   return retval;
2110 }
2111
2112 static gboolean
2113 handle_key_for_application (kf_validator *kf,
2114                             const char   *locale_key,
2115                             const char   *value)
2116 {
2117   kf->application_keys = g_list_append (kf->application_keys,
2118                                         g_strdup (locale_key));
2119   return TRUE;
2120 }
2121
2122 static gboolean
2123 handle_key_for_link (kf_validator *kf,
2124                      const char   *locale_key,
2125                      const char   *value)
2126 {
2127   kf->link_keys = g_list_append (kf->link_keys,
2128                                  g_strdup (locale_key));
2129   return TRUE;
2130 }
2131
2132 static gboolean
2133 handle_key_for_fsdevice (kf_validator *kf,
2134                          const char   *locale_key,
2135                          const char   *value)
2136 {
2137   kf->fsdevice_keys = g_list_append (kf->fsdevice_keys,
2138                                      g_strdup (locale_key));
2139   return TRUE;
2140 }
2141
2142 static gboolean
2143 handle_key_for_mimetype (kf_validator *kf,
2144                          const char   *locale_key,
2145                          const char   *value)
2146 {
2147   kf->mimetype_keys = g_list_append (kf->mimetype_keys,
2148                                      g_strdup (locale_key));
2149   return TRUE;
2150 }
2151
2152 /* + Key names must contain only the characters A-Za-z0-9-.
2153  *   Checked (through key_is_valid()).
2154  * + LOCALE must be of the form lang_COUNTRY.ENCODING@MODIFIER, where _COUNTRY,
2155  *   .ENCODING, and @MODIFIER  may be omitted.
2156  *   Checked.
2157  */
2158 static gboolean
2159 key_extract_locale (const char  *key,
2160                     char       **real_key,
2161                     char       **locale)
2162 {
2163   const char *start_locale;
2164   char        c;
2165   int         len;
2166   int         i;
2167
2168   if (real_key)
2169     *real_key = NULL;
2170   if (locale)
2171     *locale = NULL;
2172
2173   start_locale = g_strrstr (key, "[");
2174
2175   if (start_locale)
2176     len = start_locale - key;
2177   else
2178     len = strlen (key);
2179
2180   if (!key_is_valid(key, len))
2181     return FALSE;
2182
2183   if (!start_locale) {
2184     if (real_key)
2185       *real_key = g_strdup (key);
2186     if (locale)
2187       *locale = NULL;
2188
2189     return TRUE;
2190   }
2191
2192   len = strlen (start_locale);
2193   if (len <= 2 || start_locale[len - 1] != ']')
2194     return FALSE;
2195
2196   /* ignore first [ and last ] */
2197   for (i = 1; i < len - 2; i++) {
2198     c = start_locale[i];
2199     if (!g_ascii_isalnum (c) && c != '-' && c != '_' && c != '.' && c != '@')
2200       return FALSE;
2201   }
2202
2203   if (real_key)
2204     *real_key = g_strndup (key, strlen (key) - len);
2205   if (locale)
2206     *locale = g_strndup (start_locale + 1, len - 2);
2207
2208   return TRUE;
2209 }
2210
2211 /* + All keys extending the format should start with "X-".
2212  *   Checked.
2213  */
2214 static gboolean
2215 validate_known_key (kf_validator         *kf,
2216                     const char           *locale_key,
2217                     const char           *key,
2218                     const char           *locale,
2219                     const char           *value,
2220                     DesktopKeyDefinition *keys,
2221                     unsigned int          n_keys)
2222 {
2223   unsigned int i;
2224   unsigned int j;
2225
2226   if (!strncmp (key, "X-", 2))
2227     return TRUE;
2228
2229   for (i = 0; i < n_keys; i++) {
2230     if (strcmp (key, keys[i].name))
2231       continue;
2232
2233     if (keys[i].type != DESKTOP_LOCALESTRING_TYPE &&
2234         keys[i].type != DESKTOP_LOCALESTRING_LIST_TYPE &&
2235         locale != NULL) {
2236       print_fatal (kf, "file contains key \"%s\" in group \"%s\", "
2237                        "but \"%s\" is not defined as a locale string\n",
2238                        locale_key, kf->current_group, key);
2239       return FALSE;
2240     }
2241
2242     for (j = 0; j < G_N_ELEMENTS (validate_for_type); j++) {
2243       if (validate_for_type[j].type == keys[i].type)
2244         break;
2245     }
2246
2247     g_assert (j != G_N_ELEMENTS (validate_for_type));
2248
2249     if (!kf->no_deprecated_warnings && keys[i].deprecated)
2250       print_warning (kf, "key \"%s\" in group \"%s\" is deprecated\n",
2251                          locale_key, kf->current_group);
2252
2253     if (keys[i].kde_reserved && kf->kde_reserved_warnings)
2254       print_warning (kf, "key \"%s\" in group \"%s\" is a reserved key for "
2255                          "KDE\n",
2256                          locale_key, kf->current_group);
2257
2258     if (!validate_for_type[j].validate (kf, key, locale, value))
2259       return FALSE;
2260
2261     if (keys[i].handle_and_validate != NULL) {
2262       if (!keys[i].handle_and_validate (kf, locale_key, value))
2263         return FALSE;
2264     }
2265
2266     break;
2267   }
2268
2269   if (i == n_keys) {
2270     print_fatal (kf, "file contains key \"%s\" in group \"%s\", but "
2271                      "keys extending the format should start with "
2272                      "\"X-\"\n", key, kf->current_group);
2273     return FALSE;
2274   }
2275
2276   return TRUE;
2277 }
2278
2279 static gboolean
2280 validate_desktop_key (kf_validator *kf,
2281                       const char   *locale_key,
2282                       const char   *key,
2283                       const char   *locale,
2284                       const char   *value)
2285 {
2286   return validate_known_key (kf, locale_key, key, locale, value,
2287                              registered_desktop_keys,
2288                              G_N_ELEMENTS (registered_desktop_keys));
2289 }
2290
2291 static gboolean
2292 validate_action_key (kf_validator *kf,
2293                      const char   *locale_key,
2294                      const char   *key,
2295                      const char   *locale,
2296                      const char   *value)
2297 {
2298   return validate_known_key (kf, locale_key, key, locale, value,
2299                              registered_action_keys,
2300                              G_N_ELEMENTS (registered_action_keys));
2301 }
2302
2303 /* + Multiple keys in the same group may not have the same name.
2304  *   Checked.
2305  */
2306 static gboolean
2307 validate_keys_for_current_group (kf_validator *kf)
2308 {
2309   gboolean     desktop_group;
2310   gboolean     action_group;
2311   gboolean     retval;
2312   GHashTable  *duplicated_keys_hash;
2313   char        *key;
2314   char        *locale;
2315   GSList      *keys;
2316   GSList      *sl;
2317   gpointer     hashvalue;
2318
2319   retval = TRUE;
2320
2321   desktop_group = (!strcmp (kf->current_group, GROUP_DESKTOP_ENTRY) ||
2322                    !strcmp (kf->current_group, GROUP_KDE_DESKTOP_ENTRY));
2323   action_group = (!strncmp (kf->current_group, GROUP_DESKTOP_ACTION,
2324                             strlen (GROUP_DESKTOP_ACTION)));
2325
2326   keys = g_slist_copy (g_hash_table_lookup (kf->groups, kf->current_group));
2327   /* keys were prepended, so reverse the list (that's why we use a
2328    * g_slist_copy() */
2329   keys = g_slist_reverse (keys);
2330
2331   kf->current_keys = g_hash_table_new_full (g_str_hash, g_str_equal,
2332                                             NULL, NULL);
2333   duplicated_keys_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
2334                                                 NULL, NULL);
2335
2336   /* we need two passes: some checks are looking if another key exists in the
2337    * group */
2338   for (sl = keys; sl != NULL; sl = sl->next) {
2339     kf_keyvalue *keyvalue;
2340
2341     keyvalue = (kf_keyvalue *) sl->data;
2342     g_hash_table_insert (kf->current_keys, keyvalue->key, keyvalue);
2343
2344     /* we could display the error about duplicate keys here, but it's better
2345      * to display it with the first occurence of this key */
2346     hashvalue = g_hash_table_lookup (duplicated_keys_hash, keyvalue->key);
2347     if (!hashvalue)
2348       g_hash_table_insert (duplicated_keys_hash, keyvalue->key,
2349                            GINT_TO_POINTER (1));
2350     else {
2351       g_hash_table_replace (duplicated_keys_hash, keyvalue->key,
2352                             GINT_TO_POINTER (GPOINTER_TO_INT (hashvalue) + 1));
2353     }
2354   }
2355
2356   for (sl = keys; sl != NULL; sl = sl->next) {
2357     kf_keyvalue *keyvalue;
2358     gboolean     skip_desktop_check;
2359
2360     keyvalue = (kf_keyvalue *) sl->data;
2361
2362     skip_desktop_check = FALSE;
2363
2364     if (!key_extract_locale (keyvalue->key, &key, &locale)) {
2365         print_fatal (kf, "file contains key \"%s\" in group \"%s\", but "
2366                          "key names must contain only the characters "
2367                          "A-Za-z0-9- (they may have a \"[LOCALE]\" postfix)\n",
2368                          keyvalue->key, kf->current_group);
2369         retval = FALSE;
2370         skip_desktop_check = TRUE;
2371
2372         key = g_strdup (keyvalue->key);
2373     }
2374
2375     g_assert (key != NULL);
2376
2377     hashvalue = g_hash_table_lookup (duplicated_keys_hash, keyvalue->key);
2378     if (GPOINTER_TO_INT (hashvalue) > 1) {
2379       g_hash_table_remove (duplicated_keys_hash, keyvalue->key);
2380       print_fatal (kf, "file contains multiple keys named \"%s\" in "
2381                        "group \"%s\"\n", keyvalue->key, kf->current_group);
2382       retval = FALSE;
2383     }
2384
2385     if (desktop_group && !skip_desktop_check) {
2386       if (!validate_desktop_key (kf, keyvalue->key,
2387                                  key, locale, keyvalue->value))
2388         retval = FALSE;
2389     } else if (action_group && !skip_desktop_check) {
2390       if (!validate_action_key (kf, keyvalue->key,
2391                                 key, locale, keyvalue->value))
2392         retval = FALSE;
2393     }
2394
2395     g_free (key);
2396     key = NULL;
2397     g_free (locale);
2398     locale = NULL;
2399   }
2400
2401   g_slist_free (keys);
2402   g_hash_table_destroy (duplicated_keys_hash);
2403   g_hash_table_destroy (kf->current_keys);
2404   kf->current_keys = NULL;
2405   /* Clear ShowIn flag, so that different groups can each have a OnlyShowIn /
2406    * NotShowIn key */
2407   kf->show_in = FALSE;
2408
2409   return retval;
2410 }
2411
2412 /* + Using [KDE Desktop Entry] instead of [Desktop Entry] as header is
2413  *   deprecated.
2414  *   Checked.
2415  * + Group names may contain all ASCII characters except for [ and ] and
2416  *   control characters.
2417  *   Checked.
2418  * + All groups extending the format should start with "X-".
2419  *   Checked.
2420  * + Accept "Desktop Action foobar" group, where foobar is a valid key
2421  *   name.
2422  *   Checked.
2423  *
2424  * Note that for "Desktop Action foobar" group, we will check later on (in
2425  * validate_actions()) that the Actions key contains "foobar".
2426  */
2427 static gboolean
2428 validate_group_name (kf_validator *kf,
2429                      const char   *group)
2430 {
2431   int  i;
2432   char c;
2433
2434   for (i = 0; group[i] != '\0'; i++) {
2435     c = group[i];
2436     if (g_ascii_iscntrl (c) || c == '[' || c == ']') {
2437       print_fatal (kf, "file contains group \"%s\", but group names "
2438                        "may contain all ASCII characters except for [ "
2439                        "and ] and control characters\n", group);
2440       return FALSE;
2441     }
2442   }
2443
2444   if (!strncmp (group, "X-", 2))
2445     return TRUE;
2446
2447   if (!strcmp (group, GROUP_DESKTOP_ENTRY)) {
2448     if (kf->main_group && !strcmp (kf->main_group, GROUP_KDE_DESKTOP_ENTRY))
2449       print_warning (kf, "file contains groups \"%s\" and \"%s\", which play "
2450                          "the same role\n",
2451                          GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY);
2452
2453     kf->main_group = GROUP_DESKTOP_ENTRY;
2454
2455     return TRUE;
2456   }
2457
2458   if (!strcmp (group, GROUP_KDE_DESKTOP_ENTRY)) {
2459     if (kf->kde_reserved_warnings || !kf->no_deprecated_warnings)
2460       print_warning (kf, "file contains group \"%s\", which is deprecated "
2461                          "in favor of \"%s\"\n", group, GROUP_DESKTOP_ENTRY);
2462
2463     if (kf->main_group && !strcmp (kf->main_group, GROUP_DESKTOP_ENTRY))
2464       print_warning (kf, "file contains groups \"%s\" and \"%s\", which play "
2465                          "the same role\n",
2466                          GROUP_DESKTOP_ENTRY, GROUP_KDE_DESKTOP_ENTRY);
2467
2468     kf->main_group = GROUP_KDE_DESKTOP_ENTRY;
2469
2470     return TRUE;
2471   }
2472
2473   if (!strncmp (group, GROUP_DESKTOP_ACTION, strlen (GROUP_DESKTOP_ACTION))) {
2474     if (group[strlen (GROUP_DESKTOP_ACTION) - 1] == '\0') {
2475       print_fatal (kf, "file contains group \"%s\", which is an action "
2476                        "group with no action name\n", group);
2477       return FALSE;
2478     } else {
2479       char *action;
2480
2481       action = g_strdup (group + strlen (GROUP_DESKTOP_ACTION));
2482
2483       if (!key_is_valid (action, strlen (action))) {
2484         print_fatal (kf, "file contains group \"%s\", which has an invalid "
2485                          "action identifier, only alphanumeric characters and "
2486                          "'-' are allowed\n", group);
2487         g_free (action);
2488         return FALSE;
2489       }
2490
2491       g_hash_table_insert (kf->action_groups, action, action);
2492
2493       return TRUE;
2494     }
2495   }
2496
2497   print_fatal (kf, "file contains group \"%s\", but groups extending "
2498                    "the format should start with \"X-\"\n", group);
2499   return FALSE;
2500 }
2501
2502 static gboolean
2503 validate_required_keys (kf_validator         *kf,
2504                         const char           *group_name,
2505                         DesktopKeyDefinition *key_definitions,
2506                         unsigned int          n_keys)
2507 {
2508   gboolean      retval;
2509   unsigned int  i;
2510   GSList       *sl;
2511   GSList       *keys;
2512   GHashTable   *hashtable;
2513
2514   retval = TRUE;
2515
2516   hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
2517   keys = g_hash_table_lookup (kf->groups, group_name);
2518
2519   for (sl = keys; sl != NULL; sl = sl->next) {
2520     kf_keyvalue *keyvalue;
2521
2522     keyvalue = (kf_keyvalue *) sl->data;
2523     g_hash_table_insert (hashtable, keyvalue->key, keyvalue->key);
2524   }
2525
2526   for (i = 0; i < n_keys; i++) {
2527     if (key_definitions[i].required) {
2528       if (!g_hash_table_lookup (hashtable,
2529                                 key_definitions[i].name)) {
2530         print_fatal (kf, "required key \"%s\" in group \"%s\" is not "
2531                          "present\n",
2532                          key_definitions[i].name, group_name);
2533         retval = FALSE;
2534       }
2535     }
2536   }
2537
2538   g_hash_table_destroy (hashtable);
2539
2540   return retval;
2541 }
2542
2543 static gboolean
2544 validate_required_desktop_keys (kf_validator *kf)
2545 {
2546   return validate_required_keys (kf, kf->main_group,
2547                                  registered_desktop_keys,
2548                                  G_N_ELEMENTS (registered_desktop_keys));
2549 }
2550
2551 #define PRINT_ERROR_FOREACH_KEY(lower, real)                                 \
2552 static void                                                                  \
2553 print_error_foreach_##lower##_key (const char   *name,                       \
2554                                    kf_validator *kf)                         \
2555 {                                                                            \
2556   print_fatal (kf, "key \"%s\" is present in group \"%s\", but the type is " \
2557                    "\"%s\" while this key is only valid for type \"%s\"\n",  \
2558                    name, kf->main_group, kf->type_string, real);             \
2559 }
2560
2561 PRINT_ERROR_FOREACH_KEY (application, "Application")
2562 PRINT_ERROR_FOREACH_KEY (link,        "Link")
2563 PRINT_ERROR_FOREACH_KEY (fsdevice,    "FSDevice")
2564 PRINT_ERROR_FOREACH_KEY (mimetype,    "MimeType")
2565
2566 static gboolean
2567 validate_type_keys (kf_validator *kf)
2568 {
2569   gboolean retval;
2570
2571   retval = TRUE;
2572
2573   switch (kf->type) {
2574     case INVALID_TYPE:
2575       break;
2576     case APPLICATION_TYPE:
2577       g_list_foreach (kf->link_keys,
2578                       (GFunc) print_error_foreach_link_key, kf);
2579       g_list_foreach (kf->fsdevice_keys,
2580                       (GFunc) print_error_foreach_fsdevice_key, kf);
2581       g_list_foreach (kf->mimetype_keys,
2582                       (GFunc) print_error_foreach_mimetype_key, kf);
2583       retval = (g_list_length (kf->link_keys) +
2584                 g_list_length (kf->fsdevice_keys) +
2585                 g_list_length (kf->mimetype_keys) == 0);
2586       break;
2587     case LINK_TYPE:
2588       g_list_foreach (kf->application_keys,
2589                       (GFunc) print_error_foreach_application_key, kf);
2590       g_list_foreach (kf->fsdevice_keys,
2591                       (GFunc) print_error_foreach_fsdevice_key, kf);
2592       g_list_foreach (kf->mimetype_keys,
2593                       (GFunc) print_error_foreach_mimetype_key, kf);
2594       retval = (g_list_length (kf->application_keys) +
2595                 g_list_length (kf->fsdevice_keys) +
2596                 g_list_length (kf->mimetype_keys) == 0);
2597       break;
2598     case DIRECTORY_TYPE:
2599     case SERVICE_TYPE:
2600     case SERVICE_TYPE_TYPE:
2601       g_list_foreach (kf->application_keys,
2602                       (GFunc) print_error_foreach_application_key, kf);
2603       g_list_foreach (kf->link_keys,
2604                       (GFunc) print_error_foreach_link_key, kf);
2605       g_list_foreach (kf->fsdevice_keys,
2606                       (GFunc) print_error_foreach_fsdevice_key, kf);
2607       g_list_foreach (kf->mimetype_keys,
2608                       (GFunc) print_error_foreach_mimetype_key, kf);
2609       retval = (g_list_length (kf->application_keys) +
2610                 g_list_length (kf->link_keys) +
2611                 g_list_length (kf->fsdevice_keys) +
2612                 g_list_length (kf->mimetype_keys) == 0);
2613       break;
2614     case FSDEVICE_TYPE:
2615       g_list_foreach (kf->application_keys,
2616                       (GFunc) print_error_foreach_application_key, kf);
2617       g_list_foreach (kf->link_keys,
2618                       (GFunc) print_error_foreach_link_key, kf);
2619       g_list_foreach (kf->mimetype_keys,
2620                       (GFunc) print_error_foreach_mimetype_key, kf);
2621       retval = (g_list_length (kf->application_keys) +
2622                 g_list_length (kf->link_keys) +
2623                 g_list_length (kf->mimetype_keys) == 0);
2624       break;
2625     case MIMETYPE_TYPE:
2626       g_list_foreach (kf->application_keys,
2627                       (GFunc) print_error_foreach_application_key, kf);
2628       g_list_foreach (kf->link_keys,
2629                       (GFunc) print_error_foreach_link_key, kf);
2630       g_list_foreach (kf->fsdevice_keys,
2631                       (GFunc) print_error_foreach_fsdevice_key, kf);
2632       retval = (g_list_length (kf->application_keys) +
2633                 g_list_length (kf->link_keys) +
2634                 g_list_length (kf->fsdevice_keys) == 0);
2635       break;
2636     case LAST_TYPE:
2637       g_assert_not_reached ();
2638   }
2639
2640   return retval;
2641 }
2642
2643 static gboolean
2644 lookup_group_foreach_action (char         *key,
2645                              char         *value,
2646                              kf_validator *kf)
2647 {
2648   if (g_hash_table_lookup (kf->action_groups, key)) {
2649     gchar *group_name;
2650
2651     group_name = g_strconcat (GROUP_DESKTOP_ACTION, key, NULL);
2652     validate_required_keys (kf, group_name,
2653                             registered_action_keys,
2654                             G_N_ELEMENTS (registered_action_keys));
2655     g_free (group_name);
2656
2657     g_hash_table_remove (kf->action_groups, key);
2658     return TRUE;
2659   }
2660
2661   return FALSE;
2662 }
2663
2664 static void
2665 print_error_foreach_action (char         *key,
2666                             char         *value,
2667                             kf_validator *kf)
2668 {
2669   print_fatal (kf, "action \"%s\" is defined, but there is no matching "
2670                    "\"%s%s\" group\n", key, GROUP_DESKTOP_ACTION, key);
2671 }
2672
2673 static void
2674 print_error_foreach_group (char         *key,
2675                            char         *value,
2676                            kf_validator *kf)
2677 {
2678   print_fatal (kf, "action group \"%s%s\" exists, but there is no matching "
2679                    "action \"%s\"\n", GROUP_DESKTOP_ACTION, key, key);
2680 }
2681
2682 static gboolean
2683 validate_actions (kf_validator *kf)
2684 {
2685   g_hash_table_foreach_remove (kf->action_values,
2686                                (GHRFunc) lookup_group_foreach_action, kf);
2687
2688   g_hash_table_foreach (kf->action_values,
2689                         (GHFunc) print_error_foreach_action, kf);
2690
2691   g_hash_table_foreach (kf->action_groups,
2692                         (GHFunc) print_error_foreach_group, kf);
2693
2694   return (g_hash_table_size (kf->action_values) +
2695           g_hash_table_size (kf->action_groups) == 0);
2696 }
2697
2698 /* + These desktop entry files should have the extension .desktop.
2699  *   Checked.
2700  * + Desktop entries which describe how a directory is to be
2701  *   formatted/displayed should be simply called .directory.
2702  *   Checked.
2703  * + Using .kdelnk instead of .desktop as the file extension is deprecated.
2704  *   Checked.
2705  * FIXME: we're not doing what the spec says wrt Directory.
2706  */
2707 static gboolean
2708 validate_filename (kf_validator *kf)
2709 {
2710   if (kf->type == DIRECTORY_TYPE) {
2711     if (g_str_has_suffix (kf->filename, ".directory"))
2712       return TRUE;
2713     else {
2714       print_fatal (kf, "file is of type \"Directory\", but filename does not "
2715                        "have a .directory extension\n");
2716       return FALSE;
2717     }
2718   }
2719
2720   if (g_str_has_suffix (kf->filename, ".desktop"))
2721     return TRUE;
2722
2723   if (g_str_has_suffix (kf->filename, ".kdelnk")) {
2724     if (kf->kde_reserved_warnings || !kf->no_deprecated_warnings)
2725       print_warning (kf, "filename has a .kdelnk extension, which is "
2726                          "deprecated in favor of .desktop\n");
2727     return TRUE;
2728   }
2729
2730   print_fatal (kf, "filename does not have a .desktop extension\n");
2731   return FALSE;
2732 }
2733
2734 /* + Lines beginning with a # and blank lines are considered comments.
2735  *   Checked.
2736  */
2737 static gboolean
2738 validate_line_is_comment (kf_validator *kf,
2739                           const char   *line)
2740 {
2741   return (*line == '#' || *line == '\0');
2742 }
2743
2744 /* + A group header with name groupname is a line in the format: [groupname]
2745  *   Checked.
2746  * + Group names may contain all ASCII characters except for [ and ] and
2747  *   control characters.
2748  *   This is done in validate_group_name().
2749  */
2750 static gboolean
2751 validate_line_looks_like_group (kf_validator  *kf,
2752                                 const char    *line,
2753                                 char         **group)
2754 {
2755   char     *chomped;
2756   gboolean  result;
2757
2758   chomped = g_strdup (line);
2759   g_strchomp (chomped);
2760
2761   result = (*chomped == '[' && chomped[strlen (chomped) - 1] == ']');
2762
2763   if (result && strcmp (chomped, line))
2764     print_fatal (kf, "line \"%s\" ends with a space, but looks like a group. "
2765                      "The validation will continue, with the trailing spaces "
2766                      "ignored.\n", line);
2767
2768   if (group && result)
2769     *group = g_strndup (chomped + 1, strlen (chomped) - 2);
2770
2771   g_free (chomped);
2772
2773   return result;
2774 }
2775
2776 /* + Space before and after the equals sign should be ignored; the = sign is
2777  *   the actual delimiter.
2778  *   Checked.
2779  */
2780 static gboolean
2781 validate_line_looks_like_entry (kf_validator  *kf,
2782                                 const char    *line,
2783                                 char         **key,
2784                                 char         **value)
2785 {
2786   char *p;
2787
2788   p = g_utf8_strchr (line, -1, '=');
2789
2790   if (!p)
2791     return FALSE;
2792
2793   /* key must be non-empty */
2794   if (*p == line[0])
2795     return FALSE;
2796
2797   if (key) {
2798     *key = g_strndup (line, p - line);
2799     g_strchomp (*key);
2800   }
2801   if (value) {
2802     *value = g_strdup (p + 1);
2803     g_strchug (*value);
2804   }
2805
2806   return TRUE;
2807 }
2808
2809 /* + Only comments are accepted before the first group.
2810  *   Checked.
2811  * + The first group should be "Desktop Entry".
2812  *   Checked.
2813  * + Multiple groups may not have the same name.
2814  *   Checked.
2815  */
2816 static void
2817 validate_parse_line (kf_validator *kf)
2818 {
2819   char *line;
2820   int   len;
2821   char *group;
2822   char *key;
2823   char *value;
2824
2825   line = kf->parse_buffer->str;
2826   len  = kf->parse_buffer->len;
2827
2828   if (!kf->utf8_warning && !g_utf8_validate (line, len, NULL)) {
2829     print_warning (kf, "file contains lines that are not UTF-8 encoded. There "
2830                        "is no guarantee the validator will correctly work.\n");
2831     kf->utf8_warning = TRUE;
2832   }
2833
2834   if (g_ascii_isspace (*line)) {
2835     print_fatal (kf, "line \"%s\" starts with a space. Comment, group and "
2836                      "key-value lines should not start with a space. The "
2837                      "validation will continue, with the leading spaces "
2838                      "ignored.\n", line);
2839     while (g_ascii_isspace (*line))
2840       line++;
2841   }
2842
2843   if (validate_line_is_comment (kf, line))
2844     return;
2845
2846   group = NULL;
2847   if (validate_line_looks_like_group (kf, line, &group)) {
2848     if (!kf->current_group &&
2849         (strcmp (group, GROUP_DESKTOP_ENTRY) &&
2850          strcmp (group, GROUP_KDE_DESKTOP_ENTRY)))
2851       print_fatal (kf, "first group is not \"" GROUP_DESKTOP_ENTRY "\"\n");
2852
2853     if (kf->current_group && strcmp (kf->current_group, group))
2854       validate_keys_for_current_group (kf);
2855
2856     if (g_hash_table_lookup_extended (kf->groups, group, NULL, NULL)) {
2857       print_fatal (kf, "file contains multiple groups named \"%s\", but "
2858                        "multiple groups may not have the same name\n", group);
2859     } else {
2860       validate_group_name (kf, group);
2861       g_hash_table_insert (kf->groups, g_strdup (group), NULL);
2862     }
2863
2864     if (kf->current_group)
2865       g_free (kf->current_group);
2866     kf->current_group = group;
2867
2868     return;
2869   }
2870
2871   key = NULL;
2872   value = NULL;
2873   if (validate_line_looks_like_entry (kf, line, &key, &value)) {
2874     if (kf->current_group) {
2875       GSList      *keys;
2876       kf_keyvalue *keyvalue;
2877
2878       keyvalue = g_slice_new (kf_keyvalue);
2879       keyvalue->key = key;
2880       keyvalue->value = value;
2881
2882       keys = g_hash_table_lookup (kf->groups, kf->current_group);
2883       keys = g_slist_prepend (keys, keyvalue);
2884       g_hash_table_replace (kf->groups, g_strdup (kf->current_group), keys);
2885     } else {
2886       if (key)
2887         g_free (key);
2888       if (value)
2889         g_free (value);
2890
2891       print_fatal (kf, "file contains entry \"%s\" before the first group, "
2892                        "but only comments are accepted before the first "
2893                        "group\n", line);
2894     }
2895
2896     return;
2897   }
2898
2899   print_fatal (kf, "file contains line \"%s\", which is not a comment, "
2900                    "a group or an entry\n", line);
2901 }
2902
2903 /* + Desktop entry files are encoded as lines of 8-bit characters separated by
2904  *   LF characters.
2905  *   Checked.
2906  */
2907 static void
2908 validate_parse_data (kf_validator *kf,
2909                      char         *data,
2910                      int           length)
2911 {
2912   int i;
2913
2914   for (i = 0; i < length; i++) {
2915     if (data[i] == '\n') {
2916       if (i > 0 && data[i - 1] == '\r') {
2917         g_string_erase (kf->parse_buffer, kf->parse_buffer->len - 1, 1);
2918
2919         if (!kf->cr_error) {
2920           print_fatal (kf, "file contains at least one line ending with a "
2921                            "carriage return before the line feed, while lines "
2922                            "should only be separated by a line feed "
2923                            "character. First such line is: \"%s\"\n",
2924                            kf->parse_buffer->str);
2925           kf->cr_error = TRUE;
2926         }
2927       }
2928
2929       if (kf->parse_buffer->len > 0) {
2930         validate_parse_line (kf);
2931         g_string_erase (kf->parse_buffer, 0, -1);
2932       }
2933
2934     } else if (data[i] == '\r') {
2935         if (!kf->cr_error) {
2936           print_fatal (kf, "file contains at least one line ending with a "
2937                            "carriage return, while lines should only be "
2938                            "separated by a line feed character. First such "
2939                            "line is: \"%s\"\n", kf->parse_buffer->str);
2940           kf->cr_error = TRUE;
2941         }
2942
2943         data[i] = '\n';
2944         i--;
2945     } else
2946       g_string_append_c (kf->parse_buffer, data[i]);
2947   }
2948 }
2949
2950 static void
2951 validate_flush_parse_buffer (kf_validator *kf)
2952 {
2953   if (kf->parse_buffer->len > 0) {
2954       validate_parse_line (kf);
2955       g_string_erase (kf->parse_buffer, 0, -1);
2956   }
2957
2958   if (kf->current_group)
2959     validate_keys_for_current_group (kf);
2960 }
2961
2962 #define VALIDATE_READ_SIZE 4096
2963 static gboolean
2964 validate_parse_from_fd (kf_validator *kf,
2965                         int           fd)
2966 {
2967   int         bytes_read;
2968   struct stat stat_buf;
2969   char        read_buf[VALIDATE_READ_SIZE];
2970
2971   if (fstat (fd, &stat_buf) < 0) {
2972     print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
2973     return FALSE;
2974   }
2975
2976   if (!S_ISREG (stat_buf.st_mode)) {
2977     print_fatal (kf, "file is not a regular file\n");
2978     return FALSE;
2979   }
2980
2981   if (stat_buf.st_size == 0) {
2982     print_fatal (kf, "file is empty\n");
2983     return FALSE;
2984   }
2985
2986   bytes_read = 0;
2987   while (1) {
2988     bytes_read = read (fd, read_buf, VALIDATE_READ_SIZE);
2989
2990     if (bytes_read == 0)  /* End of File */
2991       break;
2992
2993     if (bytes_read < 0) {
2994       if (errno == EINTR || errno == EAGAIN)
2995         continue;
2996
2997       /* let's validate what we already have */
2998       validate_flush_parse_buffer (kf);
2999
3000       print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
3001       return FALSE;
3002     }
3003
3004     validate_parse_data (kf, read_buf, bytes_read);
3005   }
3006
3007   validate_flush_parse_buffer (kf);
3008
3009   return TRUE;
3010 }
3011
3012 static gboolean
3013 validate_load_and_parse (kf_validator *kf)
3014 {
3015   int      fd;
3016   gboolean ret;
3017
3018   fd = g_open (kf->filename, O_RDONLY, 0);
3019
3020   if (fd < 0) {
3021     print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
3022     return FALSE;
3023   }
3024
3025   ret = validate_parse_from_fd (kf, fd);
3026
3027   close (fd);
3028
3029   return ret;
3030 }
3031
3032 static gboolean
3033 groups_hashtable_free (gpointer key,
3034                        gpointer value,
3035                        gpointer data)
3036 {
3037   GSList *list;
3038   GSList *sl;
3039
3040   list = (GSList *) value;
3041   for (sl = list; sl != NULL; sl = sl->next) {
3042     kf_keyvalue *keyvalue;
3043
3044     keyvalue = (kf_keyvalue *) sl->data;
3045     g_free (keyvalue->key);
3046     g_free (keyvalue->value);
3047     g_slice_free (kf_keyvalue, keyvalue);
3048   }
3049
3050   g_slist_free (list);
3051
3052   return TRUE;
3053 }
3054
3055 gboolean
3056 desktop_file_validate (const char *filename,
3057                        gboolean    warn_kde,
3058                        gboolean    no_warn_deprecated,
3059                        gboolean    no_hints)
3060 {
3061   kf_validator kf;
3062
3063   /* just a consistency check */
3064   g_assert (G_N_ELEMENTS (registered_types) == LAST_TYPE - 1);
3065
3066   kf.filename               = filename;
3067   kf.parse_buffer           = g_string_new ("");
3068   kf.utf8_warning           = FALSE;
3069   kf.cr_error               = FALSE;
3070   kf.current_group          = NULL;
3071   kf.groups                 = g_hash_table_new_full (g_str_hash, g_str_equal,
3072                                                      g_free, NULL);
3073   kf.current_keys           = NULL;
3074   kf.kde_reserved_warnings  = warn_kde;
3075   kf.no_deprecated_warnings = no_warn_deprecated;
3076   kf.no_hints               = no_hints;
3077
3078   kf.main_group       = NULL;
3079   kf.type             = INVALID_TYPE;
3080   kf.type_string      = NULL;
3081   kf.show_in          = FALSE;
3082   kf.application_keys = NULL;
3083   kf.link_keys        = NULL;
3084   kf.fsdevice_keys    = NULL;
3085   kf.mimetype_keys    = NULL;
3086   kf.action_values    = g_hash_table_new_full (g_str_hash, g_str_equal,
3087                                                NULL, g_free);
3088   kf.action_groups    = g_hash_table_new_full (g_str_hash, g_str_equal,
3089                                                NULL, g_free);
3090   kf.fatal_error      = FALSE;
3091
3092   validate_load_and_parse (&kf);
3093   //FIXME: this does not work well if there are both a Desktop Entry and a KDE
3094   //Desktop Entry groups since only the last one will be validated for this.
3095   if (kf.main_group) {
3096     validate_required_desktop_keys (&kf);
3097     validate_type_keys (&kf);
3098   }
3099   validate_actions (&kf);
3100   validate_filename (&kf);
3101
3102   g_list_foreach (kf.application_keys, (GFunc) g_free, NULL);
3103   g_list_free (kf.application_keys);
3104   g_list_foreach (kf.link_keys, (GFunc) g_free, NULL);
3105   g_list_free (kf.link_keys);
3106   g_list_foreach (kf.fsdevice_keys, (GFunc) g_free, NULL);
3107   g_list_free (kf.fsdevice_keys);
3108   g_list_foreach (kf.mimetype_keys, (GFunc) g_free, NULL);
3109   g_list_free (kf.mimetype_keys);
3110
3111   g_hash_table_destroy (kf.action_values);
3112   g_hash_table_destroy (kf.action_groups);
3113
3114   g_assert (kf.current_keys == NULL);
3115   /* we can't add an automatic destroy handler for the value because we replace
3116    * it when adding keys, and this means we'd have to copy the value each time
3117    * we replace it */
3118   g_hash_table_foreach_remove (kf.groups, groups_hashtable_free, NULL);
3119   g_hash_table_destroy (kf.groups);
3120   g_free (kf.current_group);
3121   g_string_free (kf.parse_buffer, TRUE);
3122
3123   return (!kf.fatal_error);
3124 }
3125
3126 /* return FALSE if we were unable to fix the file */
3127 gboolean
3128 desktop_file_fixup (GKeyFile   *keyfile,
3129                     const char *filename)
3130 {
3131   if (g_key_file_has_group (keyfile, GROUP_KDE_DESKTOP_ENTRY)) {
3132     g_printerr ("%s: warning: renaming deprecated \"%s\" group to \"%s\"\n",
3133                 filename, GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY);
3134     dfu_key_file_rename_group (keyfile,
3135                                GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY);
3136   }
3137
3138   return TRUE;
3139 }