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