1 /* validate.c: validate a desktop entry file
2 * vim: set ts=2 sw=2 et: */
5 * Copyright (C) 2007-2009 Vincent Untz <vuntz@gnome.org>
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.
10 * Mark McLoughlin <mark@skynet.ie>
11 * Havoc Pennington <hp@pobox.com>
12 * Ray Strode <rstrode@redhat.com>
14 * A portion of this code comes from glib (gkeyfile.c)
15 * Authors of gkeyfile.c are:
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.
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.
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,
43 #include <glib/gstdio.h>
45 #include "keyfileutils.h"
46 #include "mimeutils.h"
49 /*FIXME: document where we are stricter than the spec
50 * + only UTF-8 (so no Legacy-Mixed encoding)
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.
67 /* Types reserved for KDE */
74 /* Deprecated types */
83 DESKTOP_LOCALESTRING_TYPE,
86 DESKTOP_STRING_LIST_TYPE,
87 DESKTOP_LOCALESTRING_LIST_TYPE,
88 /* Deprecated types */
90 DESKTOP_REGEXP_LIST_TYPE
93 typedef struct _kf_keyvalue kf_keyvalue;
100 typedef struct _kf_validator kf_validator;
102 struct _kf_validator {
103 const char *filename;
105 GString *parse_buffer;
106 gboolean utf8_warning;
111 GHashTable *current_keys;
113 gboolean kde_reserved_warnings;
114 gboolean no_deprecated_warnings;
122 GList *application_keys;
124 GList *fsdevice_keys;
125 GList *mimetype_keys;
127 GHashTable *action_values;
128 GHashTable *action_groups;
130 gboolean fatal_error;
134 validate_string_key (kf_validator *kf,
139 validate_localestring_key (kf_validator *kf,
144 validate_boolean_key (kf_validator *kf,
149 validate_numeric_key (kf_validator *kf,
154 validate_string_list_key (kf_validator *kf,
159 validate_regexp_list_key (kf_validator *kf,
164 validate_localestring_list_key (kf_validator *kf,
170 handle_type_key (kf_validator *kf,
171 const char *locale_key,
174 handle_version_key (kf_validator *kf,
175 const char *locale_key,
178 handle_comment_key (kf_validator *kf,
179 const char *locale_key,
182 handle_icon_key (kf_validator *kf,
183 const char *locale_key,
186 handle_show_in_key (kf_validator *kf,
187 const char *locale_key,
190 handle_desktop_exec_key (kf_validator *kf,
191 const char *locale_key,
194 handle_exec_key (kf_validator *kf,
195 const char *locale_key,
198 handle_path_key (kf_validator *kf,
199 const char *locale_key,
202 handle_mime_key (kf_validator *kf,
203 const char *locale_key,
206 handle_categories_key (kf_validator *kf,
207 const char *locale_key,
210 handle_actions_key (kf_validator *kf,
211 const char *locale_key,
214 handle_dev_key (kf_validator *kf,
215 const char *locale_key,
218 handle_mountpoint_key (kf_validator *kf,
219 const char *locale_key,
222 handle_encoding_key (kf_validator *kf,
223 const char *locale_key,
226 handle_autostart_condition_key (kf_validator *kf,
227 const char *locale_key,
230 handle_key_for_application (kf_validator *kf,
231 const char *locale_key,
234 handle_key_for_link (kf_validator *kf,
235 const char *locale_key,
238 handle_key_for_fsdevice (kf_validator *kf,
239 const char *locale_key,
242 handle_key_for_mimetype (kf_validator *kf,
243 const char *locale_key,
249 gboolean kde_reserved;
251 } registered_types[] = {
252 { APPLICATION_TYPE, "Application", FALSE, FALSE },
253 { LINK_TYPE, "Link", FALSE, FALSE },
254 { DIRECTORY_TYPE, "Directory", FALSE, FALSE },
255 { SERVICE_TYPE, "Service", TRUE, FALSE },
256 { SERVICE_TYPE_TYPE, "ServiceType", TRUE, FALSE },
257 { FSDEVICE_TYPE, "FSDevice", TRUE, FALSE },
258 { MIMETYPE_TYPE, "MimeType", FALSE, TRUE }
263 gboolean (* validate) (kf_validator *kf,
267 } validate_for_type[] = {
268 { DESKTOP_STRING_TYPE, validate_string_key },
269 { DESKTOP_LOCALESTRING_TYPE, validate_localestring_key },
270 { DESKTOP_BOOLEAN_TYPE, validate_boolean_key },
271 { DESKTOP_NUMERIC_TYPE, validate_numeric_key },
272 { DESKTOP_STRING_LIST_TYPE, validate_string_list_key },
273 { DESKTOP_REGEXP_LIST_TYPE, validate_regexp_list_key },
274 { DESKTOP_LOCALESTRING_LIST_TYPE, validate_localestring_list_key }
282 gboolean kde_reserved;
283 gboolean (* handle_and_validate) (kf_validator *kf,
284 const char *locale_key,
286 } DesktopKeyDefinition;
288 static DesktopKeyDefinition registered_desktop_keys[] = {
289 { DESKTOP_STRING_TYPE, "Type", TRUE, FALSE, FALSE, handle_type_key },
290 /* it is numeric according to the spec, but it's not true in previous
291 * versions of the spec. handle_version_key() will manage this */
292 { DESKTOP_STRING_TYPE, "Version", FALSE, FALSE, FALSE, handle_version_key },
293 { DESKTOP_LOCALESTRING_TYPE, "Name", TRUE, FALSE, FALSE, NULL },
294 { DESKTOP_LOCALESTRING_TYPE, "GenericName", FALSE, FALSE, FALSE, NULL },
295 { DESKTOP_BOOLEAN_TYPE, "NoDisplay", FALSE, FALSE, FALSE, NULL },
296 { DESKTOP_LOCALESTRING_TYPE, "Comment", FALSE, FALSE, FALSE, handle_comment_key },
297 { DESKTOP_LOCALESTRING_TYPE, "Icon", FALSE, FALSE, FALSE, handle_icon_key },
298 { DESKTOP_BOOLEAN_TYPE, "Hidden", FALSE, FALSE, FALSE, NULL },
299 { DESKTOP_STRING_LIST_TYPE, "OnlyShowIn", FALSE, FALSE, FALSE, handle_show_in_key },
300 { DESKTOP_STRING_LIST_TYPE, "NotShowIn", FALSE, FALSE, FALSE, handle_show_in_key },
301 { DESKTOP_STRING_TYPE, "TryExec", FALSE, FALSE, FALSE, handle_key_for_application },
302 { DESKTOP_STRING_TYPE, "Exec", FALSE, FALSE, FALSE, handle_desktop_exec_key },
303 { DESKTOP_STRING_TYPE, "Path", FALSE, FALSE, FALSE, handle_path_key },
304 { DESKTOP_BOOLEAN_TYPE, "Terminal", FALSE, FALSE, FALSE, handle_key_for_application },
305 { DESKTOP_STRING_LIST_TYPE, "MimeType", FALSE, FALSE, FALSE, handle_mime_key },
306 { DESKTOP_STRING_LIST_TYPE, "Categories", FALSE, FALSE, FALSE, handle_categories_key },
307 { DESKTOP_BOOLEAN_TYPE, "StartupNotify", FALSE, FALSE, FALSE, handle_key_for_application },
308 { DESKTOP_STRING_TYPE, "StartupWMClass", FALSE, FALSE, FALSE, handle_key_for_application },
309 { DESKTOP_STRING_TYPE, "URL", FALSE, FALSE, FALSE, handle_key_for_link },
310 /* since 1.1 (used to be a key reserved for KDE since 0.9.4) */
311 { DESKTOP_LOCALESTRING_LIST_TYPE, "Keywords", FALSE, FALSE, FALSE, NULL },
312 /* since 1.1 (used to be in the spec before 1.0, but was not really
314 { DESKTOP_STRING_LIST_TYPE, "Actions", FALSE, FALSE, FALSE, handle_actions_key },
316 { DESKTOP_BOOLEAN_TYPE, "DBusActivatable", FALSE, FALSE, FALSE, NULL },
318 /* Keys reserved for KDE */
321 { DESKTOP_STRING_TYPE, "ServiceTypes", FALSE, FALSE, TRUE, NULL },
322 { DESKTOP_STRING_TYPE, "DocPath", FALSE, FALSE, TRUE, NULL },
323 { DESKTOP_STRING_TYPE, "InitialPreference", FALSE, FALSE, TRUE, NULL },
325 { DESKTOP_STRING_TYPE, "Dev", FALSE, FALSE, TRUE, handle_dev_key },
326 { DESKTOP_STRING_TYPE, "FSType", FALSE, FALSE, TRUE, handle_key_for_fsdevice },
327 { DESKTOP_STRING_TYPE, "MountPoint", FALSE, FALSE, TRUE, handle_mountpoint_key },
328 { DESKTOP_BOOLEAN_TYPE, "ReadOnly", FALSE, FALSE, TRUE, handle_key_for_fsdevice },
329 { DESKTOP_STRING_TYPE, "UnmountIcon", FALSE, FALSE, TRUE, handle_key_for_fsdevice },
331 /* Deprecated keys */
334 { DESKTOP_STRING_TYPE, "Protocols", FALSE, TRUE, FALSE, NULL },
335 { DESKTOP_STRING_TYPE, "Extensions", FALSE, TRUE, FALSE, NULL },
336 { DESKTOP_STRING_TYPE, "BinaryPattern", FALSE, TRUE, FALSE, NULL },
337 { DESKTOP_STRING_TYPE, "MapNotify", FALSE, TRUE, FALSE, NULL },
339 { DESKTOP_REGEXP_LIST_TYPE, "Patterns", FALSE, TRUE, FALSE, handle_key_for_mimetype },
340 { DESKTOP_STRING_TYPE, "DefaultApp", FALSE, TRUE, FALSE, handle_key_for_mimetype },
341 { DESKTOP_STRING_TYPE, "MiniIcon", FALSE, TRUE, FALSE, NULL },
342 { DESKTOP_STRING_TYPE, "TerminalOptions", FALSE, TRUE, FALSE, NULL },
344 { DESKTOP_STRING_TYPE, "Encoding", FALSE, TRUE, FALSE, handle_encoding_key },
345 { DESKTOP_LOCALESTRING_TYPE, "SwallowTitle", FALSE, TRUE, FALSE, NULL },
346 { DESKTOP_STRING_TYPE, "SwallowExec", FALSE, TRUE, FALSE, NULL },
348 { DESKTOP_STRING_LIST_TYPE, "SortOrder", FALSE, TRUE, FALSE, NULL },
349 { DESKTOP_REGEXP_LIST_TYPE, "FilePattern", FALSE, TRUE, FALSE, NULL },
351 /* Keys from other specifications */
353 /* Autostart spec, currently proposed; adopted by GNOME */
354 { DESKTOP_STRING_TYPE, "AutostartCondition", FALSE, FALSE, FALSE, handle_autostart_condition_key }
357 static DesktopKeyDefinition registered_action_keys[] = {
358 { DESKTOP_LOCALESTRING_TYPE, "Name", TRUE, FALSE, FALSE, NULL },
359 { DESKTOP_LOCALESTRING_TYPE, "Icon", FALSE, FALSE, FALSE, handle_icon_key },
360 { DESKTOP_STRING_LIST_TYPE, "OnlyShowIn", FALSE, TRUE, FALSE, handle_show_in_key },
361 { DESKTOP_STRING_LIST_TYPE, "NotShowIn", FALSE, TRUE, FALSE, handle_show_in_key },
362 { DESKTOP_STRING_TYPE, "Exec", TRUE, FALSE, FALSE, handle_exec_key }
365 static const char *show_in_registered[] = {
366 "GNOME", "KDE", "LXDE", "MATE", "Razor", "ROX", "TDE", "Unity", "XFCE", "Old"
371 const char *first_arg[3];
372 unsigned int additional_args;
373 } registered_autostart_condition[] = {
374 { "GNOME", { NULL }, 1 },
375 { "GNOME3", { "if-session", "unless-session", NULL }, 1},
376 { "GSettings", { NULL }, 2 }
382 gboolean require_only_show_in;
384 const char *requires[2];
385 const char *suggests[4];
386 } registered_categories[] = {
387 { "AudioVideo", TRUE, FALSE, FALSE, { NULL }, { NULL } },
388 { "Audio", TRUE, FALSE, FALSE, { "AudioVideo", NULL }, { NULL } },
389 { "Video", TRUE, FALSE, FALSE, { "AudioVideo", NULL }, { NULL } },
390 { "Development", TRUE, FALSE, FALSE, { NULL }, { NULL } },
391 { "Education", TRUE, FALSE, FALSE, { NULL }, { NULL } },
392 { "Game", TRUE, FALSE, FALSE, { NULL }, { NULL } },
393 { "Graphics", TRUE, FALSE, FALSE, { NULL }, { NULL } },
394 { "Network", TRUE, FALSE, FALSE, { NULL }, { NULL } },
395 { "Office", TRUE, FALSE, FALSE, { NULL }, { NULL } },
396 { "Science", TRUE, FALSE, FALSE, { NULL }, { NULL } },
397 { "Settings", TRUE, FALSE, FALSE, { NULL }, { NULL } },
398 { "System", TRUE, FALSE, FALSE, { NULL }, { NULL } },
399 { "Utility", TRUE, FALSE, FALSE, { NULL }, { NULL } },
400 { "Audio", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
401 { "Video", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
402 { "Building", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
403 { "Debugger", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
404 { "IDE", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
405 { "GUIDesigner", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
406 { "Profiling", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
407 { "RevisionControl", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
408 { "Translation", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
409 { "Calendar", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
410 { "ContactManagement", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
411 { "Database", FALSE, FALSE, FALSE, { NULL }, { "Office", "Development", "AudioVideo", NULL } },
412 { "Dictionary", FALSE, FALSE, FALSE, { NULL }, { "Office", "TextTools", NULL } },
413 { "Chart", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
414 { "Email", FALSE, FALSE, FALSE, { NULL }, { "Office", "Network", NULL } },
415 { "Finance", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
416 { "FlowChart", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
417 { "PDA", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
418 { "ProjectManagement", FALSE, FALSE, FALSE, { NULL }, { "Office", "Development", NULL } },
419 { "Presentation", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
420 { "Spreadsheet", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
421 { "WordProcessor", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
422 { "2DGraphics", FALSE, FALSE, FALSE, { NULL }, { "Graphics", NULL } },
423 { "VectorGraphics", FALSE, FALSE, FALSE, { NULL }, { "Graphics;2DGraphics", NULL } },
424 { "RasterGraphics", FALSE, FALSE, FALSE, { NULL }, { "Graphics;2DGraphics", NULL } },
425 { "3DGraphics", FALSE, FALSE, FALSE, { NULL }, { "Graphics", NULL } },
426 { "Scanning", FALSE, FALSE, FALSE, { NULL }, { "Graphics", NULL } },
427 { "OCR", FALSE, FALSE, FALSE, { NULL }, { "Graphics;Scanning", NULL } },
428 { "Photography", FALSE, FALSE, FALSE, { NULL }, { "Graphics", "Office", NULL } },
429 { "Publishing", FALSE, FALSE, FALSE, { NULL }, { "Graphics", "Office", NULL } },
430 { "Viewer", FALSE, FALSE, FALSE, { NULL }, { "Graphics", "Office", NULL } },
431 { "TextTools", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
432 { "DesktopSettings", FALSE, FALSE, FALSE, { NULL }, { "Settings", NULL } },
433 { "HardwareSettings", FALSE, FALSE, FALSE, { NULL }, { "Settings", NULL } },
434 { "Printing", FALSE, FALSE, FALSE, { NULL }, { "HardwareSettings;Settings", NULL } },
435 { "PackageManager", FALSE, FALSE, FALSE, { NULL }, { "Settings", NULL } },
436 { "Dialup", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
437 { "InstantMessaging", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
438 { "Chat", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
439 { "IRCClient", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
440 { "Feed", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
441 { "FileTransfer", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
442 { "HamRadio", FALSE, FALSE, FALSE, { NULL }, { "Network", "Audio", NULL } },
443 { "News", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
444 { "P2P", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
445 { "RemoteAccess", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
446 { "Telephony", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
447 { "TelephonyTools", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
448 { "VideoConference", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
449 { "WebBrowser", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
450 { "WebDevelopment", FALSE, FALSE, FALSE, { NULL }, { "Network", "Development", NULL } },
451 { "Midi", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
452 { "Mixer", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
453 { "Sequencer", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
454 { "Tuner", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
455 { "TV", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Video", NULL } },
456 { "AudioVideoEditing", FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
457 { "Player", FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
458 { "Recorder", FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
459 { "DiscBurning", FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
460 { "ActionGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
461 { "AdventureGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
462 { "ArcadeGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
463 { "BoardGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
464 { "BlocksGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
465 { "CardGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
466 { "KidsGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
467 { "LogicGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
468 { "RolePlaying", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
469 { "Shooter", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
470 { "Simulation", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
471 { "SportsGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
472 { "StrategyGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
473 { "Art", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
474 { "Construction", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
475 { "Music", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo", "Education", NULL } },
476 { "Languages", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
477 { "ArtificialIntelligence", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
478 { "Astronomy", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
479 { "Biology", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
480 { "Chemistry", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
481 { "ComputerScience", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
482 { "DataVisualization", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
483 { "Economy", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
484 { "Electricity", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
485 { "Geography", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
486 { "Geology", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
487 { "Geoscience", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
488 { "History", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
489 { "Humanities", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
490 { "ImageProcessing", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
491 { "Literature", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
492 { "Maps", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", "Utility", NULL } },
493 { "Math", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
494 { "NumericalAnalysis", FALSE, FALSE, FALSE, { NULL }, { "Education;Math", "Science;Math", NULL } },
495 { "MedicalSoftware", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
496 { "Physics", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
497 { "Robotics", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
498 { "Spirituality", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", "Utility", NULL } },
499 { "Sports", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
500 { "ParallelComputing", FALSE, FALSE, FALSE, { NULL }, { "Education;ComputerScience", "Science;ComputerScience", NULL } },
501 { "Amusement", FALSE, FALSE, FALSE, { NULL }, { NULL } },
502 { "Archiving", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
503 { "Compression", FALSE, FALSE, FALSE, { NULL }, { "Utility;Archiving", NULL } },
504 { "Electronics", FALSE, FALSE, FALSE, { NULL }, { NULL } },
505 { "Emulator", FALSE, FALSE, FALSE, { NULL }, { "System", "Game", NULL } },
506 { "Engineering", FALSE, FALSE, FALSE, { NULL }, { NULL } },
507 { "FileTools", FALSE, FALSE, FALSE, { NULL }, { "Utility", "System", NULL } },
508 { "FileManager", FALSE, FALSE, FALSE, { NULL }, { "System;FileTools", NULL } },
509 { "TerminalEmulator", FALSE, FALSE, FALSE, { NULL }, { "System", NULL } },
510 { "Filesystem", FALSE, FALSE, FALSE, { NULL }, { "System", NULL } },
511 { "Monitor", FALSE, FALSE, FALSE, { NULL }, { "System", "Network", NULL } },
512 { "Security", FALSE, FALSE, FALSE, { NULL }, { "Settings", "System", NULL } },
513 { "Accessibility", FALSE, FALSE, FALSE, { NULL }, { "Settings", "Utility", NULL } },
514 { "Calculator", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
515 { "Clock", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
516 { "TextEditor", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
517 { "Documentation", FALSE, FALSE, FALSE, { NULL }, { NULL } },
518 { "Adult", FALSE, FALSE, FALSE, { NULL }, { NULL } },
519 { "Core", FALSE, FALSE, FALSE, { NULL }, { NULL } },
520 { "KDE", FALSE, FALSE, FALSE, { NULL }, { "Qt", NULL } },
521 { "GNOME", FALSE, FALSE, FALSE, { NULL }, { "GTK", NULL } },
522 { "XFCE", FALSE, FALSE, FALSE, { NULL }, { "GTK", NULL } },
523 { "GTK", FALSE, FALSE, FALSE, { NULL }, { NULL } },
524 { "Qt", FALSE, FALSE, FALSE, { NULL }, { NULL } },
525 { "Motif", FALSE, FALSE, FALSE, { NULL }, { NULL } },
526 { "Java", FALSE, FALSE, FALSE, { NULL }, { NULL } },
527 { "ConsoleOnly", FALSE, FALSE, FALSE, { NULL }, { NULL } },
528 { "Screensaver", FALSE, TRUE, FALSE, { NULL }, { NULL } },
529 { "TrayIcon", FALSE, TRUE, FALSE, { NULL }, { NULL } },
530 { "Applet", FALSE, TRUE, FALSE, { NULL }, { NULL } },
531 { "Shell", FALSE, TRUE, FALSE, { NULL }, { NULL } },
532 { "Application", FALSE, FALSE, TRUE, { NULL }, { NULL } },
533 { "Applications", FALSE, FALSE, TRUE, { NULL }, { NULL } }
537 print_fatal (kf_validator *kf, const char *format, ...)
542 g_return_if_fail (kf != NULL && format != NULL);
544 kf->fatal_error = TRUE;
546 va_start (args, format);
547 str = g_strdup_vprintf (format, args);
550 g_print ("%s: error: %s", kf->filename, str);
556 print_future_fatal (kf_validator *kf, const char *format, ...)
561 g_return_if_fail (kf != NULL && format != NULL);
563 va_start (args, format);
564 str = g_strdup_vprintf (format, args);
567 g_print ("%s: error: (will be fatal in the future): %s", kf->filename, str);
573 print_warning (kf_validator *kf, const char *format, ...)
578 g_return_if_fail (kf != NULL && format != NULL);
580 va_start (args, format);
581 str = g_strdup_vprintf (format, args);
584 g_print ("%s: warning: %s", kf->filename, str);
590 print_hint (kf_validator *kf, const char *format, ...)
595 g_return_if_fail (kf != NULL && format != NULL);
600 va_start (args, format);
601 str = g_strdup_vprintf (format, args);
604 g_print ("%s: hint: %s", kf->filename, str);
609 /* + Key names must contain only the characters A-Za-z0-9-.
613 key_is_valid (const char *key,
619 for (i = 0; i < len; i++) {
621 if (!g_ascii_isalnum (c) && c != '-')
628 /* + Values of type string may contain all ASCII characters except for control
633 validate_string_key (kf_validator *kf,
643 for (i = 0; value[i] != '\0'; i++) {
644 if (g_ascii_iscntrl (value[i])) {
651 print_fatal (kf, "value \"%s\" for string key \"%s\" in group \"%s\" "
652 "contains invalid characters, string values may contain "
653 "all ASCII characters except for control characters\n",
654 value, key, kf->current_group);
662 /* + Values of type localestring are user displayable, and are encoded in
665 * + If a postfixed key occurs, the same key must be also present without the
670 validate_localestring_key (kf_validator *kf,
678 locale_key = g_strdup_printf ("%s[%s]", key, locale);
680 locale_key = g_strdup_printf ("%s", key);
682 if (!g_utf8_validate (value, -1, NULL)) {
683 print_fatal (kf, "value \"%s\" for locale string key \"%s\" in group "
684 "\"%s\" contains invalid UTF-8 characters, locale string "
685 "values should be encoded in UTF-8\n",
686 value, locale_key, kf->current_group);
692 if (!g_hash_table_lookup (kf->current_keys, key)) {
693 print_fatal (kf, "key \"%s\" in group \"%s\" is a localized key, but "
694 "there is no non-localized key \"%s\"\n",
695 locale_key, kf->current_group, key);
706 /* + Values of type boolean must either be the string true or false.
708 * + Historically some booleans have been represented by the numeric entries 0
709 * or 1. With this version of the standard they are now to be represented as
710 * a boolean string. However, if an implementation is reading a pre-1.0
711 * desktop entry, it should interpret 0 and 1 as false and true,
716 validate_boolean_key (kf_validator *kf,
721 if (strcmp (value, "true") && strcmp (value, "false") &&
722 strcmp (value, "0") && strcmp (value, "1")) {
723 print_fatal (kf, "value \"%s\" for boolean key \"%s\" in group \"%s\" "
724 "contains invalid characters, boolean values must be "
725 "\"false\" or \"true\"\n",
726 value, key, kf->current_group);
730 if (!kf->no_deprecated_warnings &&
731 (!strcmp (value, "0") || !strcmp (value, "1")))
732 print_warning (kf, "boolean key \"%s\" in group \"%s\" has value \"%s\", "
733 "which is deprecated: boolean values should be "
734 "\"false\" or \"true\"\n",
735 key, kf->current_group, value);
740 /* + Values of type numeric must be a valid floating point number as recognized
741 * by the %f specifier for scanf.
745 validate_numeric_key (kf_validator *kf,
753 res = sscanf (value, "%f", &d);
755 print_fatal (kf, "value \"%s\" for numeric key \"%s\" in group \"%s\" "
756 "contains invalid characters, numeric values must be "
757 "valid floating point numbers\n",
758 value, key, kf->current_group);
765 /* + Values of type string may contain all ASCII characters except for control
768 * + The multiple values should be separated by a semicolon. Those keys which
769 * have several values should have a semicolon as the trailing character.
771 * + FIXME: how should an empty list be handled?
774 validate_string_regexp_list_key (kf_validator *kf,
785 for (i = 0; value[i] != '\0'; i++) {
786 if (g_ascii_iscntrl (value[i])) {
793 print_fatal (kf, "value \"%s\" for %s list key \"%s\" in group \"%s\" "
794 "contains invalid character '%c', %s list values may "
795 "contain all ASCII characters except for control "
797 value, type, key, kf->current_group, value[i], type);
802 if (i > 0 && value[i - 1] != ';') {
803 print_fatal (kf, "value \"%s\" for %s list key \"%s\" in group \"%s\" "
804 "does not have a semicolon (';') as trailing "
806 value, type, key, kf->current_group);
811 if (i > 1 && value[i - 1] == ';' && value[i - 2] == '\\' &&
812 (i < 3 || value[i - 3] != '\\')) {
813 print_fatal (kf, "value \"%s\" for %s list key \"%s\" in group \"%s\" "
814 "has an escaped semicolon (';') as trailing character\n",
815 value, type, key, kf->current_group);
824 validate_string_list_key (kf_validator *kf,
829 return validate_string_regexp_list_key (kf, key, locale, value, "string");
833 validate_regexp_list_key (kf_validator *kf,
838 return validate_string_regexp_list_key (kf, key, locale, value, "regexp");
841 /* + Values of type localestring are user displayable, and are encoded in
843 * FIXME: partly checked; we checked the whole value is encored in UTF-8, but
844 * not that each value of the list is. Although this might be equivalent?
845 * + If a postfixed key occurs, the same key must be also present without the
848 * + The multiple values should be separated by a semicolon. Those keys which
849 * have several values should have a semicolon as the trailing character.
850 * FIXME: partly checked. We use checks that work for sure for ascii
851 * characters, but that could possibly fail in some weird UTF-8 strings.
852 * + FIXME: how should an empty list be handled?
855 validate_localestring_list_key (kf_validator *kf,
864 locale_key = g_strdup_printf ("%s[%s]", key, locale);
866 locale_key = g_strdup_printf ("%s", key);
869 if (!g_utf8_validate (value, -1, NULL)) {
870 print_fatal (kf, "value \"%s\" for locale string list key \"%s\" in group "
871 "\"%s\" contains invalid UTF-8 characters, locale string "
872 "list values should be encoded in UTF-8\n",
873 value, locale_key, kf->current_group);
879 len = strlen (value);
881 if (len > 0 && value[len - 1] != ';') {
882 print_fatal (kf, "value \"%s\" for locale string list key \"%s\" in group "
883 "\"%s\" does not have a semicolon (';') as trailing "
885 value, locale_key, kf->current_group);
890 if (len > 1 && value[len - 1] == ';' && value[len - 2] == '\\' &&
891 (len < 3 || value[len - 3] != '\\')) {
892 print_fatal (kf, "value \"%s\" for locale string list key \"%s\" in group "
893 "\"%s\" has an escaped semicolon (';') as trailing "
895 value, locale_key, kf->current_group);
900 if (!g_hash_table_lookup (kf->current_keys, key)) {
901 print_fatal (kf, "key \"%s\" in group \"%s\" is a localized key, but "
902 "there is no non-localized key \"%s\"\n",
903 locale_key, kf->current_group, key);
914 /* + This specification defines 3 types of desktop entries: Application
915 * (type 1), Link (type 2) and Directory (type 3). To allow the addition of
916 * new types in the future, implementations should ignore desktop entries
917 * with an unknown type.
919 * + KDE specific types: ServiceType, Service and FSDevice
923 handle_type_key (kf_validator *kf,
924 const char *locale_key,
929 for (i = 0; i < G_N_ELEMENTS (registered_types); i++) {
930 if (!strcmp (value, registered_types[i].name))
934 if (i == G_N_ELEMENTS (registered_types)) {
935 /* force the type, since the key might be present multiple times... */
936 kf->type = INVALID_TYPE;
938 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
939 "is not a registered type value (\"Application\", "
940 "\"Link\" and \"Directory\")\n",
941 value, locale_key, kf->current_group);
945 if (registered_types[i].kde_reserved && kf->kde_reserved_warnings)
946 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
947 "is a reserved value for KDE\n",
948 value, locale_key, kf->current_group);
950 if (registered_types[i].deprecated && !kf->no_deprecated_warnings)
951 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
953 value, locale_key, kf->current_group);
955 kf->type = registered_types[i].type;
956 kf->type_string = registered_types[i].name;
961 /* + Entries that confirm with this version of the specification should use
964 * + Previous versions of the spec: 0.9.x where 3 <= x <= 8
968 handle_version_key (kf_validator *kf,
969 const char *locale_key,
972 if (!strcmp (value, "1.0"))
975 if (!strncmp (value, "0.9.", strlen ("0.9."))) {
978 c = value[strlen ("0.9.")];
979 if ('3' <= c && c <= '8' && value[strlen ("0.9.") + 1] == '\0')
983 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
984 "is not a known version\n",
985 value, locale_key, kf->current_group);
989 /* + Tooltip for the entry, for example "View sites on the Internet", should
990 * not be redundant with Name or GenericName.
994 handle_comment_key (kf_validator *kf,
995 const char *locale_key,
998 char *locale_compare_key;
999 kf_keyvalue *keyvalue;
1001 locale_compare_key = g_strdup_printf ("Name%s",
1002 locale_key + strlen ("Comment"));
1003 keyvalue = g_hash_table_lookup (kf->current_keys, locale_compare_key);
1004 g_free (locale_compare_key);
1006 if (keyvalue && g_ascii_strcasecmp (value, keyvalue->value) == 0) {
1007 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1008 "looks redundant with value \"%s\" of key \"%s\"\n",
1009 value, locale_key, kf->current_group,
1010 keyvalue->value, keyvalue->key);
1014 locale_compare_key = g_strdup_printf ("GenericName%s",
1015 locale_key + strlen ("Comment"));
1016 keyvalue = g_hash_table_lookup (kf->current_keys, locale_compare_key);
1017 g_free (locale_compare_key);
1019 if (keyvalue && g_ascii_strcasecmp (value, keyvalue->value) == 0) {
1020 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1021 "looks redundant with value \"%s\" of key \"%s\"\n",
1022 value, locale_key, kf->current_group,
1023 keyvalue->value, keyvalue->key);
1030 /* + If the name is an absolute path, the given file will be used.
1032 * + If the name is not an absolute path, the algorithm described in the Icon
1033 * Theme Specification will be used to locate the icon.
1035 * FIXME: add clarification to desktop entry spec that the name doesn't
1036 * contain an extension
1039 handle_icon_key (kf_validator *kf,
1040 const char *locale_key,
1043 if (g_path_is_absolute (value)) {
1044 if (g_str_has_suffix (value, "/")) {
1045 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" is an "
1046 "absolute path to a directory, instead of being an "
1047 "absolute path to an icon or an icon name\n",
1048 value, locale_key, kf->current_group);
1054 if (g_utf8_strchr (value, -1, G_DIR_SEPARATOR)) {
1055 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" looks like "
1056 "a relative path, instead of being an absolute path to "
1057 "an icon or an icon name\n",
1058 value, locale_key, kf->current_group);
1062 if (g_str_has_suffix (value, ".png") ||
1063 g_str_has_suffix (value, ".xpm") ||
1064 g_str_has_suffix (value, ".svg")) {
1065 print_future_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" is an "
1066 "icon name with an extension, but there should be "
1067 "no extension as described in the Icon Theme "
1068 "Specification if the value is not an absolute "
1070 value, locale_key, kf->current_group);
1077 /* + Only one of these keys, either OnlyShowIn or NotShowIn, may appear in a
1080 * + (for possible values see the Desktop Menu Specification)
1082 * FIXME: this is not perfect because it could fail if a new value with
1083 * a semicolon is registered.
1084 * + All values extending the format should start with "X-".
1086 * + FIXME: is this okay to have only ";"? (gnome-theme-installer.desktop does)
1089 handle_show_in_key (kf_validator *kf,
1090 const char *locale_key,
1095 GHashTable *hashtable;
1102 print_fatal (kf, "only one of \"OnlyShowIn\" and \"NotShowIn\" keys "
1103 "may appear in group \"%s\"\n",
1109 hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
1110 show = g_strsplit (value, ";", 0);
1112 for (i = 0; show[i]; i++) {
1113 /* since the value ends with a semicolon, we'll have an empty string
1115 if (*show[i] == '\0' && show[i + 1] == NULL)
1118 if (g_hash_table_lookup (hashtable, show[i])) {
1119 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1120 "contains \"%s\" more than once\n",
1121 value, locale_key, kf->current_group, show[i]);
1125 g_hash_table_insert (hashtable, show[i], show[i]);
1127 if (!strncmp (show[i], "X-", 2))
1130 for (j = 0; j < G_N_ELEMENTS (show_in_registered); j++) {
1131 if (!strcmp (show[i], show_in_registered[j]))
1135 if (j == G_N_ELEMENTS (show_in_registered)) {
1136 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1137 "contains an unregistered value \"%s\"; values "
1138 "extending the format should start with \"X-\"\n",
1139 value, locale_key, kf->current_group, show[i]);
1145 g_hash_table_destroy (hashtable);
1150 /* + A command line consists of an executable program optionally followed by
1151 * one or more arguments. The executable program can either be specified with
1152 * its full path or with the name of the executable only. If no full path is
1153 * provided the executable is looked up in the $PATH used by the desktop
1154 * environment. The name or path of the executable program may not contain
1155 * the equal sign ("=").
1157 * + Arguments are separated by a space.
1159 * + Arguments may be quoted in whole.
1161 * + If an argument contains a reserved character the argument must be quoted.
1163 * + The rules for quoting of arguments is also applicable to the executable
1164 * name or path of the executable program as provided.
1166 * + Quoting must be done by enclosing the argument between double quotes and
1167 * escaping the double quote character, backtick character ("`"), dollar sign
1168 * ("$") and backslash character ("\") by preceding it with an additional
1169 * backslash character. Implementations must undo quoting before expanding
1170 * field codes and before passing the argument to the executable program.
1171 * Reserved characters are space (" "), tab, newline, double quote, single
1172 * quote ("'"), backslash character ("\"), greater-than sign (">"), less-than
1173 * sign ("<"), tilde ("~"), vertical bar ("|"), ampersand ("&"), semicolon
1174 * (";"), dollar sign ("$"), asterisk ("*"), question mark ("?"), hash mark
1175 * ("#"), parenthesis ("(") and (")") and backtick character ("`").
1177 * + Note that the general escape rule for values of type string states that
1178 * the backslash character can be escaped as ("\\") as well and that this
1179 * escape rule is applied before the quoting rule. As such, to unambiguously
1180 * represent a literal backslash character in a quoted argument in a desktop
1181 * entry file requires the use of four successive backslash characters
1182 * ("\\\\"). Likewise, a literal dollar sign in a quoted argument in a
1183 * desktop entry file is unambiguously represented with ("\\$").
1185 * + Field codes consist of the percentage character ("%") followed by an alpha
1186 * character. Literal percentage characters must be escaped as %%.
1188 * + Command lines that contain a field code that is not listed in this
1189 * specification are invalid and must not be processed, in particular
1190 * implementations may not introduce support for field codes not listed in
1191 * this specification. Extensions, if any, should be introduced by means of a
1194 * + A command line may contain at most one %f, %u, %F or %U field code.
1196 * + The %F and %U field codes may only be used as an argument on their own.
1200 handle_exec_key (kf_validator *kf,
1201 const char *locale_key,
1218 #define PRINT_INVALID_IF_FLAG \
1220 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " \
1221 "contains an invalid field code \"%%%c\"\n", \
1222 value, locale_key, kf->current_group, *c); \
1231 /* quotes and escaped characters in quotes */
1233 PRINT_INVALID_IF_FLAG;
1241 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1242 "contains an escaped double quote (\\\\\") "
1243 "outside of a quote, but the double quote is "
1244 "a reserved character\n",
1245 value, locale_key, kf->current_group);
1254 PRINT_INVALID_IF_FLAG;
1257 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1258 "contains a non-escaped character '%c' in a "
1259 "quote, but it should be escaped with two "
1260 "backslashes (\"\\\\%c\")\n",
1261 value, locale_key, kf->current_group, *c, *c);
1266 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1267 "contains a reserved character '%c' outside of a "
1269 value, locale_key, kf->current_group, *c);
1274 PRINT_INVALID_IF_FLAG;
1276 if (*c == '\\' && in_quote)
1280 /* reserved characters */
1298 PRINT_INVALID_IF_FLAG;
1300 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1301 "contains a reserved character '%c' outside of a "
1303 value, locale_key, kf->current_group, *c);
1316 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1317 "may contain at most one \"%f\", \"%u\", "
1318 "\"%F\" or \"%U\" field code\n",
1319 value, locale_key, kf->current_group);
1331 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1332 "may contain at most one \"%f\", \"%u\", "
1333 "\"%F\" or \"%U\" field code\n",
1334 value, locale_key, kf->current_group);
1355 if (!kf->no_deprecated_warnings)
1356 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1357 "contains a deprecated field code \"%%%c\"\n",
1358 value, locale_key, kf->current_group, *c);
1364 PRINT_INVALID_IF_FLAG;
1372 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" contains a "
1373 "quote which is not closed\n",
1374 value, locale_key, kf->current_group);
1379 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" contains a "
1380 "non-complete field code\n",
1381 value, locale_key, kf->current_group);
1388 /* See checks for handle_exec_key().
1391 handle_desktop_exec_key (kf_validator *kf,
1392 const char *locale_key,
1395 handle_key_for_application (kf, locale_key, value);
1397 return handle_exec_key (kf, locale_key, value);
1400 /* + If entry is of type Application, the working directory to run the program
1401 * in. (probably implies an absolute path)
1403 * + FIXME: is it okay to have an empty string here? (wireshark.desktop does)
1406 handle_path_key (kf_validator *kf,
1407 const char *locale_key,
1410 handle_key_for_application (kf, locale_key, value);
1412 if (!g_path_is_absolute (value))
1413 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1414 "does not look like an absolute path\n",
1415 value, locale_key, kf->current_group);
1420 /* + The MIME type(s) supported by this application. Check they are valid
1425 handle_mime_key (kf_validator *kf,
1426 const char *locale_key,
1431 GHashTable *hashtable;
1434 MimeUtilsValidity valid;
1436 handle_key_for_application (kf, locale_key, value);
1440 hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
1441 types = g_strsplit (value, ";", 0);
1443 for (i = 0; types[i]; i++) {
1444 /* since the value ends with a semicolon, we'll have an empty string
1446 if (*types[i] == '\0' && types[i + 1] == NULL)
1449 if (g_hash_table_lookup (hashtable, types[i])) {
1450 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1451 "contains \"%s\" more than once\n",
1452 value, locale_key, kf->current_group, types[i]);
1456 g_hash_table_insert (hashtable, types[i], types[i]);
1458 valid = mu_mime_type_is_valid (types[i], &valid_error);
1462 case MU_DISCOURAGED:
1463 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1464 "contains value \"%s\" which is a MIME type that "
1465 "should probably not be used: %s\n",
1466 value, locale_key, kf->current_group,
1467 types[i], valid_error);
1469 g_free (valid_error);
1472 print_future_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1473 "contains value \"%s\" which is an invalid "
1475 value, locale_key, kf->current_group,
1476 types[i], valid_error);
1479 g_free (valid_error);
1482 g_assert_not_reached ();
1487 g_hash_table_destroy (hashtable);
1492 /* + FIXME: are there restrictions on how a category should be named?
1493 * + Categories in which the entry should be shown in a menu (for possible
1494 * values see the Desktop Menu Specification).
1496 * + The table below describes Reserved Categories. Reserved Categories have a
1497 * specific desktop specific meaning that has not been standardized (yet).
1498 * Desktop entry files that use a reserved category MUST also include an
1499 * appropriate OnlyShowIn= entry to restrict themselves to those environments
1500 * that properly support the reserved category as used.
1502 * + Accept "Application" as a deprecated category.
1504 * FIXME: it's not really deprecated, so the error message is wrong
1505 * + All categories extending the format should start with "X-".
1507 * + Using multiple main categories may lead to appearing more than once in
1510 * + One main category should be included, otherwise application will appear in
1511 * "catch-all" section of application menu.
1513 * FIXME: decide if it's okay to have an empty list of categories.
1514 * + Some categories, if included, require that another category is included.
1515 * Eg: if Audio is there, AudioVideo must be there.
1517 * + Some categories, if included, suggest that another category is included.
1518 * Eg: Debugger suggests Development.
1519 * This is the case for most additional categories.
1523 handle_categories_key (kf_validator *kf,
1524 const char *locale_key,
1529 GHashTable *hashtable;
1532 int main_categories_nb;
1534 handle_key_for_application (kf, locale_key, value);
1538 /* accept empty value as valid: this is like having no category at all */
1539 if (value[0] == '\0')
1542 hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
1543 categories = g_strsplit (value, ";", 0);
1545 /* this is a two-pass check: we first put the categories in a hash table so
1546 * that they are easy-to-find, and we then do many checks */
1549 for (i = 0; categories[i]; i++) {
1550 /* since the value ends with a semicolon, we'll have an empty string
1552 if (*categories[i] == '\0' && categories[i + 1] == NULL)
1555 if (g_hash_table_lookup (hashtable, categories[i])) {
1556 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1557 "contains \"%s\" more than once\n",
1558 value, locale_key, kf->current_group, categories[i]);
1562 g_hash_table_insert (hashtable, categories[i], categories[i]);
1566 main_categories_nb = 0;
1568 for (i = 0; categories[i]; i++) {
1571 /* since the value ends with a semicolon, we'll have an empty string
1573 if (*categories[i] == '\0' && categories[i + 1] == NULL)
1576 if (!strncmp (categories[i], "X-", 2))
1579 for (j = 0; j < G_N_ELEMENTS (registered_categories); j++) {
1580 if (!strcmp (categories[i], registered_categories[j].name))
1584 if (j == G_N_ELEMENTS (registered_categories)) {
1585 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1586 "contains an unregistered value \"%s\"; values "
1587 "extending the format should start with \"X-\"\n",
1588 value, locale_key, kf->current_group, categories[i]);
1593 if (registered_categories[j].main) {
1594 /* only count it as a main category if none of the required categories
1595 * for this one is also a main category (and is present) */
1596 gboolean required_main_category_present = FALSE;
1598 for (k = 0; registered_categories[j].requires[k] != NULL; k++) {
1599 char **required_categories;
1602 required_categories = g_strsplit (registered_categories[j].requires[k],
1605 for (l = 0; required_categories[l]; l++) {
1608 if (!g_hash_table_lookup (hashtable, required_categories[l]))
1611 for (m = 0; m < G_N_ELEMENTS (registered_categories); m++) {
1612 if (strcmp (required_categories[l],
1613 registered_categories[m].name) != 0)
1616 if (registered_categories[m].main)
1617 required_main_category_present = TRUE;
1622 if (required_main_category_present)
1626 if (required_main_category_present) {
1627 g_strfreev (required_categories);
1631 g_strfreev (required_categories);
1634 if (!required_main_category_present)
1635 main_categories_nb++;
1638 if (registered_categories[j].main && main_categories_nb > 1)
1639 print_hint (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1640 "contains more than one main category; application "
1641 "might appear more than once in the application menu\n",
1642 value, locale_key, kf->current_group);
1645 if (registered_categories[j].deprecated) {
1646 if (!kf->no_deprecated_warnings)
1647 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1648 "contains a deprecated value \"%s\"\n",
1649 value, locale_key, kf->current_group,
1653 if (registered_categories[j].require_only_show_in) {
1654 if (!g_hash_table_lookup (kf->current_keys, "OnlyShowIn")) {
1655 print_fatal (kf, "value item \"%s\" in key \"%s\" in group \"%s\" "
1656 "is a reserved category, so a \"OnlyShowIn\" key "
1657 "must be included\n",
1658 categories[i], locale_key, kf->current_group);
1663 /* required categories */
1665 for (k = 0; registered_categories[j].requires[k] != NULL; k++) {
1666 char **required_categories;
1669 required_categories = g_strsplit (registered_categories[j].requires[k],
1672 for (l = 0; required_categories[l]; l++) {
1673 if (!g_hash_table_lookup (hashtable, required_categories[l]))
1677 /* we've reached the end of a list of required categories, so
1678 * the condition is satisfied */
1679 if (required_categories[l] == NULL) {
1680 g_strfreev (required_categories);
1684 g_strfreev (required_categories);
1687 /* we've reached the end of a non-empty set of required categories; this
1688 * means none of the possible required category (or list of required
1689 * categories) was found */
1690 if (k != 0 && registered_categories[j].requires[k] == NULL) {
1691 GString *output_required;
1693 output_required = g_string_new (registered_categories[j].requires[0]);
1694 for (k = 1; registered_categories[j].requires[k] != NULL; k++)
1695 g_string_append_printf (output_required, ", or %s",
1696 registered_categories[j].requires[k]);
1698 print_future_fatal (kf, "value item \"%s\" in key \"%s\" in group \"%s\" "
1699 "requires another category to be present among "
1700 "the following categories: %s\n",
1701 categories[i], locale_key, kf->current_group,
1702 output_required->str);
1704 g_string_free (output_required, TRUE);
1708 /* suggested categories */
1710 for (k = 0; registered_categories[j].suggests[k] != NULL; k++) {
1711 char **suggested_categories;
1714 suggested_categories = g_strsplit (registered_categories[j].suggests[k],
1717 for (l = 0; suggested_categories[l]; l++) {
1718 if (!g_hash_table_lookup (hashtable, suggested_categories[l]))
1722 /* we've reached the end of a list of suggested categories, so
1723 * the condition is satisfied */
1724 if (suggested_categories[l] == NULL) {
1725 g_strfreev (suggested_categories);
1729 g_strfreev (suggested_categories);
1732 /* we've reached the end of a non-empty set of suggested categories; this
1733 * means none of the possible suggested category (or list of suggested
1734 * categories) was found */
1735 if (k != 0 && registered_categories[j].suggests[k] == NULL) {
1736 GString *output_suggested;
1738 output_suggested = g_string_new (registered_categories[j].suggests[0]);
1739 for (k = 1; registered_categories[j].suggests[k] != NULL; k++)
1740 g_string_append_printf (output_suggested, ", or %s",
1741 registered_categories[j].suggests[k]);
1743 print_hint (kf, "value item \"%s\" in key \"%s\" in group \"%s\" "
1744 "can be extended with another category among the "
1745 "following categories: %s\n",
1746 categories[i], locale_key, kf->current_group,
1747 output_suggested->str);
1749 g_string_free (output_suggested, TRUE);
1754 g_strfreev (categories);
1755 g_hash_table_destroy (hashtable);
1757 g_assert (main_categories_nb >= 0);
1759 if (main_categories_nb == 0)
1760 print_hint (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1761 "does not contain a registered main category; application "
1762 "might only show up in a \"catch-all\" section of the "
1763 "application menu\n",
1764 value, locale_key, kf->current_group);
1769 /* + Identifiers for application actions. Check they are using a valid format.
1772 * Note that we will check later on (in * validate_actions()) that there is a
1773 * "Desktop Action foobar" group for each "foobar" identifier.
1776 handle_actions_key (kf_validator *kf,
1777 const char *locale_key,
1785 handle_key_for_application (kf, locale_key, value);
1788 actions = g_strsplit (value, ";", 0);
1790 for (i = 0; actions[i]; i++) {
1791 /* since the value ends with a semicolon, we'll have an empty string
1793 if (*actions[i] == '\0') {
1794 if (actions[i + 1] != NULL) {
1795 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1796 "contains an empty action\n",
1797 value, locale_key, kf->current_group);
1805 if (g_hash_table_lookup (kf->action_values, actions[i])) {
1806 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1807 "contains action \"%s\" more than once\n",
1808 value, locale_key, kf->current_group, actions[i]);
1812 if (!key_is_valid (actions[i], strlen (actions[i]))) {
1813 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1814 "contains invalid action identifier \"%s\", only "
1815 "alphanumeric characters and '-' are allowed\n",
1816 value, locale_key, kf->current_group, actions[i]);
1821 action = g_strdup (actions[i]);
1822 g_hash_table_insert (kf->action_values, action, action);
1825 g_strfreev (actions);
1830 /* + The device to mount. (probably implies an absolute path)
1834 handle_dev_key (kf_validator *kf,
1835 const char *locale_key,
1838 handle_key_for_fsdevice (kf, locale_key, value);
1840 if (!g_path_is_absolute (value))
1841 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1842 "does not look like an absolute path\n",
1843 value, locale_key, kf->current_group);
1848 /* + The mount point of the device in question. (probably implies an absolute
1853 handle_mountpoint_key (kf_validator *kf,
1854 const char *locale_key,
1857 handle_key_for_fsdevice (kf, locale_key, value);
1859 if (!g_path_is_absolute (value))
1860 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1861 "does not look like an absolute path\n",
1862 value, locale_key, kf->current_group);
1867 /* + Possible values are UTF-8 and Legacy-Mixed.
1871 handle_encoding_key (kf_validator *kf,
1872 const char *locale_key,
1875 if (!strcmp (value, "UTF-8") || !strcmp (value, "Legacy-Mixed"))
1878 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1879 "is not a registered encoding value (\"UTF-8\", and "
1880 "\"Legacy-Mixed\")\n",
1881 value, locale_key, kf->current_group);
1886 /* + See http://lists.freedesktop.org/archives/xdg/2007-January/007436.html
1887 * + Value is one of:
1889 * - unless-exists FILE
1890 * - DESKTOP-ENVIRONMENT-NAME [DESKTOP-SPECIFIC-TEST]
1891 * - other known conditions (GNOME3, GSettings, etc.)
1893 * + FILE must be a path to a filename, relative to $XDG_CONFIG_HOME.
1895 * + DESKTOP-ENVIRONMENT-NAME should be a registered value (in Desktop Menu
1896 * Specification) or start with "X-".
1898 * + [DESKTOP-SPECIFIC-TEST] is optional.
1902 handle_autostart_condition_key (kf_validator *kf,
1903 const char *locale_key,
1910 handle_key_for_application (kf, locale_key, value);
1914 condition = g_strdup (value);
1915 argument = g_utf8_strchr (condition, -1, ' ');
1918 /* make condition a 0-ended string */
1921 /* skip the space(s) */
1923 while (*argument == ' ') {
1928 if (!strcmp (condition, "if-exists") || !strcmp (condition, "unless-exists")) {
1929 if (!argument || argument[0] == '\0') {
1930 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1931 "does not contain a path to a file to test the "
1933 value, locale_key, kf->current_group);
1935 } else if (argument[0] == G_DIR_SEPARATOR) {
1936 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1937 "contains a path \"%s\" that is absolute, while it "
1938 "should be relative (to $XDG_CONFIG_HOME)\n",
1939 value, locale_key, kf->current_group, argument);
1941 } else if (argument[0] == '.' &&
1942 ((strlen (argument) == 2 &&
1943 argument[1] == '.') ||
1944 (strlen (argument) >= 3 &&
1945 argument[1] == '.' &&
1946 argument[2] == G_DIR_SEPARATOR))) {
1947 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1948 "contains a path \"%s\" that depends on the value "
1949 "of $XDG_CONFIG_HOME (\"..\" should be avoided)\n",
1950 value, locale_key, kf->current_group, argument);
1953 } else if (strncmp (condition, "X-", 2) == 0) {
1954 if (argument && argument[0] == '\0')
1955 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1956 "has trailing space(s)\n",
1957 value, locale_key, kf->current_group);
1962 /* Look if it's a registered AutostartCondition */
1964 for (i = 0; i < G_N_ELEMENTS (registered_autostart_condition); i++) {
1966 if (strcmp (condition, registered_autostart_condition[i].name) != 0)
1969 /* check if first argument is one of the expected ones */
1970 for (j = 0; registered_autostart_condition[i].first_arg[j] != NULL; j++) {
1971 const char *first = registered_autostart_condition[i].first_arg[j];
1972 char *after_first = argument;
1974 if (argument && !strncmp (argument, first, strlen (first))) {
1975 after_first += strlen (first);
1976 if (after_first[0] == '\0' || after_first[0] == ' ') {
1977 /* find next argument */
1978 argument = after_first;
1979 while (*argument == ' ')
1987 /* we've reached the end of a non-empty set of first arguments; this
1988 * means none of the possible first arguments was found */
1989 if (j != 0 && registered_autostart_condition[i].first_arg[j] == NULL) {
1992 output = g_string_new (registered_autostart_condition[i].first_arg[0]);
1993 for (j = 1; registered_autostart_condition[i].first_arg[j] != NULL; j++)
1994 g_string_append_printf (output, ", or %s",
1995 registered_autostart_condition[i].first_arg[j]);
1997 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1998 "does not contain a valid first argument for "
1999 "condition \"%s\"; valid first arguments are: %s\n",
2000 value, locale_key, kf->current_group,
2001 condition, output->str);
2004 g_string_free (output, TRUE);
2008 switch (registered_autostart_condition[i].additional_args) {
2010 if (argument && argument[0] != '\0') {
2011 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2012 "has too many arguments for condition \"%s\"\n",
2013 value, locale_key, kf->current_group, condition);
2019 /* we handle the "one argument" case specially, as spaces might be
2020 * normal there, and therefore we don't want to split the string
2021 * based on spaces */
2022 if (!argument || argument[0] == '\0') {
2023 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2024 "is missing a last argument for condition "
2026 value, locale_key, kf->current_group, condition);
2033 int argc_diff = -registered_autostart_condition[i].additional_args;
2035 while (argument && argument[0] != '\0') {
2037 argument = g_utf8_strchr (argument, -1, ' ');
2038 while (argument && *argument == ' ')
2042 if (argc_diff > 0) {
2043 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2044 "has %d too many arguments for condition "
2046 value, locale_key, kf->current_group,
2047 argc_diff, condition);
2049 } else if (argc_diff < 0) {
2050 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2051 "has %d too few arguments for condition "
2053 value, locale_key, kf->current_group,
2054 -argc_diff, condition);
2067 /* Now, if we didn't find condition in list of registered
2068 * AutostartCondition... */
2069 if (i == G_N_ELEMENTS (registered_autostart_condition)) {
2070 /* Accept conditions with same name as OnlyShowIn values */
2072 for (i = 0; i < G_N_ELEMENTS (show_in_registered); i++) {
2073 if (!strcmp (condition, show_in_registered[i]))
2077 if (i == G_N_ELEMENTS (show_in_registered)) {
2078 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2079 "contains an unregistered value \"%s\" for the "
2080 "condition; values extending the format should "
2081 "start with \"X-\"\n",
2082 value, locale_key, kf->current_group, condition);
2086 if (argument && argument[0] == '\0')
2087 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2088 "has trailing space(s)\n",
2089 value, locale_key, kf->current_group);
2099 handle_key_for_application (kf_validator *kf,
2100 const char *locale_key,
2103 kf->application_keys = g_list_append (kf->application_keys,
2104 g_strdup (locale_key));
2109 handle_key_for_link (kf_validator *kf,
2110 const char *locale_key,
2113 kf->link_keys = g_list_append (kf->link_keys,
2114 g_strdup (locale_key));
2119 handle_key_for_fsdevice (kf_validator *kf,
2120 const char *locale_key,
2123 kf->fsdevice_keys = g_list_append (kf->fsdevice_keys,
2124 g_strdup (locale_key));
2129 handle_key_for_mimetype (kf_validator *kf,
2130 const char *locale_key,
2133 kf->mimetype_keys = g_list_append (kf->mimetype_keys,
2134 g_strdup (locale_key));
2138 /* + Key names must contain only the characters A-Za-z0-9-.
2139 * Checked (through key_is_valid()).
2140 * + LOCALE must be of the form lang_COUNTRY.ENCODING@MODIFIER, where _COUNTRY,
2141 * .ENCODING, and @MODIFIER may be omitted.
2145 key_extract_locale (const char *key,
2149 const char *start_locale;
2159 start_locale = g_strrstr (key, "[");
2162 len = start_locale - key;
2166 if (!key_is_valid(key, len))
2169 if (!start_locale) {
2171 *real_key = g_strdup (key);
2178 len = strlen (start_locale);
2179 if (len <= 2 || start_locale[len - 1] != ']')
2182 /* ignore first [ and last ] */
2183 for (i = 1; i < len - 2; i++) {
2184 c = start_locale[i];
2185 if (!g_ascii_isalnum (c) && c != '-' && c != '_' && c != '.' && c != '@')
2190 *real_key = g_strndup (key, strlen (key) - len);
2192 *locale = g_strndup (start_locale + 1, len - 2);
2197 /* + All keys extending the format should start with "X-".
2201 validate_known_key (kf_validator *kf,
2202 const char *locale_key,
2206 DesktopKeyDefinition *keys,
2207 unsigned int n_keys)
2212 if (!strncmp (key, "X-", 2))
2215 for (i = 0; i < n_keys; i++) {
2216 if (strcmp (key, keys[i].name))
2219 if (keys[i].type != DESKTOP_LOCALESTRING_TYPE &&
2220 keys[i].type != DESKTOP_LOCALESTRING_LIST_TYPE &&
2222 print_fatal (kf, "file contains key \"%s\" in group \"%s\", "
2223 "but \"%s\" is not defined as a locale string\n",
2224 locale_key, kf->current_group, key);
2228 for (j = 0; j < G_N_ELEMENTS (validate_for_type); j++) {
2229 if (validate_for_type[j].type == keys[i].type)
2233 g_assert (j != G_N_ELEMENTS (validate_for_type));
2235 if (!kf->no_deprecated_warnings && keys[i].deprecated)
2236 print_warning (kf, "key \"%s\" in group \"%s\" is deprecated\n",
2237 locale_key, kf->current_group);
2239 if (keys[i].kde_reserved && kf->kde_reserved_warnings)
2240 print_warning (kf, "key \"%s\" in group \"%s\" is a reserved key for "
2242 locale_key, kf->current_group);
2244 if (!validate_for_type[j].validate (kf, key, locale, value))
2247 if (keys[i].handle_and_validate != NULL) {
2248 if (!keys[i].handle_and_validate (kf, locale_key, value))
2256 print_fatal (kf, "file contains key \"%s\" in group \"%s\", but "
2257 "keys extending the format should start with "
2258 "\"X-\"\n", key, kf->current_group);
2266 validate_desktop_key (kf_validator *kf,
2267 const char *locale_key,
2272 return validate_known_key (kf, locale_key, key, locale, value,
2273 registered_desktop_keys,
2274 G_N_ELEMENTS (registered_desktop_keys));
2278 validate_action_key (kf_validator *kf,
2279 const char *locale_key,
2284 return validate_known_key (kf, locale_key, key, locale, value,
2285 registered_action_keys,
2286 G_N_ELEMENTS (registered_action_keys));
2289 /* + Multiple keys in the same group may not have the same name.
2293 validate_keys_for_current_group (kf_validator *kf)
2295 gboolean desktop_group;
2296 gboolean action_group;
2298 GHashTable *duplicated_keys_hash;
2307 desktop_group = (!strcmp (kf->current_group, GROUP_DESKTOP_ENTRY) ||
2308 !strcmp (kf->current_group, GROUP_KDE_DESKTOP_ENTRY));
2309 action_group = (!strncmp (kf->current_group, GROUP_DESKTOP_ACTION,
2310 strlen (GROUP_DESKTOP_ACTION)));
2312 keys = g_slist_copy (g_hash_table_lookup (kf->groups, kf->current_group));
2313 /* keys were prepended, so reverse the list (that's why we use a
2315 keys = g_slist_reverse (keys);
2317 kf->current_keys = g_hash_table_new_full (g_str_hash, g_str_equal,
2319 duplicated_keys_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
2322 /* we need two passes: some checks are looking if another key exists in the
2324 for (sl = keys; sl != NULL; sl = sl->next) {
2325 kf_keyvalue *keyvalue;
2327 keyvalue = (kf_keyvalue *) sl->data;
2328 g_hash_table_insert (kf->current_keys, keyvalue->key, keyvalue);
2330 /* we could display the error about duplicate keys here, but it's better
2331 * to display it with the first occurence of this key */
2332 hashvalue = g_hash_table_lookup (duplicated_keys_hash, keyvalue->key);
2334 g_hash_table_insert (duplicated_keys_hash, keyvalue->key,
2335 GINT_TO_POINTER (1));
2337 g_hash_table_replace (duplicated_keys_hash, keyvalue->key,
2338 GINT_TO_POINTER (GPOINTER_TO_INT (hashvalue) + 1));
2342 for (sl = keys; sl != NULL; sl = sl->next) {
2343 kf_keyvalue *keyvalue;
2344 gboolean skip_desktop_check;
2346 keyvalue = (kf_keyvalue *) sl->data;
2348 skip_desktop_check = FALSE;
2350 if (!key_extract_locale (keyvalue->key, &key, &locale)) {
2351 print_fatal (kf, "file contains key \"%s\" in group \"%s\", but "
2352 "key names must contain only the characters "
2353 "A-Za-z0-9- (they may have a \"[LOCALE]\" postfix)\n",
2354 keyvalue->key, kf->current_group);
2356 skip_desktop_check = TRUE;
2358 key = g_strdup (keyvalue->key);
2361 g_assert (key != NULL);
2363 hashvalue = g_hash_table_lookup (duplicated_keys_hash, keyvalue->key);
2364 if (GPOINTER_TO_INT (hashvalue) > 1) {
2365 g_hash_table_remove (duplicated_keys_hash, keyvalue->key);
2366 print_fatal (kf, "file contains multiple keys named \"%s\" in "
2367 "group \"%s\"\n", keyvalue->key, kf->current_group);
2371 if (desktop_group && !skip_desktop_check) {
2372 if (!validate_desktop_key (kf, keyvalue->key,
2373 key, locale, keyvalue->value))
2375 } else if (action_group && !skip_desktop_check) {
2376 if (!validate_action_key (kf, keyvalue->key,
2377 key, locale, keyvalue->value))
2387 g_slist_free (keys);
2388 g_hash_table_destroy (duplicated_keys_hash);
2389 g_hash_table_destroy (kf->current_keys);
2390 kf->current_keys = NULL;
2391 /* Clear ShowIn flag, so that different groups can each have a OnlyShowIn /
2393 kf->show_in = FALSE;
2398 /* + Using [KDE Desktop Entry] instead of [Desktop Entry] as header is
2401 * + Group names may contain all ASCII characters except for [ and ] and
2402 * control characters.
2404 * + All groups extending the format should start with "X-".
2406 * + Accept "Desktop Action foobar" group, where foobar is a valid key
2410 * Note that for "Desktop Action foobar" group, we will check later on (in
2411 * validate_actions()) that the Actions key contains "foobar".
2414 validate_group_name (kf_validator *kf,
2420 for (i = 0; group[i] != '\0'; i++) {
2422 if (g_ascii_iscntrl (c) || c == '[' || c == ']') {
2423 print_fatal (kf, "file contains group \"%s\", but group names "
2424 "may contain all ASCII characters except for [ "
2425 "and ] and control characters\n", group);
2430 if (!strncmp (group, "X-", 2))
2433 if (!strcmp (group, GROUP_DESKTOP_ENTRY)) {
2434 if (kf->main_group && !strcmp (kf->main_group, GROUP_KDE_DESKTOP_ENTRY))
2435 print_warning (kf, "file contains groups \"%s\" and \"%s\", which play "
2437 GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY);
2439 kf->main_group = GROUP_DESKTOP_ENTRY;
2444 if (!strcmp (group, GROUP_KDE_DESKTOP_ENTRY)) {
2445 if (kf->kde_reserved_warnings || !kf->no_deprecated_warnings)
2446 print_warning (kf, "file contains group \"%s\", which is deprecated "
2447 "in favor of \"%s\"\n", group, GROUP_DESKTOP_ENTRY);
2449 if (kf->main_group && !strcmp (kf->main_group, GROUP_DESKTOP_ENTRY))
2450 print_warning (kf, "file contains groups \"%s\" and \"%s\", which play "
2452 GROUP_DESKTOP_ENTRY, GROUP_KDE_DESKTOP_ENTRY);
2454 kf->main_group = GROUP_KDE_DESKTOP_ENTRY;
2459 if (!strncmp (group, GROUP_DESKTOP_ACTION, strlen (GROUP_DESKTOP_ACTION))) {
2460 if (group[strlen (GROUP_DESKTOP_ACTION) - 1] == '\0') {
2461 print_fatal (kf, "file contains group \"%s\", which is an action "
2462 "group with no action name\n", group);
2467 action = g_strdup (group + strlen (GROUP_DESKTOP_ACTION));
2469 if (!key_is_valid (action, strlen (action))) {
2470 print_fatal (kf, "file contains group \"%s\", which has an invalid "
2471 "action identifier, only alphanumeric characters and "
2472 "'-' are allowed\n", group);
2477 g_hash_table_insert (kf->action_groups, action, action);
2483 print_fatal (kf, "file contains group \"%s\", but groups extending "
2484 "the format should start with \"X-\"\n", group);
2489 validate_required_keys (kf_validator *kf,
2490 const char *group_name,
2491 DesktopKeyDefinition *key_definitions,
2492 unsigned int n_keys)
2498 GHashTable *hashtable;
2502 hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
2503 keys = g_hash_table_lookup (kf->groups, group_name);
2505 for (sl = keys; sl != NULL; sl = sl->next) {
2506 kf_keyvalue *keyvalue;
2508 keyvalue = (kf_keyvalue *) sl->data;
2509 g_hash_table_insert (hashtable, keyvalue->key, keyvalue->key);
2512 for (i = 0; i < n_keys; i++) {
2513 if (key_definitions[i].required) {
2514 if (!g_hash_table_lookup (hashtable,
2515 key_definitions[i].name)) {
2516 print_fatal (kf, "required key \"%s\" in group \"%s\" is not "
2518 key_definitions[i].name, group_name);
2524 g_hash_table_destroy (hashtable);
2530 validate_required_desktop_keys (kf_validator *kf)
2532 return validate_required_keys (kf, kf->main_group,
2533 registered_desktop_keys,
2534 G_N_ELEMENTS (registered_desktop_keys));
2537 #define PRINT_ERROR_FOREACH_KEY(lower, real) \
2539 print_error_foreach_##lower##_key (const char *name, \
2542 print_fatal (kf, "key \"%s\" is present in group \"%s\", but the type is " \
2543 "\"%s\" while this key is only valid for type \"%s\"\n", \
2544 name, kf->main_group, kf->type_string, real); \
2547 PRINT_ERROR_FOREACH_KEY (application, "Application")
2548 PRINT_ERROR_FOREACH_KEY (link, "Link")
2549 PRINT_ERROR_FOREACH_KEY (fsdevice, "FSDevice")
2550 PRINT_ERROR_FOREACH_KEY (mimetype, "MimeType")
2553 validate_type_keys (kf_validator *kf)
2562 case APPLICATION_TYPE:
2563 g_list_foreach (kf->link_keys,
2564 (GFunc) print_error_foreach_link_key, kf);
2565 g_list_foreach (kf->fsdevice_keys,
2566 (GFunc) print_error_foreach_fsdevice_key, kf);
2567 g_list_foreach (kf->mimetype_keys,
2568 (GFunc) print_error_foreach_mimetype_key, kf);
2569 retval = (g_list_length (kf->link_keys) +
2570 g_list_length (kf->fsdevice_keys) +
2571 g_list_length (kf->mimetype_keys) == 0);
2574 g_list_foreach (kf->application_keys,
2575 (GFunc) print_error_foreach_application_key, kf);
2576 g_list_foreach (kf->fsdevice_keys,
2577 (GFunc) print_error_foreach_fsdevice_key, kf);
2578 g_list_foreach (kf->mimetype_keys,
2579 (GFunc) print_error_foreach_mimetype_key, kf);
2580 retval = (g_list_length (kf->application_keys) +
2581 g_list_length (kf->fsdevice_keys) +
2582 g_list_length (kf->mimetype_keys) == 0);
2584 case DIRECTORY_TYPE:
2586 case SERVICE_TYPE_TYPE:
2587 g_list_foreach (kf->application_keys,
2588 (GFunc) print_error_foreach_application_key, kf);
2589 g_list_foreach (kf->link_keys,
2590 (GFunc) print_error_foreach_link_key, kf);
2591 g_list_foreach (kf->fsdevice_keys,
2592 (GFunc) print_error_foreach_fsdevice_key, kf);
2593 g_list_foreach (kf->mimetype_keys,
2594 (GFunc) print_error_foreach_mimetype_key, kf);
2595 retval = (g_list_length (kf->application_keys) +
2596 g_list_length (kf->link_keys) +
2597 g_list_length (kf->fsdevice_keys) +
2598 g_list_length (kf->mimetype_keys) == 0);
2601 g_list_foreach (kf->application_keys,
2602 (GFunc) print_error_foreach_application_key, kf);
2603 g_list_foreach (kf->link_keys,
2604 (GFunc) print_error_foreach_link_key, kf);
2605 g_list_foreach (kf->mimetype_keys,
2606 (GFunc) print_error_foreach_mimetype_key, kf);
2607 retval = (g_list_length (kf->application_keys) +
2608 g_list_length (kf->link_keys) +
2609 g_list_length (kf->mimetype_keys) == 0);
2612 g_list_foreach (kf->application_keys,
2613 (GFunc) print_error_foreach_application_key, kf);
2614 g_list_foreach (kf->link_keys,
2615 (GFunc) print_error_foreach_link_key, kf);
2616 g_list_foreach (kf->fsdevice_keys,
2617 (GFunc) print_error_foreach_fsdevice_key, kf);
2618 retval = (g_list_length (kf->application_keys) +
2619 g_list_length (kf->link_keys) +
2620 g_list_length (kf->fsdevice_keys) == 0);
2623 g_assert_not_reached ();
2630 lookup_group_foreach_action (char *key,
2634 if (g_hash_table_lookup (kf->action_groups, key)) {
2637 group_name = g_strconcat (GROUP_DESKTOP_ACTION, key, NULL);
2638 validate_required_keys (kf, group_name,
2639 registered_action_keys,
2640 G_N_ELEMENTS (registered_action_keys));
2641 g_free (group_name);
2643 g_hash_table_remove (kf->action_groups, key);
2651 print_error_foreach_action (char *key,
2655 print_fatal (kf, "action \"%s\" is defined, but there is no matching "
2656 "\"%s%s\" group\n", key, GROUP_DESKTOP_ACTION, key);
2660 print_error_foreach_group (char *key,
2664 print_fatal (kf, "action group \"%s%s\" exists, but there is no matching "
2665 "action \"%s\"\n", GROUP_DESKTOP_ACTION, key, key);
2669 validate_actions (kf_validator *kf)
2671 g_hash_table_foreach_remove (kf->action_values,
2672 (GHRFunc) lookup_group_foreach_action, kf);
2674 g_hash_table_foreach (kf->action_values,
2675 (GHFunc) print_error_foreach_action, kf);
2677 g_hash_table_foreach (kf->action_groups,
2678 (GHFunc) print_error_foreach_group, kf);
2680 return (g_hash_table_size (kf->action_values) +
2681 g_hash_table_size (kf->action_groups) == 0);
2684 /* + These desktop entry files should have the extension .desktop.
2686 * + Desktop entries which describe how a directory is to be
2687 * formatted/displayed should be simply called .directory.
2689 * + Using .kdelnk instead of .desktop as the file extension is deprecated.
2691 * FIXME: we're not doing what the spec says wrt Directory.
2694 validate_filename (kf_validator *kf)
2696 if (kf->type == DIRECTORY_TYPE) {
2697 if (g_str_has_suffix (kf->filename, ".directory"))
2700 print_fatal (kf, "file is of type \"Directory\", but filename does not "
2701 "have a .directory extension\n");
2706 if (g_str_has_suffix (kf->filename, ".desktop"))
2709 if (g_str_has_suffix (kf->filename, ".kdelnk")) {
2710 if (kf->kde_reserved_warnings || !kf->no_deprecated_warnings)
2711 print_warning (kf, "filename has a .kdelnk extension, which is "
2712 "deprecated in favor of .desktop\n");
2716 print_fatal (kf, "filename does not have a .desktop extension\n");
2720 /* + Lines beginning with a # and blank lines are considered comments.
2724 validate_line_is_comment (kf_validator *kf,
2727 return (*line == '#' || *line == '\0');
2730 /* + A group header with name groupname is a line in the format: [groupname]
2732 * + Group names may contain all ASCII characters except for [ and ] and
2733 * control characters.
2734 * This is done in validate_group_name().
2737 validate_line_looks_like_group (kf_validator *kf,
2744 chomped = g_strdup (line);
2745 g_strchomp (chomped);
2747 result = (*chomped == '[' && chomped[strlen (chomped) - 1] == ']');
2749 if (result && strcmp (chomped, line))
2750 print_fatal (kf, "line \"%s\" ends with a space, but looks like a group. "
2751 "The validation will continue, with the trailing spaces "
2752 "ignored.\n", line);
2754 if (group && result)
2755 *group = g_strndup (chomped + 1, strlen (chomped) - 2);
2762 /* + Space before and after the equals sign should be ignored; the = sign is
2763 * the actual delimiter.
2767 validate_line_looks_like_entry (kf_validator *kf,
2774 p = g_utf8_strchr (line, -1, '=');
2779 /* key must be non-empty */
2784 *key = g_strndup (line, p - line);
2788 *value = g_strdup (p + 1);
2795 /* + Only comments are accepted before the first group.
2797 * + The first group should be "Desktop Entry".
2799 * + Multiple groups may not have the same name.
2803 validate_parse_line (kf_validator *kf)
2811 line = kf->parse_buffer->str;
2812 len = kf->parse_buffer->len;
2814 if (!kf->utf8_warning && !g_utf8_validate (line, len, NULL)) {
2815 print_warning (kf, "file contains lines that are not UTF-8 encoded. There "
2816 "is no guarantee the validator will correctly work.\n");
2817 kf->utf8_warning = TRUE;
2820 if (g_ascii_isspace (*line)) {
2821 print_fatal (kf, "line \"%s\" starts with a space. Comment, group and "
2822 "key-value lines should not start with a space. The "
2823 "validation will continue, with the leading spaces "
2824 "ignored.\n", line);
2825 while (g_ascii_isspace (*line))
2829 if (validate_line_is_comment (kf, line))
2833 if (validate_line_looks_like_group (kf, line, &group)) {
2834 if (!kf->current_group &&
2835 (strcmp (group, GROUP_DESKTOP_ENTRY) &&
2836 strcmp (group, GROUP_KDE_DESKTOP_ENTRY)))
2837 print_fatal (kf, "first group is not \"" GROUP_DESKTOP_ENTRY "\"\n");
2839 if (kf->current_group && strcmp (kf->current_group, group))
2840 validate_keys_for_current_group (kf);
2842 if (g_hash_table_lookup_extended (kf->groups, group, NULL, NULL)) {
2843 print_fatal (kf, "file contains multiple groups named \"%s\", but "
2844 "multiple groups may not have the same name\n", group);
2846 validate_group_name (kf, group);
2847 g_hash_table_insert (kf->groups, g_strdup (group), NULL);
2850 if (kf->current_group)
2851 g_free (kf->current_group);
2852 kf->current_group = group;
2859 if (validate_line_looks_like_entry (kf, line, &key, &value)) {
2860 if (kf->current_group) {
2862 kf_keyvalue *keyvalue;
2864 keyvalue = g_slice_new (kf_keyvalue);
2865 keyvalue->key = key;
2866 keyvalue->value = value;
2868 keys = g_hash_table_lookup (kf->groups, kf->current_group);
2869 keys = g_slist_prepend (keys, keyvalue);
2870 g_hash_table_replace (kf->groups, g_strdup (kf->current_group), keys);
2877 print_fatal (kf, "file contains entry \"%s\" before the first group, "
2878 "but only comments are accepted before the first "
2885 print_fatal (kf, "file contains line \"%s\", which is not a comment, "
2886 "a group or an entry\n", line);
2889 /* + Desktop entry files are encoded as lines of 8-bit characters separated by
2894 validate_parse_data (kf_validator *kf,
2900 for (i = 0; i < length; i++) {
2901 if (data[i] == '\n') {
2902 if (i > 0 && data[i - 1] == '\r') {
2903 g_string_erase (kf->parse_buffer, kf->parse_buffer->len - 1, 1);
2905 if (!kf->cr_error) {
2906 print_fatal (kf, "file contains at least one line ending with a "
2907 "carriage return before the line feed, while lines "
2908 "should only be separated by a line feed "
2909 "character. First such line is: \"%s\"\n",
2910 kf->parse_buffer->str);
2911 kf->cr_error = TRUE;
2915 if (kf->parse_buffer->len > 0) {
2916 validate_parse_line (kf);
2917 g_string_erase (kf->parse_buffer, 0, -1);
2920 } else if (data[i] == '\r') {
2921 if (!kf->cr_error) {
2922 print_fatal (kf, "file contains at least one line ending with a "
2923 "carriage return, while lines should only be "
2924 "separated by a line feed character. First such "
2925 "line is: \"%s\"\n", kf->parse_buffer->str);
2926 kf->cr_error = TRUE;
2932 g_string_append_c (kf->parse_buffer, data[i]);
2937 validate_flush_parse_buffer (kf_validator *kf)
2939 if (kf->parse_buffer->len > 0) {
2940 validate_parse_line (kf);
2941 g_string_erase (kf->parse_buffer, 0, -1);
2944 if (kf->current_group)
2945 validate_keys_for_current_group (kf);
2948 #define VALIDATE_READ_SIZE 4096
2950 validate_parse_from_fd (kf_validator *kf,
2954 struct stat stat_buf;
2955 char read_buf[VALIDATE_READ_SIZE];
2957 if (fstat (fd, &stat_buf) < 0) {
2958 print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
2962 if (!S_ISREG (stat_buf.st_mode)) {
2963 print_fatal (kf, "file is not a regular file\n");
2967 if (stat_buf.st_size == 0) {
2968 print_fatal (kf, "file is empty\n");
2974 bytes_read = read (fd, read_buf, VALIDATE_READ_SIZE);
2976 if (bytes_read == 0) /* End of File */
2979 if (bytes_read < 0) {
2980 if (errno == EINTR || errno == EAGAIN)
2983 /* let's validate what we already have */
2984 validate_flush_parse_buffer (kf);
2986 print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
2990 validate_parse_data (kf, read_buf, bytes_read);
2993 validate_flush_parse_buffer (kf);
2999 validate_load_and_parse (kf_validator *kf)
3004 fd = g_open (kf->filename, O_RDONLY, 0);
3007 print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
3011 ret = validate_parse_from_fd (kf, fd);
3019 groups_hashtable_free (gpointer key,
3026 list = (GSList *) value;
3027 for (sl = list; sl != NULL; sl = sl->next) {
3028 kf_keyvalue *keyvalue;
3030 keyvalue = (kf_keyvalue *) sl->data;
3031 g_free (keyvalue->key);
3032 g_free (keyvalue->value);
3033 g_slice_free (kf_keyvalue, keyvalue);
3036 g_slist_free (list);
3042 desktop_file_validate (const char *filename,
3044 gboolean no_warn_deprecated,
3049 /* just a consistency check */
3050 g_assert (G_N_ELEMENTS (registered_types) == LAST_TYPE - 1);
3052 kf.filename = filename;
3053 kf.parse_buffer = g_string_new ("");
3054 kf.utf8_warning = FALSE;
3055 kf.cr_error = FALSE;
3056 kf.current_group = NULL;
3057 kf.groups = g_hash_table_new_full (g_str_hash, g_str_equal,
3059 kf.current_keys = NULL;
3060 kf.kde_reserved_warnings = warn_kde;
3061 kf.no_deprecated_warnings = no_warn_deprecated;
3062 kf.no_hints = no_hints;
3064 kf.main_group = NULL;
3065 kf.type = INVALID_TYPE;
3066 kf.type_string = NULL;
3068 kf.application_keys = NULL;
3069 kf.link_keys = NULL;
3070 kf.fsdevice_keys = NULL;
3071 kf.mimetype_keys = NULL;
3072 kf.action_values = g_hash_table_new_full (g_str_hash, g_str_equal,
3074 kf.action_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
3076 kf.fatal_error = FALSE;
3078 validate_load_and_parse (&kf);
3079 //FIXME: this does not work well if there are both a Desktop Entry and a KDE
3080 //Desktop Entry groups since only the last one will be validated for this.
3081 if (kf.main_group) {
3082 validate_required_desktop_keys (&kf);
3083 validate_type_keys (&kf);
3085 validate_actions (&kf);
3086 validate_filename (&kf);
3088 g_list_foreach (kf.application_keys, (GFunc) g_free, NULL);
3089 g_list_free (kf.application_keys);
3090 g_list_foreach (kf.link_keys, (GFunc) g_free, NULL);
3091 g_list_free (kf.link_keys);
3092 g_list_foreach (kf.fsdevice_keys, (GFunc) g_free, NULL);
3093 g_list_free (kf.fsdevice_keys);
3094 g_list_foreach (kf.mimetype_keys, (GFunc) g_free, NULL);
3095 g_list_free (kf.mimetype_keys);
3097 g_hash_table_destroy (kf.action_values);
3098 g_hash_table_destroy (kf.action_groups);
3100 g_assert (kf.current_keys == NULL);
3101 /* we can't add an automatic destroy handler for the value because we replace
3102 * it when adding keys, and this means we'd have to copy the value each time
3104 g_hash_table_foreach_remove (kf.groups, groups_hashtable_free, NULL);
3105 g_hash_table_destroy (kf.groups);
3106 g_free (kf.current_group);
3107 g_string_free (kf.parse_buffer, TRUE);
3109 return (!kf.fatal_error);
3113 fixup_list (GKeyFile *keyfile,
3114 const gchar *filename,
3120 value = g_key_file_get_value (keyfile, GROUP_DESKTOP_ENTRY, key, NULL);
3124 len = strlen (value);
3126 if (len > 0 && (value[len - 1] != ';' ||
3127 (len > 1 && value[len - 2] == '\\' &&
3128 (len < 3 || value[len - 3] != '\\')))) {
3131 g_printerr ("%s: warning: key \"%s\" is a list and does not have a "
3132 "semicolon as trailing character, fixing\n",
3135 str = g_strconcat (value, ";", NULL);
3136 g_key_file_set_value (keyfile, GROUP_DESKTOP_ENTRY,
3142 /* return FALSE if we were unable to fix the file */
3144 desktop_file_fixup (GKeyFile *keyfile,
3145 const char *filename)
3151 if (g_key_file_has_group (keyfile, GROUP_KDE_DESKTOP_ENTRY)) {
3152 g_printerr ("%s: warning: renaming deprecated \"%s\" group to \"%s\"\n",
3153 filename, GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY);
3154 dfu_key_file_rename_group (keyfile,
3155 GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY);
3158 keys = g_key_file_get_keys (keyfile, GROUP_DESKTOP_ENTRY, &keys_nb, NULL);
3160 /* Fix lists to have a ';' at the end if they don't */
3161 for (i = 0; i < G_N_ELEMENTS (registered_desktop_keys); i++) {
3162 if (registered_desktop_keys[i].type == DESKTOP_STRING_LIST_TYPE ||
3163 registered_desktop_keys[i].type == DESKTOP_REGEXP_LIST_TYPE)
3164 fixup_list (keyfile, filename, registered_desktop_keys[i].name);
3166 if (registered_desktop_keys[i].type == DESKTOP_LOCALESTRING_LIST_TYPE) {
3170 keylen = strlen (registered_desktop_keys[i].name);
3171 for (j = 0; j < keys_nb; j++) {
3172 if (g_str_has_prefix (keys[j], registered_desktop_keys[i].name) &&
3173 (keys[j][keylen] == '[' || keys[j][keylen] == '\0')) {
3174 fixup_list (keyfile, filename, keys[j]);