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