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