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