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_dbus_activatable_key (kf_validator *kf,
215 const char *locale_key,
218 handle_dev_key (kf_validator *kf,
219 const char *locale_key,
222 handle_mountpoint_key (kf_validator *kf,
223 const char *locale_key,
226 handle_encoding_key (kf_validator *kf,
227 const char *locale_key,
230 handle_autostart_condition_key (kf_validator *kf,
231 const char *locale_key,
234 handle_key_for_application (kf_validator *kf,
235 const char *locale_key,
238 handle_key_for_link (kf_validator *kf,
239 const char *locale_key,
242 handle_key_for_fsdevice (kf_validator *kf,
243 const char *locale_key,
246 handle_key_for_mimetype (kf_validator *kf,
247 const char *locale_key,
253 gboolean kde_reserved;
255 } registered_types[] = {
256 { APPLICATION_TYPE, "Application", FALSE, FALSE },
257 { LINK_TYPE, "Link", FALSE, FALSE },
258 { DIRECTORY_TYPE, "Directory", FALSE, FALSE },
259 { SERVICE_TYPE, "Service", TRUE, FALSE },
260 { SERVICE_TYPE_TYPE, "ServiceType", TRUE, FALSE },
261 { FSDEVICE_TYPE, "FSDevice", TRUE, FALSE },
262 { MIMETYPE_TYPE, "MimeType", FALSE, TRUE }
267 gboolean (* validate) (kf_validator *kf,
271 } validate_for_type[] = {
272 { DESKTOP_STRING_TYPE, validate_string_key },
273 { DESKTOP_LOCALESTRING_TYPE, validate_localestring_key },
274 { DESKTOP_BOOLEAN_TYPE, validate_boolean_key },
275 { DESKTOP_NUMERIC_TYPE, validate_numeric_key },
276 { DESKTOP_STRING_LIST_TYPE, validate_string_list_key },
277 { DESKTOP_REGEXP_LIST_TYPE, validate_regexp_list_key },
278 { DESKTOP_LOCALESTRING_LIST_TYPE, validate_localestring_list_key }
286 gboolean kde_reserved;
287 gboolean (* handle_and_validate) (kf_validator *kf,
288 const char *locale_key,
290 } DesktopKeyDefinition;
292 static DesktopKeyDefinition registered_desktop_keys[] = {
293 { DESKTOP_STRING_TYPE, "Type", TRUE, FALSE, FALSE, handle_type_key },
294 /* it is numeric according to the spec, but it's not true in previous
295 * versions of the spec. handle_version_key() will manage this */
296 { DESKTOP_STRING_TYPE, "Version", FALSE, FALSE, FALSE, handle_version_key },
297 { DESKTOP_LOCALESTRING_TYPE, "Name", TRUE, FALSE, FALSE, NULL },
298 { DESKTOP_LOCALESTRING_TYPE, "GenericName", FALSE, FALSE, FALSE, NULL },
299 { DESKTOP_BOOLEAN_TYPE, "NoDisplay", FALSE, FALSE, FALSE, NULL },
300 { DESKTOP_LOCALESTRING_TYPE, "Comment", FALSE, FALSE, FALSE, handle_comment_key },
301 { DESKTOP_LOCALESTRING_TYPE, "Icon", FALSE, FALSE, FALSE, handle_icon_key },
302 { DESKTOP_BOOLEAN_TYPE, "Hidden", FALSE, FALSE, FALSE, NULL },
303 { DESKTOP_STRING_LIST_TYPE, "OnlyShowIn", FALSE, FALSE, FALSE, handle_show_in_key },
304 { DESKTOP_STRING_LIST_TYPE, "NotShowIn", FALSE, FALSE, FALSE, handle_show_in_key },
305 { DESKTOP_STRING_TYPE, "TryExec", FALSE, FALSE, FALSE, handle_key_for_application },
306 { DESKTOP_STRING_TYPE, "Exec", FALSE, FALSE, FALSE, handle_desktop_exec_key },
307 { DESKTOP_STRING_TYPE, "Path", FALSE, FALSE, FALSE, handle_path_key },
308 { DESKTOP_BOOLEAN_TYPE, "Terminal", FALSE, FALSE, FALSE, handle_key_for_application },
309 { DESKTOP_STRING_LIST_TYPE, "MimeType", FALSE, FALSE, FALSE, handle_mime_key },
310 { DESKTOP_STRING_LIST_TYPE, "Categories", FALSE, FALSE, FALSE, handle_categories_key },
311 { DESKTOP_BOOLEAN_TYPE, "StartupNotify", FALSE, FALSE, FALSE, handle_key_for_application },
312 { DESKTOP_STRING_TYPE, "StartupWMClass", FALSE, FALSE, FALSE, handle_key_for_application },
313 { DESKTOP_STRING_TYPE, "URL", FALSE, FALSE, FALSE, handle_key_for_link },
314 /* since 1.1 (used to be a key reserved for KDE since 0.9.4) */
315 { DESKTOP_LOCALESTRING_LIST_TYPE, "Keywords", FALSE, FALSE, FALSE, NULL },
316 /* since 1.1 (used to be in the spec before 1.0, but was not really
318 { DESKTOP_STRING_LIST_TYPE, "Actions", FALSE, FALSE, FALSE, handle_actions_key },
320 { DESKTOP_BOOLEAN_TYPE, "DBusActivatable", FALSE, FALSE, FALSE, handle_dbus_activatable_key },
322 /* Keys reserved for KDE */
325 { DESKTOP_STRING_TYPE, "ServiceTypes", FALSE, FALSE, TRUE, NULL },
326 { DESKTOP_STRING_TYPE, "DocPath", FALSE, FALSE, TRUE, NULL },
327 { DESKTOP_STRING_TYPE, "InitialPreference", FALSE, FALSE, TRUE, NULL },
329 { DESKTOP_STRING_TYPE, "Dev", FALSE, FALSE, TRUE, handle_dev_key },
330 { DESKTOP_STRING_TYPE, "FSType", FALSE, FALSE, TRUE, handle_key_for_fsdevice },
331 { DESKTOP_STRING_TYPE, "MountPoint", FALSE, FALSE, TRUE, handle_mountpoint_key },
332 { DESKTOP_BOOLEAN_TYPE, "ReadOnly", FALSE, FALSE, TRUE, handle_key_for_fsdevice },
333 { DESKTOP_STRING_TYPE, "UnmountIcon", FALSE, FALSE, TRUE, handle_key_for_fsdevice },
335 /* Deprecated keys */
338 { DESKTOP_STRING_TYPE, "Protocols", FALSE, TRUE, FALSE, NULL },
339 { DESKTOP_STRING_TYPE, "Extensions", FALSE, TRUE, FALSE, NULL },
340 { DESKTOP_STRING_TYPE, "BinaryPattern", FALSE, TRUE, FALSE, NULL },
341 { DESKTOP_STRING_TYPE, "MapNotify", FALSE, TRUE, FALSE, NULL },
343 { DESKTOP_REGEXP_LIST_TYPE, "Patterns", FALSE, TRUE, FALSE, handle_key_for_mimetype },
344 { DESKTOP_STRING_TYPE, "DefaultApp", FALSE, TRUE, FALSE, handle_key_for_mimetype },
345 { DESKTOP_STRING_TYPE, "MiniIcon", FALSE, TRUE, FALSE, NULL },
346 { DESKTOP_STRING_TYPE, "TerminalOptions", FALSE, TRUE, FALSE, NULL },
348 { DESKTOP_STRING_TYPE, "Encoding", FALSE, TRUE, FALSE, handle_encoding_key },
349 { DESKTOP_LOCALESTRING_TYPE, "SwallowTitle", FALSE, TRUE, FALSE, NULL },
350 { DESKTOP_STRING_TYPE, "SwallowExec", FALSE, TRUE, FALSE, NULL },
352 { DESKTOP_STRING_LIST_TYPE, "SortOrder", FALSE, TRUE, FALSE, NULL },
353 { DESKTOP_REGEXP_LIST_TYPE, "FilePattern", FALSE, TRUE, FALSE, NULL },
355 /* Keys from other specifications */
357 /* Autostart spec, currently proposed; adopted by GNOME */
358 { DESKTOP_STRING_TYPE, "AutostartCondition", FALSE, FALSE, FALSE, handle_autostart_condition_key }
361 static DesktopKeyDefinition registered_action_keys[] = {
362 { DESKTOP_LOCALESTRING_TYPE, "Name", TRUE, FALSE, FALSE, NULL },
363 { DESKTOP_LOCALESTRING_TYPE, "Icon", FALSE, FALSE, FALSE, handle_icon_key },
364 { DESKTOP_STRING_LIST_TYPE, "OnlyShowIn", FALSE, TRUE, FALSE, handle_show_in_key },
365 { DESKTOP_STRING_LIST_TYPE, "NotShowIn", FALSE, TRUE, FALSE, handle_show_in_key },
366 { DESKTOP_STRING_TYPE, "Exec", TRUE, FALSE, FALSE, handle_exec_key }
369 static const char *show_in_registered[] = {
370 "GNOME", "KDE", "LXDE", "LXQt", "MATE", "Razor", "ROX", "TDE", "Unity", "XFCE", "Cinnamon", "EDE", "Old"
375 const char *first_arg[3];
376 unsigned int additional_args;
377 } registered_autostart_condition[] = {
378 { "GNOME", { NULL }, 1 },
379 { "GNOME3", { "if-session", "unless-session", NULL }, 1},
380 { "GSettings", { NULL }, 2 }
386 gboolean require_only_show_in;
388 const char *requires[2];
389 const char *suggests[4];
390 } registered_categories[] = {
391 { "AudioVideo", TRUE, FALSE, FALSE, { NULL }, { NULL } },
392 { "Audio", TRUE, FALSE, FALSE, { "AudioVideo", NULL }, { NULL } },
393 { "Video", TRUE, FALSE, FALSE, { "AudioVideo", NULL }, { NULL } },
394 { "Development", TRUE, FALSE, FALSE, { NULL }, { NULL } },
395 { "Education", TRUE, FALSE, FALSE, { NULL }, { NULL } },
396 { "Game", TRUE, FALSE, FALSE, { NULL }, { NULL } },
397 { "Graphics", TRUE, FALSE, FALSE, { NULL }, { NULL } },
398 { "Network", TRUE, FALSE, FALSE, { NULL }, { NULL } },
399 { "Office", TRUE, FALSE, FALSE, { NULL }, { NULL } },
400 { "Science", TRUE, FALSE, FALSE, { NULL }, { NULL } },
401 { "Settings", TRUE, FALSE, FALSE, { NULL }, { NULL } },
402 { "System", TRUE, FALSE, FALSE, { NULL }, { NULL } },
403 { "Utility", TRUE, FALSE, FALSE, { NULL }, { NULL } },
404 { "Audio", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
405 { "Video", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
406 { "Building", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
407 { "Debugger", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
408 { "IDE", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
409 { "GUIDesigner", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
410 { "Profiling", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
411 { "RevisionControl", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
412 { "Translation", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
413 { "Calendar", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
414 { "ContactManagement", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
415 { "Database", FALSE, FALSE, FALSE, { NULL }, { "Office", "Development", "AudioVideo", NULL } },
416 { "Dictionary", FALSE, FALSE, FALSE, { NULL }, { "Office", "TextTools", NULL } },
417 { "Chart", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
418 { "Email", FALSE, FALSE, FALSE, { NULL }, { "Office", "Network", NULL } },
419 { "Finance", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
420 { "FlowChart", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
421 { "PDA", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
422 { "ProjectManagement", FALSE, FALSE, FALSE, { NULL }, { "Office", "Development", NULL } },
423 { "Presentation", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
424 { "Spreadsheet", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
425 { "WordProcessor", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
426 { "2DGraphics", FALSE, FALSE, FALSE, { NULL }, { "Graphics", NULL } },
427 { "VectorGraphics", FALSE, FALSE, FALSE, { NULL }, { "Graphics;2DGraphics", NULL } },
428 { "RasterGraphics", FALSE, FALSE, FALSE, { NULL }, { "Graphics;2DGraphics", NULL } },
429 { "3DGraphics", FALSE, FALSE, FALSE, { NULL }, { "Graphics", NULL } },
430 { "Scanning", FALSE, FALSE, FALSE, { NULL }, { "Graphics", NULL } },
431 { "OCR", FALSE, FALSE, FALSE, { NULL }, { "Graphics;Scanning", NULL } },
432 { "Photography", FALSE, FALSE, FALSE, { NULL }, { "Graphics", "Office", NULL } },
433 { "Publishing", FALSE, FALSE, FALSE, { NULL }, { "Graphics", "Office", NULL } },
434 { "Viewer", FALSE, FALSE, FALSE, { NULL }, { "Graphics", "Office", NULL } },
435 { "TextTools", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
436 { "DesktopSettings", FALSE, FALSE, FALSE, { NULL }, { "Settings", NULL } },
437 { "HardwareSettings", FALSE, FALSE, FALSE, { NULL }, { "Settings", NULL } },
438 { "Printing", FALSE, FALSE, FALSE, { NULL }, { "HardwareSettings;Settings", NULL } },
439 { "PackageManager", FALSE, FALSE, FALSE, { NULL }, { "Settings", NULL } },
440 { "Dialup", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
441 { "InstantMessaging", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
442 { "Chat", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
443 { "IRCClient", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
444 { "Feed", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
445 { "FileTransfer", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
446 { "HamRadio", FALSE, FALSE, FALSE, { NULL }, { "Network", "Audio", NULL } },
447 { "News", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
448 { "P2P", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
449 { "RemoteAccess", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
450 { "Telephony", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
451 { "TelephonyTools", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
452 { "VideoConference", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
453 { "WebBrowser", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
454 { "WebDevelopment", FALSE, FALSE, FALSE, { NULL }, { "Network", "Development", NULL } },
455 { "Midi", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
456 { "Mixer", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
457 { "Sequencer", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
458 { "Tuner", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
459 { "TV", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Video", NULL } },
460 { "AudioVideoEditing", FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
461 { "Player", FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
462 { "Recorder", FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
463 { "DiscBurning", FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
464 { "ActionGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
465 { "AdventureGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
466 { "ArcadeGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
467 { "BoardGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
468 { "BlocksGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
469 { "CardGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
470 { "KidsGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
471 { "LogicGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
472 { "RolePlaying", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
473 { "Shooter", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
474 { "Simulation", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
475 { "SportsGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
476 { "StrategyGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
477 { "Art", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
478 { "Construction", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
479 { "Music", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo", "Education", NULL } },
480 { "Languages", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
481 { "ArtificialIntelligence", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
482 { "Astronomy", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
483 { "Biology", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
484 { "Chemistry", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
485 { "ComputerScience", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
486 { "DataVisualization", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
487 { "Economy", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
488 { "Electricity", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
489 { "Geography", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
490 { "Geology", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
491 { "Geoscience", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
492 { "History", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
493 { "Humanities", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
494 { "ImageProcessing", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
495 { "Literature", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
496 { "Maps", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", "Utility", NULL } },
497 { "Math", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
498 { "NumericalAnalysis", FALSE, FALSE, FALSE, { NULL }, { "Education;Math", "Science;Math", NULL } },
499 { "MedicalSoftware", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
500 { "Physics", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
501 { "Robotics", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
502 { "Spirituality", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", "Utility", NULL } },
503 { "Sports", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
504 { "ParallelComputing", FALSE, FALSE, FALSE, { NULL }, { "Education;ComputerScience", "Science;ComputerScience", NULL } },
505 { "Amusement", FALSE, FALSE, FALSE, { NULL }, { NULL } },
506 { "Archiving", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
507 { "Compression", FALSE, FALSE, FALSE, { NULL }, { "Utility;Archiving", NULL } },
508 { "Electronics", FALSE, FALSE, FALSE, { NULL }, { NULL } },
509 { "Emulator", FALSE, FALSE, FALSE, { NULL }, { "System", "Game", NULL } },
510 { "Engineering", FALSE, FALSE, FALSE, { NULL }, { NULL } },
511 { "FileTools", FALSE, FALSE, FALSE, { NULL }, { "Utility", "System", NULL } },
512 { "FileManager", FALSE, FALSE, FALSE, { NULL }, { "System;FileTools", NULL } },
513 { "TerminalEmulator", FALSE, FALSE, FALSE, { NULL }, { "System", NULL } },
514 { "Filesystem", FALSE, FALSE, FALSE, { NULL }, { "System", NULL } },
515 { "Monitor", FALSE, FALSE, FALSE, { NULL }, { "System", "Network", NULL } },
516 { "Security", FALSE, FALSE, FALSE, { NULL }, { "Settings", "System", NULL } },
517 { "Accessibility", FALSE, FALSE, FALSE, { NULL }, { "Settings", "Utility", NULL } },
518 { "Calculator", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
519 { "Clock", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
520 { "TextEditor", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
521 { "Documentation", FALSE, FALSE, FALSE, { NULL }, { NULL } },
522 { "Adult", FALSE, FALSE, FALSE, { NULL }, { NULL } },
523 { "Core", FALSE, FALSE, FALSE, { NULL }, { NULL } },
524 { "KDE", FALSE, FALSE, FALSE, { NULL }, { "Qt", NULL } },
525 { "GNOME", FALSE, FALSE, FALSE, { NULL }, { "GTK", NULL } },
526 { "XFCE", FALSE, FALSE, FALSE, { NULL }, { "GTK", NULL } },
527 { "GTK", FALSE, FALSE, FALSE, { NULL }, { NULL } },
528 { "Qt", FALSE, FALSE, FALSE, { NULL }, { NULL } },
529 { "Motif", FALSE, FALSE, FALSE, { NULL }, { NULL } },
530 { "Java", FALSE, FALSE, FALSE, { NULL }, { NULL } },
531 { "ConsoleOnly", FALSE, FALSE, FALSE, { NULL }, { NULL } },
532 { "Screensaver", FALSE, TRUE, FALSE, { NULL }, { NULL } },
533 { "TrayIcon", FALSE, TRUE, FALSE, { NULL }, { NULL } },
534 { "Applet", FALSE, TRUE, FALSE, { NULL }, { NULL } },
535 { "Shell", FALSE, TRUE, FALSE, { NULL }, { NULL } },
536 { "Application", FALSE, FALSE, TRUE, { NULL }, { NULL } },
537 { "Applications", FALSE, FALSE, TRUE, { NULL }, { NULL } }
541 print_fatal (kf_validator *kf, const char *format, ...)
546 g_return_if_fail (kf != NULL && format != NULL);
548 kf->fatal_error = TRUE;
550 va_start (args, format);
551 str = g_strdup_vprintf (format, args);
554 g_print ("%s: error: %s", kf->filename, str);
560 print_future_fatal (kf_validator *kf, const char *format, ...)
565 g_return_if_fail (kf != NULL && format != NULL);
567 va_start (args, format);
568 str = g_strdup_vprintf (format, args);
571 g_print ("%s: error: (will be fatal in the future): %s", kf->filename, str);
577 print_warning (kf_validator *kf, const char *format, ...)
582 g_return_if_fail (kf != NULL && format != NULL);
584 va_start (args, format);
585 str = g_strdup_vprintf (format, args);
588 g_print ("%s: warning: %s", kf->filename, str);
594 print_hint (kf_validator *kf, const char *format, ...)
599 g_return_if_fail (kf != NULL && format != NULL);
604 va_start (args, format);
605 str = g_strdup_vprintf (format, args);
608 g_print ("%s: hint: %s", kf->filename, str);
613 /* + Key names must contain only the characters A-Za-z0-9-.
617 key_is_valid (const char *key,
623 for (i = 0; i < len; i++) {
625 if (!g_ascii_isalnum (c) && c != '-')
632 /* + Values of type string may contain all ASCII characters except for control
637 validate_string_key (kf_validator *kf,
647 for (i = 0; value[i] != '\0'; i++) {
648 if (g_ascii_iscntrl (value[i])) {
655 print_fatal (kf, "value \"%s\" for string key \"%s\" in group \"%s\" "
656 "contains invalid characters, string values may contain "
657 "all ASCII characters except for control characters\n",
658 value, key, kf->current_group);
666 /* + Values of type localestring are user displayable, and are encoded in
669 * + If a postfixed key occurs, the same key must be also present without the
674 validate_localestring_key (kf_validator *kf,
682 locale_key = g_strdup_printf ("%s[%s]", key, locale);
684 locale_key = g_strdup_printf ("%s", key);
686 if (!g_utf8_validate (value, -1, NULL)) {
687 print_fatal (kf, "value \"%s\" for locale string key \"%s\" in group "
688 "\"%s\" contains invalid UTF-8 characters, locale string "
689 "values should be encoded in UTF-8\n",
690 value, locale_key, kf->current_group);
696 if (!g_hash_table_lookup (kf->current_keys, key)) {
697 print_fatal (kf, "key \"%s\" in group \"%s\" is a localized key, but "
698 "there is no non-localized key \"%s\"\n",
699 locale_key, kf->current_group, key);
710 /* + Values of type boolean must either be the string true or false.
712 * + Historically some booleans have been represented by the numeric entries 0
713 * or 1. With this version of the standard they are now to be represented as
714 * a boolean string. However, if an implementation is reading a pre-1.0
715 * desktop entry, it should interpret 0 and 1 as false and true,
720 validate_boolean_key (kf_validator *kf,
725 if (strcmp (value, "true") && strcmp (value, "false") &&
726 strcmp (value, "0") && strcmp (value, "1")) {
727 print_fatal (kf, "value \"%s\" for boolean key \"%s\" in group \"%s\" "
728 "contains invalid characters, boolean values must be "
729 "\"false\" or \"true\"\n",
730 value, key, kf->current_group);
734 if (!kf->no_deprecated_warnings &&
735 (!strcmp (value, "0") || !strcmp (value, "1")))
736 print_warning (kf, "boolean key \"%s\" in group \"%s\" has value \"%s\", "
737 "which is deprecated: boolean values should be "
738 "\"false\" or \"true\"\n",
739 key, kf->current_group, value);
744 /* + Values of type numeric must be a valid floating point number as recognized
745 * by the %f specifier for scanf.
749 validate_numeric_key (kf_validator *kf,
757 res = sscanf (value, "%f", &d);
759 print_fatal (kf, "value \"%s\" for numeric key \"%s\" in group \"%s\" "
760 "contains invalid characters, numeric values must be "
761 "valid floating point numbers\n",
762 value, key, kf->current_group);
769 /* + Values of type string may contain all ASCII characters except for control
772 * + FIXME: how should an empty list be handled?
775 validate_string_regexp_list_key (kf_validator *kf,
786 for (i = 0; value[i] != '\0'; i++) {
787 if (g_ascii_iscntrl (value[i])) {
794 print_fatal (kf, "value \"%s\" for %s list key \"%s\" in group \"%s\" "
795 "contains invalid character '%c', %s list values may "
796 "contain all ASCII characters except for control "
798 value, type, key, kf->current_group, value[i], type);
807 validate_string_list_key (kf_validator *kf,
812 return validate_string_regexp_list_key (kf, key, locale, value, "string");
816 validate_regexp_list_key (kf_validator *kf,
821 return validate_string_regexp_list_key (kf, key, locale, value, "regexp");
824 /* + Values of type localestring are user displayable, and are encoded in
826 * FIXME: partly checked; we checked the whole value is encored in UTF-8, but
827 * not that each value of the list is. Although this might be equivalent?
828 * + If a postfixed key occurs, the same key must be also present without the
831 * + FIXME: how should an empty list be handled?
834 validate_localestring_list_key (kf_validator *kf,
842 locale_key = g_strdup_printf ("%s[%s]", key, locale);
844 locale_key = g_strdup_printf ("%s", key);
847 if (!g_utf8_validate (value, -1, NULL)) {
848 print_fatal (kf, "value \"%s\" for locale string list key \"%s\" in group "
849 "\"%s\" contains invalid UTF-8 characters, locale string "
850 "list values should be encoded in UTF-8\n",
851 value, locale_key, kf->current_group);
857 if (!g_hash_table_lookup (kf->current_keys, key)) {
858 print_fatal (kf, "key \"%s\" in group \"%s\" is a localized key, but "
859 "there is no non-localized key \"%s\"\n",
860 locale_key, kf->current_group, key);
871 /* + This specification defines 3 types of desktop entries: Application
872 * (type 1), Link (type 2) and Directory (type 3). To allow the addition of
873 * new types in the future, implementations should ignore desktop entries
874 * with an unknown type.
876 * + KDE specific types: ServiceType, Service and FSDevice
880 handle_type_key (kf_validator *kf,
881 const char *locale_key,
886 for (i = 0; i < G_N_ELEMENTS (registered_types); i++) {
887 if (!strcmp (value, registered_types[i].name))
891 if (i == G_N_ELEMENTS (registered_types)) {
892 /* force the type, since the key might be present multiple times... */
893 kf->type = INVALID_TYPE;
895 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
896 "is not a registered type value (\"Application\", "
897 "\"Link\" and \"Directory\")\n",
898 value, locale_key, kf->current_group);
902 if (registered_types[i].kde_reserved && kf->kde_reserved_warnings)
903 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
904 "is a reserved value for KDE\n",
905 value, locale_key, kf->current_group);
907 if (registered_types[i].deprecated && !kf->no_deprecated_warnings)
908 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
910 value, locale_key, kf->current_group);
912 kf->type = registered_types[i].type;
913 kf->type_string = registered_types[i].name;
918 /* + Entries that confirm with this version of the specification should use
921 * + Previous versions of the spec: 0.9.x where 3 <= x <= 8
925 handle_version_key (kf_validator *kf,
926 const char *locale_key,
929 if (!strcmp (value, "1.1"))
932 if (!strcmp (value, "1.0"))
935 if (!strncmp (value, "0.9.", strlen ("0.9."))) {
938 c = value[strlen ("0.9.")];
939 if ('3' <= c && c <= '8' && value[strlen ("0.9.") + 1] == '\0')
943 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
944 "is not a known version\n",
945 value, locale_key, kf->current_group);
949 /* + Tooltip for the entry, for example "View sites on the Internet", should
950 * not be redundant with Name or GenericName.
954 handle_comment_key (kf_validator *kf,
955 const char *locale_key,
958 char *locale_compare_key;
959 kf_keyvalue *keyvalue;
961 locale_compare_key = g_strdup_printf ("Name%s",
962 locale_key + strlen ("Comment"));
963 keyvalue = g_hash_table_lookup (kf->current_keys, locale_compare_key);
964 g_free (locale_compare_key);
966 if (keyvalue && g_ascii_strcasecmp (value, keyvalue->value) == 0) {
967 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
968 "looks redundant with value \"%s\" of key \"%s\"\n",
969 value, locale_key, kf->current_group,
970 keyvalue->value, keyvalue->key);
974 locale_compare_key = g_strdup_printf ("GenericName%s",
975 locale_key + strlen ("Comment"));
976 keyvalue = g_hash_table_lookup (kf->current_keys, locale_compare_key);
977 g_free (locale_compare_key);
979 if (keyvalue && g_ascii_strcasecmp (value, keyvalue->value) == 0) {
980 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
981 "looks redundant with value \"%s\" of key \"%s\"\n",
982 value, locale_key, kf->current_group,
983 keyvalue->value, keyvalue->key);
990 /* + If the name is an absolute path, the given file will be used.
992 * + If the name is not an absolute path, the algorithm described in the Icon
993 * Theme Specification will be used to locate the icon.
995 * FIXME: add clarification to desktop entry spec that the name doesn't
996 * contain an extension
999 handle_icon_key (kf_validator *kf,
1000 const char *locale_key,
1003 if (g_path_is_absolute (value)) {
1004 if (g_str_has_suffix (value, "/")) {
1005 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" is an "
1006 "absolute path to a directory, instead of being an "
1007 "absolute path to an icon or an icon name\n",
1008 value, locale_key, kf->current_group);
1014 if (g_utf8_strchr (value, -1, G_DIR_SEPARATOR)) {
1015 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" looks like "
1016 "a relative path, instead of being an absolute path to "
1017 "an icon or an icon name\n",
1018 value, locale_key, kf->current_group);
1022 if (g_str_has_suffix (value, ".png") ||
1023 g_str_has_suffix (value, ".xpm") ||
1024 g_str_has_suffix (value, ".svg")) {
1025 print_future_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" is an "
1026 "icon name with an extension, but there should be "
1027 "no extension as described in the Icon Theme "
1028 "Specification if the value is not an absolute "
1030 value, locale_key, kf->current_group);
1037 /* + Only one of these keys, either OnlyShowIn or NotShowIn, may appear in a
1040 * + (for possible values see the Desktop Menu Specification)
1042 * FIXME: this is not perfect because it could fail if a new value with
1043 * a semicolon is registered.
1044 * + All values extending the format should start with "X-".
1046 * + FIXME: is this okay to have only ";"? (gnome-theme-installer.desktop does)
1049 handle_show_in_key (kf_validator *kf,
1050 const char *locale_key,
1055 GHashTable *hashtable;
1062 print_fatal (kf, "only one of \"OnlyShowIn\" and \"NotShowIn\" keys "
1063 "may appear in group \"%s\"\n",
1069 hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
1070 show = g_strsplit (value, ";", 0);
1072 for (i = 0; show[i]; i++) {
1073 /* since the value ends with a semicolon, we'll have an empty string
1075 if (*show[i] == '\0' && show[i + 1] == NULL)
1078 if (g_hash_table_lookup (hashtable, show[i])) {
1079 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1080 "contains \"%s\" more than once\n",
1081 value, locale_key, kf->current_group, show[i]);
1085 g_hash_table_insert (hashtable, show[i], show[i]);
1087 if (!strncmp (show[i], "X-", 2))
1090 for (j = 0; j < G_N_ELEMENTS (show_in_registered); j++) {
1091 if (!strcmp (show[i], show_in_registered[j]))
1095 if (j == G_N_ELEMENTS (show_in_registered)) {
1096 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1097 "contains an unregistered value \"%s\"; values "
1098 "extending the format should start with \"X-\"\n",
1099 value, locale_key, kf->current_group, show[i]);
1105 g_hash_table_destroy (hashtable);
1110 /* + A command line consists of an executable program optionally followed by
1111 * one or more arguments. The executable program can either be specified with
1112 * its full path or with the name of the executable only. If no full path is
1113 * provided the executable is looked up in the $PATH used by the desktop
1114 * environment. The name or path of the executable program may not contain
1115 * the equal sign ("=").
1117 * + Arguments are separated by a space.
1119 * + Arguments may be quoted in whole.
1121 * + If an argument contains a reserved character the argument must be quoted.
1123 * + The rules for quoting of arguments is also applicable to the executable
1124 * name or path of the executable program as provided.
1126 * + Quoting must be done by enclosing the argument between double quotes and
1127 * escaping the double quote character, backtick character ("`"), dollar sign
1128 * ("$") and backslash character ("\") by preceding it with an additional
1129 * backslash character. Implementations must undo quoting before expanding
1130 * field codes and before passing the argument to the executable program.
1131 * Reserved characters are space (" "), tab, newline, double quote, single
1132 * quote ("'"), backslash character ("\"), greater-than sign (">"), less-than
1133 * sign ("<"), tilde ("~"), vertical bar ("|"), ampersand ("&"), semicolon
1134 * (";"), dollar sign ("$"), asterisk ("*"), question mark ("?"), hash mark
1135 * ("#"), parenthesis ("(") and (")") and backtick character ("`").
1137 * + Note that the general escape rule for values of type string states that
1138 * the backslash character can be escaped as ("\\") as well and that this
1139 * escape rule is applied before the quoting rule. As such, to unambiguously
1140 * represent a literal backslash character in a quoted argument in a desktop
1141 * entry file requires the use of four successive backslash characters
1142 * ("\\\\"). Likewise, a literal dollar sign in a quoted argument in a
1143 * desktop entry file is unambiguously represented with ("\\$").
1145 * + Field codes consist of the percentage character ("%") followed by an alpha
1146 * character. Literal percentage characters must be escaped as %%.
1148 * + Command lines that contain a field code that is not listed in this
1149 * specification are invalid and must not be processed, in particular
1150 * implementations may not introduce support for field codes not listed in
1151 * this specification. Extensions, if any, should be introduced by means of a
1154 * + A command line may contain at most one %f, %u, %F or %U field code.
1156 * + The %F and %U field codes may only be used as an argument on their own.
1160 handle_exec_key (kf_validator *kf,
1161 const char *locale_key,
1178 #define PRINT_INVALID_IF_FLAG \
1180 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " \
1181 "contains an invalid field code \"%%%c\"\n", \
1182 value, locale_key, kf->current_group, *c); \
1191 /* quotes and escaped characters in quotes */
1193 PRINT_INVALID_IF_FLAG;
1201 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1202 "contains an escaped double quote (\\\\\") "
1203 "outside of a quote, but the double quote is "
1204 "a reserved character\n",
1205 value, locale_key, kf->current_group);
1214 PRINT_INVALID_IF_FLAG;
1217 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1218 "contains a non-escaped character '%c' in a "
1219 "quote, but it should be escaped with two "
1220 "backslashes (\"\\\\%c\")\n",
1221 value, locale_key, kf->current_group, *c, *c);
1226 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1227 "contains a reserved character '%c' outside of a "
1229 value, locale_key, kf->current_group, *c);
1234 PRINT_INVALID_IF_FLAG;
1236 /* Escape character immediately followed by \0? */
1237 if (*(c + 1) == '\0') {
1238 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1239 "ends in an incomplete escape sequence\n",
1240 value, locale_key, kf->current_group);
1246 if (*c == '\\' && in_quote)
1250 /* reserved characters */
1268 PRINT_INVALID_IF_FLAG;
1270 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1271 "contains a reserved character '%c' outside of a "
1273 value, locale_key, kf->current_group, *c);
1286 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1287 "may contain at most one \"%f\", \"%u\", "
1288 "\"%F\" or \"%U\" field code\n",
1289 value, locale_key, kf->current_group);
1301 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1302 "may contain at most one \"%f\", \"%u\", "
1303 "\"%F\" or \"%U\" field code\n",
1304 value, locale_key, kf->current_group);
1325 if (!kf->no_deprecated_warnings)
1326 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1327 "contains a deprecated field code \"%%%c\"\n",
1328 value, locale_key, kf->current_group, *c);
1334 PRINT_INVALID_IF_FLAG;
1342 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" contains a "
1343 "quote which is not closed\n",
1344 value, locale_key, kf->current_group);
1349 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" contains a "
1350 "non-complete field code\n",
1351 value, locale_key, kf->current_group);
1358 /* See checks for handle_exec_key().
1361 handle_desktop_exec_key (kf_validator *kf,
1362 const char *locale_key,
1365 handle_key_for_application (kf, locale_key, value);
1367 return handle_exec_key (kf, locale_key, value);
1370 /* + If entry is of type Application, the working directory to run the program
1371 * in. (probably implies an absolute path)
1373 * + FIXME: is it okay to have an empty string here? (wireshark.desktop does)
1376 handle_path_key (kf_validator *kf,
1377 const char *locale_key,
1380 handle_key_for_application (kf, locale_key, value);
1382 if (!g_path_is_absolute (value))
1383 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1384 "does not look like an absolute path\n",
1385 value, locale_key, kf->current_group);
1390 /* + The MIME type(s) supported by this application. Check they are valid
1395 handle_mime_key (kf_validator *kf,
1396 const char *locale_key,
1401 GHashTable *hashtable;
1404 MimeUtilsValidity valid;
1406 handle_key_for_application (kf, locale_key, value);
1410 hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
1411 types = g_strsplit (value, ";", 0);
1413 for (i = 0; types[i]; i++) {
1414 /* since the value ends with a semicolon, we'll have an empty string
1416 if (*types[i] == '\0' && types[i + 1] == NULL)
1419 if (g_hash_table_lookup (hashtable, types[i])) {
1420 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1421 "contains \"%s\" more than once\n",
1422 value, locale_key, kf->current_group, types[i]);
1426 g_hash_table_insert (hashtable, types[i], types[i]);
1428 valid = mu_mime_type_is_valid (types[i], &valid_error);
1432 case MU_DISCOURAGED:
1433 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1434 "contains value \"%s\" which is a MIME type that "
1435 "should probably not be used: %s\n",
1436 value, locale_key, kf->current_group,
1437 types[i], valid_error);
1439 g_free (valid_error);
1442 print_future_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1443 "contains value \"%s\" which is an invalid "
1445 value, locale_key, kf->current_group,
1446 types[i], valid_error);
1449 g_free (valid_error);
1452 g_assert_not_reached ();
1457 g_hash_table_destroy (hashtable);
1462 /* + FIXME: are there restrictions on how a category should be named?
1463 * + Categories in which the entry should be shown in a menu (for possible
1464 * values see the Desktop Menu Specification).
1466 * + The table below describes Reserved Categories. Reserved Categories have a
1467 * specific desktop specific meaning that has not been standardized (yet).
1468 * Desktop entry files that use a reserved category MUST also include an
1469 * appropriate OnlyShowIn= entry to restrict themselves to those environments
1470 * that properly support the reserved category as used.
1472 * + Accept "Application" as a deprecated category.
1474 * FIXME: it's not really deprecated, so the error message is wrong
1475 * + All categories extending the format should start with "X-".
1477 * + Using multiple main categories may lead to appearing more than once in
1480 * + One main category should be included, otherwise application will appear in
1481 * "catch-all" section of application menu.
1483 * FIXME: decide if it's okay to have an empty list of categories.
1484 * + Some categories, if included, require that another category is included.
1485 * Eg: if Audio is there, AudioVideo must be there.
1487 * + Some categories, if included, suggest that another category is included.
1488 * Eg: Debugger suggests Development.
1489 * This is the case for most additional categories.
1493 handle_categories_key (kf_validator *kf,
1494 const char *locale_key,
1499 GHashTable *hashtable;
1502 int main_categories_nb;
1504 handle_key_for_application (kf, locale_key, value);
1508 /* accept empty value as valid: this is like having no category at all */
1509 if (value[0] == '\0')
1512 hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
1513 categories = g_strsplit (value, ";", 0);
1515 /* this is a two-pass check: we first put the categories in a hash table so
1516 * that they are easy-to-find, and we then do many checks */
1519 for (i = 0; categories[i]; i++) {
1520 /* since the value ends with a semicolon, we'll have an empty string
1522 if (*categories[i] == '\0' && categories[i + 1] == NULL)
1525 if (g_hash_table_lookup (hashtable, categories[i])) {
1526 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1527 "contains \"%s\" more than once\n",
1528 value, locale_key, kf->current_group, categories[i]);
1532 g_hash_table_insert (hashtable, categories[i], categories[i]);
1536 main_categories_nb = 0;
1538 for (i = 0; categories[i]; i++) {
1541 /* since the value ends with a semicolon, we'll have an empty string
1543 if (*categories[i] == '\0' && categories[i + 1] == NULL)
1546 if (!strncmp (categories[i], "X-", 2))
1549 for (j = 0; j < G_N_ELEMENTS (registered_categories); j++) {
1550 if (!strcmp (categories[i], registered_categories[j].name))
1554 if (j == G_N_ELEMENTS (registered_categories)) {
1555 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1556 "contains an unregistered value \"%s\"; values "
1557 "extending the format should start with \"X-\"\n",
1558 value, locale_key, kf->current_group, categories[i]);
1563 if (registered_categories[j].main) {
1564 /* only count it as a main category if none of the required categories
1565 * for this one is also a main category (and is present) */
1566 gboolean required_main_category_present = FALSE;
1568 for (k = 0; registered_categories[j].requires[k] != NULL; k++) {
1569 char **required_categories;
1572 required_categories = g_strsplit (registered_categories[j].requires[k],
1575 for (l = 0; required_categories[l]; l++) {
1578 if (!g_hash_table_lookup (hashtable, required_categories[l]))
1581 for (m = 0; m < G_N_ELEMENTS (registered_categories); m++) {
1582 if (strcmp (required_categories[l],
1583 registered_categories[m].name) != 0)
1586 if (registered_categories[m].main)
1587 required_main_category_present = TRUE;
1592 if (required_main_category_present)
1596 if (required_main_category_present) {
1597 g_strfreev (required_categories);
1601 g_strfreev (required_categories);
1604 if (!required_main_category_present)
1605 main_categories_nb++;
1608 if (registered_categories[j].main && main_categories_nb > 1)
1609 print_hint (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1610 "contains more than one main category; application "
1611 "might appear more than once in the application menu\n",
1612 value, locale_key, kf->current_group);
1615 if (registered_categories[j].deprecated) {
1616 if (!kf->no_deprecated_warnings)
1617 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1618 "contains a deprecated value \"%s\"\n",
1619 value, locale_key, kf->current_group,
1623 if (registered_categories[j].require_only_show_in) {
1624 if (!g_hash_table_lookup (kf->current_keys, "OnlyShowIn")) {
1625 print_fatal (kf, "value item \"%s\" in key \"%s\" in group \"%s\" "
1626 "is a reserved category, so a \"OnlyShowIn\" key "
1627 "must be included\n",
1628 categories[i], locale_key, kf->current_group);
1633 /* required categories */
1635 for (k = 0; registered_categories[j].requires[k] != NULL; k++) {
1636 char **required_categories;
1639 required_categories = g_strsplit (registered_categories[j].requires[k],
1642 for (l = 0; required_categories[l]; l++) {
1643 if (!g_hash_table_lookup (hashtable, required_categories[l]))
1647 /* we've reached the end of a list of required categories, so
1648 * the condition is satisfied */
1649 if (required_categories[l] == NULL) {
1650 g_strfreev (required_categories);
1654 g_strfreev (required_categories);
1657 /* we've reached the end of a non-empty set of required categories; this
1658 * means none of the possible required category (or list of required
1659 * categories) was found */
1660 if (k != 0 && registered_categories[j].requires[k] == NULL) {
1661 GString *output_required;
1663 output_required = g_string_new (registered_categories[j].requires[0]);
1664 for (k = 1; registered_categories[j].requires[k] != NULL; k++)
1665 g_string_append_printf (output_required, ", or %s",
1666 registered_categories[j].requires[k]);
1668 print_future_fatal (kf, "value item \"%s\" in key \"%s\" in group \"%s\" "
1669 "requires another category to be present among "
1670 "the following categories: %s\n",
1671 categories[i], locale_key, kf->current_group,
1672 output_required->str);
1674 g_string_free (output_required, TRUE);
1678 /* suggested categories */
1680 for (k = 0; registered_categories[j].suggests[k] != NULL; k++) {
1681 char **suggested_categories;
1684 suggested_categories = g_strsplit (registered_categories[j].suggests[k],
1687 for (l = 0; suggested_categories[l]; l++) {
1688 if (!g_hash_table_lookup (hashtable, suggested_categories[l]))
1692 /* we've reached the end of a list of suggested categories, so
1693 * the condition is satisfied */
1694 if (suggested_categories[l] == NULL) {
1695 g_strfreev (suggested_categories);
1699 g_strfreev (suggested_categories);
1702 /* we've reached the end of a non-empty set of suggested categories; this
1703 * means none of the possible suggested category (or list of suggested
1704 * categories) was found */
1705 if (k != 0 && registered_categories[j].suggests[k] == NULL) {
1706 GString *output_suggested;
1708 output_suggested = g_string_new (registered_categories[j].suggests[0]);
1709 for (k = 1; registered_categories[j].suggests[k] != NULL; k++)
1710 g_string_append_printf (output_suggested, ", or %s",
1711 registered_categories[j].suggests[k]);
1713 print_hint (kf, "value item \"%s\" in key \"%s\" in group \"%s\" "
1714 "can be extended with another category among the "
1715 "following categories: %s\n",
1716 categories[i], locale_key, kf->current_group,
1717 output_suggested->str);
1719 g_string_free (output_suggested, TRUE);
1724 g_strfreev (categories);
1725 g_hash_table_destroy (hashtable);
1727 g_assert (main_categories_nb >= 0);
1729 if (main_categories_nb == 0)
1730 print_hint (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1731 "does not contain a registered main category; application "
1732 "might only show up in a \"catch-all\" section of the "
1733 "application menu\n",
1734 value, locale_key, kf->current_group);
1739 /* + Identifiers for application actions. Check they are using a valid format.
1742 * Note that we will check later on (in * validate_actions()) that there is a
1743 * "Desktop Action foobar" group for each "foobar" identifier.
1746 handle_actions_key (kf_validator *kf,
1747 const char *locale_key,
1755 handle_key_for_application (kf, locale_key, value);
1758 actions = g_strsplit (value, ";", 0);
1760 for (i = 0; actions[i]; i++) {
1761 /* since the value ends with a semicolon, we'll have an empty string
1763 if (*actions[i] == '\0') {
1764 if (actions[i + 1] != NULL) {
1765 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1766 "contains an empty action\n",
1767 value, locale_key, kf->current_group);
1775 if (g_hash_table_lookup (kf->action_values, actions[i])) {
1776 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1777 "contains action \"%s\" more than once\n",
1778 value, locale_key, kf->current_group, actions[i]);
1782 if (!key_is_valid (actions[i], strlen (actions[i]))) {
1783 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1784 "contains invalid action identifier \"%s\", only "
1785 "alphanumeric characters and '-' are allowed\n",
1786 value, locale_key, kf->current_group, actions[i]);
1791 action = g_strdup (actions[i]);
1792 g_hash_table_insert (kf->action_values, action, action);
1795 g_strfreev (actions);
1800 /* + If the file describes a D-Bus activatable service, the filename must be in
1801 * reverse-DNS notation, i.e. contain at least two dots including the dot
1806 handle_dbus_activatable_key (kf_validator *kf,
1807 const char *locale_key,
1810 gchar *basename_utf8;
1812 const gchar *p = NULL;
1813 gboolean retval = TRUE;
1815 /* If DBusActivatable=false, don't check */
1816 if (strcmp (value, "true") && strcmp (value, "1"))
1819 basename = g_path_get_basename (kf->filename);
1820 basename_utf8 = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL);
1824 p = g_utf8_strchr (basename_utf8, -1, '.');
1827 p = g_utf8_strchr (p + 1, -1, '.');
1831 print_fatal (kf, "DBusActivatable filename must conform to reverse-DNS notation\n");
1835 g_free (basename_utf8);
1840 /* + The device to mount. (probably implies an absolute path)
1844 handle_dev_key (kf_validator *kf,
1845 const char *locale_key,
1848 handle_key_for_fsdevice (kf, locale_key, value);
1850 if (!g_path_is_absolute (value))
1851 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1852 "does not look like an absolute path\n",
1853 value, locale_key, kf->current_group);
1858 /* + The mount point of the device in question. (probably implies an absolute
1863 handle_mountpoint_key (kf_validator *kf,
1864 const char *locale_key,
1867 handle_key_for_fsdevice (kf, locale_key, value);
1869 if (!g_path_is_absolute (value))
1870 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1871 "does not look like an absolute path\n",
1872 value, locale_key, kf->current_group);
1877 /* + Possible values are UTF-8 and Legacy-Mixed.
1881 handle_encoding_key (kf_validator *kf,
1882 const char *locale_key,
1885 if (!strcmp (value, "UTF-8") || !strcmp (value, "Legacy-Mixed"))
1888 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1889 "is not a registered encoding value (\"UTF-8\", and "
1890 "\"Legacy-Mixed\")\n",
1891 value, locale_key, kf->current_group);
1896 /* + See http://lists.freedesktop.org/archives/xdg/2007-January/007436.html
1897 * + Value is one of:
1899 * - unless-exists FILE
1900 * - DESKTOP-ENVIRONMENT-NAME [DESKTOP-SPECIFIC-TEST]
1901 * - other known conditions (GNOME3, GSettings, etc.)
1903 * + FILE must be a path to a filename, relative to $XDG_CONFIG_HOME.
1905 * + DESKTOP-ENVIRONMENT-NAME should be a registered value (in Desktop Menu
1906 * Specification) or start with "X-".
1908 * + [DESKTOP-SPECIFIC-TEST] is optional.
1912 handle_autostart_condition_key (kf_validator *kf,
1913 const char *locale_key,
1920 handle_key_for_application (kf, locale_key, value);
1924 condition = g_strdup (value);
1925 argument = g_utf8_strchr (condition, -1, ' ');
1928 /* make condition a 0-ended string */
1931 /* skip the space(s) */
1933 while (*argument == ' ') {
1938 if (!strcmp (condition, "if-exists") || !strcmp (condition, "unless-exists")) {
1939 if (!argument || argument[0] == '\0') {
1940 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1941 "does not contain a path to a file to test the "
1943 value, locale_key, kf->current_group);
1945 } else if (argument[0] == G_DIR_SEPARATOR) {
1946 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1947 "contains a path \"%s\" that is absolute, while it "
1948 "should be relative (to $XDG_CONFIG_HOME)\n",
1949 value, locale_key, kf->current_group, argument);
1951 } else if (argument[0] == '.' &&
1952 ((strlen (argument) == 2 &&
1953 argument[1] == '.') ||
1954 (strlen (argument) >= 3 &&
1955 argument[1] == '.' &&
1956 argument[2] == G_DIR_SEPARATOR))) {
1957 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1958 "contains a path \"%s\" that depends on the value "
1959 "of $XDG_CONFIG_HOME (\"..\" should be avoided)\n",
1960 value, locale_key, kf->current_group, argument);
1963 } else if (strncmp (condition, "X-", 2) == 0) {
1964 if (argument && argument[0] == '\0')
1965 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1966 "has trailing space(s)\n",
1967 value, locale_key, kf->current_group);
1972 /* Look if it's a registered AutostartCondition */
1974 for (i = 0; i < G_N_ELEMENTS (registered_autostart_condition); i++) {
1976 if (strcmp (condition, registered_autostart_condition[i].name) != 0)
1979 /* check if first argument is one of the expected ones */
1980 for (j = 0; registered_autostart_condition[i].first_arg[j] != NULL; j++) {
1981 const char *first = registered_autostart_condition[i].first_arg[j];
1982 char *after_first = argument;
1984 if (argument && !strncmp (argument, first, strlen (first))) {
1985 after_first += strlen (first);
1986 if (after_first[0] == '\0' || after_first[0] == ' ') {
1987 /* find next argument */
1988 argument = after_first;
1989 while (*argument == ' ')
1997 /* we've reached the end of a non-empty set of first arguments; this
1998 * means none of the possible first arguments was found */
1999 if (j != 0 && registered_autostart_condition[i].first_arg[j] == NULL) {
2002 output = g_string_new (registered_autostart_condition[i].first_arg[0]);
2003 for (j = 1; registered_autostart_condition[i].first_arg[j] != NULL; j++)
2004 g_string_append_printf (output, ", or %s",
2005 registered_autostart_condition[i].first_arg[j]);
2007 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2008 "does not contain a valid first argument for "
2009 "condition \"%s\"; valid first arguments are: %s\n",
2010 value, locale_key, kf->current_group,
2011 condition, output->str);
2014 g_string_free (output, TRUE);
2018 switch (registered_autostart_condition[i].additional_args) {
2020 if (argument && argument[0] != '\0') {
2021 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2022 "has too many arguments for condition \"%s\"\n",
2023 value, locale_key, kf->current_group, condition);
2029 /* we handle the "one argument" case specially, as spaces might be
2030 * normal there, and therefore we don't want to split the string
2031 * based on spaces */
2032 if (!argument || argument[0] == '\0') {
2033 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2034 "is missing a last argument for condition "
2036 value, locale_key, kf->current_group, condition);
2043 int argc_diff = -registered_autostart_condition[i].additional_args;
2045 while (argument && argument[0] != '\0') {
2047 argument = g_utf8_strchr (argument, -1, ' ');
2048 while (argument && *argument == ' ')
2052 if (argc_diff > 0) {
2053 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2054 "has %d too many arguments for condition "
2056 value, locale_key, kf->current_group,
2057 argc_diff, condition);
2059 } else if (argc_diff < 0) {
2060 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2061 "has %d too few arguments for condition "
2063 value, locale_key, kf->current_group,
2064 -argc_diff, condition);
2077 /* Now, if we didn't find condition in list of registered
2078 * AutostartCondition... */
2079 if (i == G_N_ELEMENTS (registered_autostart_condition)) {
2080 /* Accept conditions with same name as OnlyShowIn values */
2082 for (i = 0; i < G_N_ELEMENTS (show_in_registered); i++) {
2083 if (!strcmp (condition, show_in_registered[i]))
2087 if (i == G_N_ELEMENTS (show_in_registered)) {
2088 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2089 "contains an unregistered value \"%s\" for the "
2090 "condition; values extending the format should "
2091 "start with \"X-\"\n",
2092 value, locale_key, kf->current_group, condition);
2096 if (argument && argument[0] == '\0')
2097 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2098 "has trailing space(s)\n",
2099 value, locale_key, kf->current_group);
2109 handle_key_for_application (kf_validator *kf,
2110 const char *locale_key,
2113 kf->application_keys = g_list_append (kf->application_keys,
2114 g_strdup (locale_key));
2119 handle_key_for_link (kf_validator *kf,
2120 const char *locale_key,
2123 kf->link_keys = g_list_append (kf->link_keys,
2124 g_strdup (locale_key));
2129 handle_key_for_fsdevice (kf_validator *kf,
2130 const char *locale_key,
2133 kf->fsdevice_keys = g_list_append (kf->fsdevice_keys,
2134 g_strdup (locale_key));
2139 handle_key_for_mimetype (kf_validator *kf,
2140 const char *locale_key,
2143 kf->mimetype_keys = g_list_append (kf->mimetype_keys,
2144 g_strdup (locale_key));
2148 /* + Key names must contain only the characters A-Za-z0-9-.
2149 * Checked (through key_is_valid()).
2150 * + LOCALE must be of the form lang_COUNTRY.ENCODING@MODIFIER, where _COUNTRY,
2151 * .ENCODING, and @MODIFIER may be omitted.
2155 key_extract_locale (const char *key,
2159 const char *start_locale;
2169 start_locale = g_strrstr (key, "[");
2172 len = start_locale - key;
2176 if (!key_is_valid(key, len))
2179 if (!start_locale) {
2181 *real_key = g_strdup (key);
2188 len = strlen (start_locale);
2189 if (len <= 2 || start_locale[len - 1] != ']')
2192 /* ignore first [ and last ] */
2193 for (i = 1; i < len - 2; i++) {
2194 c = start_locale[i];
2195 if (!g_ascii_isalnum (c) && c != '-' && c != '_' && c != '.' && c != '@')
2200 *real_key = g_strndup (key, strlen (key) - len);
2202 *locale = g_strndup (start_locale + 1, len - 2);
2207 /* + All keys extending the format should start with "X-".
2211 validate_known_key (kf_validator *kf,
2212 const char *locale_key,
2216 DesktopKeyDefinition *keys,
2217 unsigned int n_keys)
2222 if (!strncmp (key, "X-", 2))
2225 for (i = 0; i < n_keys; i++) {
2226 if (strcmp (key, keys[i].name))
2229 if (keys[i].type != DESKTOP_LOCALESTRING_TYPE &&
2230 keys[i].type != DESKTOP_LOCALESTRING_LIST_TYPE &&
2232 print_fatal (kf, "file contains key \"%s\" in group \"%s\", "
2233 "but \"%s\" is not defined as a locale string\n",
2234 locale_key, kf->current_group, key);
2238 for (j = 0; j < G_N_ELEMENTS (validate_for_type); j++) {
2239 if (validate_for_type[j].type == keys[i].type)
2243 g_assert (j != G_N_ELEMENTS (validate_for_type));
2245 if (!kf->no_deprecated_warnings && keys[i].deprecated)
2246 print_warning (kf, "key \"%s\" in group \"%s\" is deprecated\n",
2247 locale_key, kf->current_group);
2249 if (keys[i].kde_reserved && kf->kde_reserved_warnings)
2250 print_warning (kf, "key \"%s\" in group \"%s\" is a reserved key for "
2252 locale_key, kf->current_group);
2254 if (!validate_for_type[j].validate (kf, key, locale, value))
2257 if (keys[i].handle_and_validate != NULL) {
2258 if (!keys[i].handle_and_validate (kf, locale_key, value))
2266 print_fatal (kf, "file contains key \"%s\" in group \"%s\", but "
2267 "keys extending the format should start with "
2268 "\"X-\"\n", key, kf->current_group);
2276 validate_desktop_key (kf_validator *kf,
2277 const char *locale_key,
2282 return validate_known_key (kf, locale_key, key, locale, value,
2283 registered_desktop_keys,
2284 G_N_ELEMENTS (registered_desktop_keys));
2288 validate_action_key (kf_validator *kf,
2289 const char *locale_key,
2294 return validate_known_key (kf, locale_key, key, locale, value,
2295 registered_action_keys,
2296 G_N_ELEMENTS (registered_action_keys));
2299 /* + Multiple keys in the same group may not have the same name.
2303 validate_keys_for_current_group (kf_validator *kf)
2305 gboolean desktop_group;
2306 gboolean action_group;
2308 GHashTable *duplicated_keys_hash;
2317 desktop_group = (!strcmp (kf->current_group, GROUP_DESKTOP_ENTRY) ||
2318 !strcmp (kf->current_group, GROUP_KDE_DESKTOP_ENTRY));
2319 action_group = (!strncmp (kf->current_group, GROUP_DESKTOP_ACTION,
2320 strlen (GROUP_DESKTOP_ACTION)));
2322 keys = g_slist_copy (g_hash_table_lookup (kf->groups, kf->current_group));
2323 /* keys were prepended, so reverse the list (that's why we use a
2325 keys = g_slist_reverse (keys);
2327 kf->current_keys = g_hash_table_new_full (g_str_hash, g_str_equal,
2329 duplicated_keys_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
2332 /* we need two passes: some checks are looking if another key exists in the
2334 for (sl = keys; sl != NULL; sl = sl->next) {
2335 kf_keyvalue *keyvalue;
2337 keyvalue = (kf_keyvalue *) sl->data;
2338 g_hash_table_insert (kf->current_keys, keyvalue->key, keyvalue);
2340 /* we could display the error about duplicate keys here, but it's better
2341 * to display it with the first occurence of this key */
2342 hashvalue = g_hash_table_lookup (duplicated_keys_hash, keyvalue->key);
2344 g_hash_table_insert (duplicated_keys_hash, keyvalue->key,
2345 GINT_TO_POINTER (1));
2347 g_hash_table_replace (duplicated_keys_hash, keyvalue->key,
2348 GINT_TO_POINTER (GPOINTER_TO_INT (hashvalue) + 1));
2352 for (sl = keys; sl != NULL; sl = sl->next) {
2353 kf_keyvalue *keyvalue;
2354 gboolean skip_desktop_check;
2356 keyvalue = (kf_keyvalue *) sl->data;
2358 skip_desktop_check = FALSE;
2360 if (!key_extract_locale (keyvalue->key, &key, &locale)) {
2361 print_fatal (kf, "file contains key \"%s\" in group \"%s\", but "
2362 "key names must contain only the characters "
2363 "A-Za-z0-9- (they may have a \"[LOCALE]\" postfix)\n",
2364 keyvalue->key, kf->current_group);
2366 skip_desktop_check = TRUE;
2368 key = g_strdup (keyvalue->key);
2371 g_assert (key != NULL);
2373 hashvalue = g_hash_table_lookup (duplicated_keys_hash, keyvalue->key);
2374 if (GPOINTER_TO_INT (hashvalue) > 1) {
2375 g_hash_table_remove (duplicated_keys_hash, keyvalue->key);
2376 print_fatal (kf, "file contains multiple keys named \"%s\" in "
2377 "group \"%s\"\n", keyvalue->key, kf->current_group);
2381 if (desktop_group && !skip_desktop_check) {
2382 if (!validate_desktop_key (kf, keyvalue->key,
2383 key, locale, keyvalue->value))
2385 } else if (action_group && !skip_desktop_check) {
2386 if (!validate_action_key (kf, keyvalue->key,
2387 key, locale, keyvalue->value))
2397 g_slist_free (keys);
2398 g_hash_table_destroy (duplicated_keys_hash);
2399 g_hash_table_destroy (kf->current_keys);
2400 kf->current_keys = NULL;
2401 /* Clear ShowIn flag, so that different groups can each have a OnlyShowIn /
2403 kf->show_in = FALSE;
2408 /* + Using [KDE Desktop Entry] instead of [Desktop Entry] as header is
2411 * + Group names may contain all ASCII characters except for [ and ] and
2412 * control characters.
2414 * + All groups extending the format should start with "X-".
2416 * + Accept "Desktop Action foobar" group, where foobar is a valid key
2420 * Note that for "Desktop Action foobar" group, we will check later on (in
2421 * validate_actions()) that the Actions key contains "foobar".
2424 validate_group_name (kf_validator *kf,
2430 for (i = 0; group[i] != '\0'; i++) {
2432 if (g_ascii_iscntrl (c) || c == '[' || c == ']') {
2433 print_fatal (kf, "file contains group \"%s\", but group names "
2434 "may contain all ASCII characters except for [ "
2435 "and ] and control characters\n", group);
2440 if (!strncmp (group, "X-", 2))
2443 if (!strcmp (group, GROUP_DESKTOP_ENTRY)) {
2444 if (kf->main_group && !strcmp (kf->main_group, GROUP_KDE_DESKTOP_ENTRY))
2445 print_warning (kf, "file contains groups \"%s\" and \"%s\", which play "
2447 GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY);
2449 kf->main_group = GROUP_DESKTOP_ENTRY;
2454 if (!strcmp (group, GROUP_KDE_DESKTOP_ENTRY)) {
2455 if (kf->kde_reserved_warnings || !kf->no_deprecated_warnings)
2456 print_warning (kf, "file contains group \"%s\", which is deprecated "
2457 "in favor of \"%s\"\n", group, GROUP_DESKTOP_ENTRY);
2459 if (kf->main_group && !strcmp (kf->main_group, GROUP_DESKTOP_ENTRY))
2460 print_warning (kf, "file contains groups \"%s\" and \"%s\", which play "
2462 GROUP_DESKTOP_ENTRY, GROUP_KDE_DESKTOP_ENTRY);
2464 kf->main_group = GROUP_KDE_DESKTOP_ENTRY;
2469 if (!strncmp (group, GROUP_DESKTOP_ACTION, strlen (GROUP_DESKTOP_ACTION))) {
2470 if (group[strlen (GROUP_DESKTOP_ACTION) - 1] == '\0') {
2471 print_fatal (kf, "file contains group \"%s\", which is an action "
2472 "group with no action name\n", group);
2477 action = g_strdup (group + strlen (GROUP_DESKTOP_ACTION));
2479 if (!key_is_valid (action, strlen (action))) {
2480 print_fatal (kf, "file contains group \"%s\", which has an invalid "
2481 "action identifier, only alphanumeric characters and "
2482 "'-' are allowed\n", group);
2487 g_hash_table_insert (kf->action_groups, action, action);
2493 print_fatal (kf, "file contains group \"%s\", but groups extending "
2494 "the format should start with \"X-\"\n", group);
2499 validate_required_keys (kf_validator *kf,
2500 const char *group_name,
2501 DesktopKeyDefinition *key_definitions,
2502 unsigned int n_keys)
2508 GHashTable *hashtable;
2512 hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
2513 keys = g_hash_table_lookup (kf->groups, group_name);
2515 for (sl = keys; sl != NULL; sl = sl->next) {
2516 kf_keyvalue *keyvalue;
2518 keyvalue = (kf_keyvalue *) sl->data;
2519 g_hash_table_insert (hashtable, keyvalue->key, keyvalue->key);
2522 for (i = 0; i < n_keys; i++) {
2523 if (key_definitions[i].required) {
2524 if (!g_hash_table_lookup (hashtable,
2525 key_definitions[i].name)) {
2526 print_fatal (kf, "required key \"%s\" in group \"%s\" is not "
2528 key_definitions[i].name, group_name);
2534 g_hash_table_destroy (hashtable);
2540 validate_required_desktop_keys (kf_validator *kf)
2542 return validate_required_keys (kf, kf->main_group,
2543 registered_desktop_keys,
2544 G_N_ELEMENTS (registered_desktop_keys));
2547 #define PRINT_ERROR_FOREACH_KEY(lower, real) \
2549 print_error_foreach_##lower##_key (const char *name, \
2552 print_fatal (kf, "key \"%s\" is present in group \"%s\", but the type is " \
2553 "\"%s\" while this key is only valid for type \"%s\"\n", \
2554 name, kf->main_group, kf->type_string, real); \
2557 PRINT_ERROR_FOREACH_KEY (application, "Application")
2558 PRINT_ERROR_FOREACH_KEY (link, "Link")
2559 PRINT_ERROR_FOREACH_KEY (fsdevice, "FSDevice")
2560 PRINT_ERROR_FOREACH_KEY (mimetype, "MimeType")
2563 validate_type_keys (kf_validator *kf)
2572 case APPLICATION_TYPE:
2573 g_list_foreach (kf->link_keys,
2574 (GFunc) print_error_foreach_link_key, kf);
2575 g_list_foreach (kf->fsdevice_keys,
2576 (GFunc) print_error_foreach_fsdevice_key, kf);
2577 g_list_foreach (kf->mimetype_keys,
2578 (GFunc) print_error_foreach_mimetype_key, kf);
2579 retval = (g_list_length (kf->link_keys) +
2580 g_list_length (kf->fsdevice_keys) +
2581 g_list_length (kf->mimetype_keys) == 0);
2584 g_list_foreach (kf->application_keys,
2585 (GFunc) print_error_foreach_application_key, kf);
2586 g_list_foreach (kf->fsdevice_keys,
2587 (GFunc) print_error_foreach_fsdevice_key, kf);
2588 g_list_foreach (kf->mimetype_keys,
2589 (GFunc) print_error_foreach_mimetype_key, kf);
2590 retval = (g_list_length (kf->application_keys) +
2591 g_list_length (kf->fsdevice_keys) +
2592 g_list_length (kf->mimetype_keys) == 0);
2594 case DIRECTORY_TYPE:
2596 case SERVICE_TYPE_TYPE:
2597 g_list_foreach (kf->application_keys,
2598 (GFunc) print_error_foreach_application_key, kf);
2599 g_list_foreach (kf->link_keys,
2600 (GFunc) print_error_foreach_link_key, kf);
2601 g_list_foreach (kf->fsdevice_keys,
2602 (GFunc) print_error_foreach_fsdevice_key, kf);
2603 g_list_foreach (kf->mimetype_keys,
2604 (GFunc) print_error_foreach_mimetype_key, kf);
2605 retval = (g_list_length (kf->application_keys) +
2606 g_list_length (kf->link_keys) +
2607 g_list_length (kf->fsdevice_keys) +
2608 g_list_length (kf->mimetype_keys) == 0);
2611 g_list_foreach (kf->application_keys,
2612 (GFunc) print_error_foreach_application_key, kf);
2613 g_list_foreach (kf->link_keys,
2614 (GFunc) print_error_foreach_link_key, kf);
2615 g_list_foreach (kf->mimetype_keys,
2616 (GFunc) print_error_foreach_mimetype_key, kf);
2617 retval = (g_list_length (kf->application_keys) +
2618 g_list_length (kf->link_keys) +
2619 g_list_length (kf->mimetype_keys) == 0);
2622 g_list_foreach (kf->application_keys,
2623 (GFunc) print_error_foreach_application_key, kf);
2624 g_list_foreach (kf->link_keys,
2625 (GFunc) print_error_foreach_link_key, kf);
2626 g_list_foreach (kf->fsdevice_keys,
2627 (GFunc) print_error_foreach_fsdevice_key, kf);
2628 retval = (g_list_length (kf->application_keys) +
2629 g_list_length (kf->link_keys) +
2630 g_list_length (kf->fsdevice_keys) == 0);
2633 g_assert_not_reached ();
2640 lookup_group_foreach_action (char *key,
2644 if (g_hash_table_lookup (kf->action_groups, key)) {
2647 group_name = g_strconcat (GROUP_DESKTOP_ACTION, key, NULL);
2648 validate_required_keys (kf, group_name,
2649 registered_action_keys,
2650 G_N_ELEMENTS (registered_action_keys));
2651 g_free (group_name);
2653 g_hash_table_remove (kf->action_groups, key);
2661 print_error_foreach_action (char *key,
2665 print_fatal (kf, "action \"%s\" is defined, but there is no matching "
2666 "\"%s%s\" group\n", key, GROUP_DESKTOP_ACTION, key);
2670 print_error_foreach_group (char *key,
2674 print_fatal (kf, "action group \"%s%s\" exists, but there is no matching "
2675 "action \"%s\"\n", GROUP_DESKTOP_ACTION, key, key);
2679 validate_actions (kf_validator *kf)
2681 g_hash_table_foreach_remove (kf->action_values,
2682 (GHRFunc) lookup_group_foreach_action, kf);
2684 g_hash_table_foreach (kf->action_values,
2685 (GHFunc) print_error_foreach_action, kf);
2687 g_hash_table_foreach (kf->action_groups,
2688 (GHFunc) print_error_foreach_group, kf);
2690 return (g_hash_table_size (kf->action_values) +
2691 g_hash_table_size (kf->action_groups) == 0);
2694 /* + These desktop entry files should have the extension .desktop.
2696 * + Desktop entries which describe how a directory is to be
2697 * formatted/displayed should be simply called .directory.
2699 * + Using .kdelnk instead of .desktop as the file extension is deprecated.
2701 * FIXME: we're not doing what the spec says wrt Directory.
2704 validate_filename (kf_validator *kf)
2706 if (kf->type == DIRECTORY_TYPE) {
2707 if (g_str_has_suffix (kf->filename, ".directory"))
2710 print_fatal (kf, "file is of type \"Directory\", but filename does not "
2711 "have a .directory extension\n");
2716 if (g_str_has_suffix (kf->filename, ".desktop"))
2719 if (g_str_has_suffix (kf->filename, ".kdelnk")) {
2720 if (kf->kde_reserved_warnings || !kf->no_deprecated_warnings)
2721 print_warning (kf, "filename has a .kdelnk extension, which is "
2722 "deprecated in favor of .desktop\n");
2726 print_fatal (kf, "filename does not have a .desktop extension\n");
2730 /* + Lines beginning with a # and blank lines are considered comments.
2734 validate_line_is_comment (kf_validator *kf,
2737 return (*line == '#' || *line == '\0');
2740 /* + A group header with name groupname is a line in the format: [groupname]
2742 * + Group names may contain all ASCII characters except for [ and ] and
2743 * control characters.
2744 * This is done in validate_group_name().
2747 validate_line_looks_like_group (kf_validator *kf,
2754 chomped = g_strdup (line);
2755 g_strchomp (chomped);
2757 result = (*chomped == '[' && chomped[strlen (chomped) - 1] == ']');
2759 if (result && strcmp (chomped, line))
2760 print_fatal (kf, "line \"%s\" ends with a space, but looks like a group. "
2761 "The validation will continue, with the trailing spaces "
2762 "ignored.\n", line);
2764 if (group && result)
2765 *group = g_strndup (chomped + 1, strlen (chomped) - 2);
2772 /* + Space before and after the equals sign should be ignored; the = sign is
2773 * the actual delimiter.
2777 validate_line_looks_like_entry (kf_validator *kf,
2784 p = g_utf8_strchr (line, -1, '=');
2789 /* key must be non-empty */
2794 *key = g_strndup (line, p - line);
2798 *value = g_strdup (p + 1);
2805 /* + Only comments are accepted before the first group.
2807 * + The first group should be "Desktop Entry".
2809 * + Multiple groups may not have the same name.
2813 validate_parse_line (kf_validator *kf)
2821 line = kf->parse_buffer->str;
2822 len = kf->parse_buffer->len;
2824 if (!kf->utf8_warning && !g_utf8_validate (line, len, NULL)) {
2825 print_warning (kf, "file contains lines that are not UTF-8 encoded. There "
2826 "is no guarantee the validator will correctly work.\n");
2827 kf->utf8_warning = TRUE;
2830 if (g_ascii_isspace (*line)) {
2831 print_fatal (kf, "line \"%s\" starts with a space. Comment, group and "
2832 "key-value lines should not start with a space. The "
2833 "validation will continue, with the leading spaces "
2834 "ignored.\n", line);
2835 while (g_ascii_isspace (*line))
2839 if (validate_line_is_comment (kf, line))
2843 if (validate_line_looks_like_group (kf, line, &group)) {
2844 if (!kf->current_group &&
2845 (strcmp (group, GROUP_DESKTOP_ENTRY) &&
2846 strcmp (group, GROUP_KDE_DESKTOP_ENTRY)))
2847 print_fatal (kf, "first group is not \"" GROUP_DESKTOP_ENTRY "\"\n");
2849 if (kf->current_group && strcmp (kf->current_group, group))
2850 validate_keys_for_current_group (kf);
2852 if (g_hash_table_lookup_extended (kf->groups, group, NULL, NULL)) {
2853 print_fatal (kf, "file contains multiple groups named \"%s\", but "
2854 "multiple groups may not have the same name\n", group);
2856 validate_group_name (kf, group);
2857 g_hash_table_insert (kf->groups, g_strdup (group), NULL);
2860 if (kf->current_group)
2861 g_free (kf->current_group);
2862 kf->current_group = group;
2869 if (validate_line_looks_like_entry (kf, line, &key, &value)) {
2870 if (kf->current_group) {
2872 kf_keyvalue *keyvalue;
2874 keyvalue = g_slice_new (kf_keyvalue);
2875 keyvalue->key = key;
2876 keyvalue->value = value;
2878 keys = g_hash_table_lookup (kf->groups, kf->current_group);
2879 keys = g_slist_prepend (keys, keyvalue);
2880 g_hash_table_replace (kf->groups, g_strdup (kf->current_group), keys);
2887 print_fatal (kf, "file contains entry \"%s\" before the first group, "
2888 "but only comments are accepted before the first "
2895 print_fatal (kf, "file contains line \"%s\", which is not a comment, "
2896 "a group or an entry\n", line);
2899 /* + Desktop entry files are encoded as lines of 8-bit characters separated by
2904 validate_parse_data (kf_validator *kf,
2910 for (i = 0; i < length; i++) {
2911 if (data[i] == '\n') {
2912 if (i > 0 && data[i - 1] == '\r') {
2913 g_string_erase (kf->parse_buffer, kf->parse_buffer->len - 1, 1);
2915 if (!kf->cr_error) {
2916 print_fatal (kf, "file contains at least one line ending with a "
2917 "carriage return before the line feed, while lines "
2918 "should only be separated by a line feed "
2919 "character. First such line is: \"%s\"\n",
2920 kf->parse_buffer->str);
2921 kf->cr_error = TRUE;
2925 if (kf->parse_buffer->len > 0) {
2926 validate_parse_line (kf);
2927 g_string_erase (kf->parse_buffer, 0, -1);
2930 } else if (data[i] == '\r') {
2931 if (!kf->cr_error) {
2932 print_fatal (kf, "file contains at least one line ending with a "
2933 "carriage return, while lines should only be "
2934 "separated by a line feed character. First such "
2935 "line is: \"%s\"\n", kf->parse_buffer->str);
2936 kf->cr_error = TRUE;
2942 g_string_append_c (kf->parse_buffer, data[i]);
2947 validate_flush_parse_buffer (kf_validator *kf)
2949 if (kf->parse_buffer->len > 0) {
2950 validate_parse_line (kf);
2951 g_string_erase (kf->parse_buffer, 0, -1);
2954 if (kf->current_group)
2955 validate_keys_for_current_group (kf);
2958 #define VALIDATE_READ_SIZE 4096
2960 validate_parse_from_fd (kf_validator *kf,
2964 struct stat stat_buf;
2965 char read_buf[VALIDATE_READ_SIZE];
2967 if (fstat (fd, &stat_buf) < 0) {
2968 print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
2972 if (!S_ISREG (stat_buf.st_mode)) {
2973 print_fatal (kf, "file is not a regular file\n");
2977 if (stat_buf.st_size == 0) {
2978 print_fatal (kf, "file is empty\n");
2984 bytes_read = read (fd, read_buf, VALIDATE_READ_SIZE);
2986 if (bytes_read == 0) /* End of File */
2989 if (bytes_read < 0) {
2990 if (errno == EINTR || errno == EAGAIN)
2993 /* let's validate what we already have */
2994 validate_flush_parse_buffer (kf);
2996 print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
3000 validate_parse_data (kf, read_buf, bytes_read);
3003 validate_flush_parse_buffer (kf);
3009 validate_load_and_parse (kf_validator *kf)
3014 fd = g_open (kf->filename, O_RDONLY, 0);
3017 print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
3021 ret = validate_parse_from_fd (kf, fd);
3029 groups_hashtable_free (gpointer key,
3036 list = (GSList *) value;
3037 for (sl = list; sl != NULL; sl = sl->next) {
3038 kf_keyvalue *keyvalue;
3040 keyvalue = (kf_keyvalue *) sl->data;
3041 g_free (keyvalue->key);
3042 g_free (keyvalue->value);
3043 g_slice_free (kf_keyvalue, keyvalue);
3046 g_slist_free (list);
3052 desktop_file_validate (const char *filename,
3054 gboolean no_warn_deprecated,
3059 /* just a consistency check */
3060 g_assert (G_N_ELEMENTS (registered_types) == LAST_TYPE - 1);
3062 kf.filename = filename;
3063 kf.parse_buffer = g_string_new ("");
3064 kf.utf8_warning = FALSE;
3065 kf.cr_error = FALSE;
3066 kf.current_group = NULL;
3067 kf.groups = g_hash_table_new_full (g_str_hash, g_str_equal,
3069 kf.current_keys = NULL;
3070 kf.kde_reserved_warnings = warn_kde;
3071 kf.no_deprecated_warnings = no_warn_deprecated;
3072 kf.no_hints = no_hints;
3074 kf.main_group = NULL;
3075 kf.type = INVALID_TYPE;
3076 kf.type_string = NULL;
3078 kf.application_keys = NULL;
3079 kf.link_keys = NULL;
3080 kf.fsdevice_keys = NULL;
3081 kf.mimetype_keys = NULL;
3082 kf.action_values = g_hash_table_new_full (g_str_hash, g_str_equal,
3084 kf.action_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
3086 kf.fatal_error = FALSE;
3088 validate_load_and_parse (&kf);
3089 //FIXME: this does not work well if there are both a Desktop Entry and a KDE
3090 //Desktop Entry groups since only the last one will be validated for this.
3091 if (kf.main_group) {
3092 validate_required_desktop_keys (&kf);
3093 validate_type_keys (&kf);
3095 validate_actions (&kf);
3096 validate_filename (&kf);
3098 g_list_foreach (kf.application_keys, (GFunc) g_free, NULL);
3099 g_list_free (kf.application_keys);
3100 g_list_foreach (kf.link_keys, (GFunc) g_free, NULL);
3101 g_list_free (kf.link_keys);
3102 g_list_foreach (kf.fsdevice_keys, (GFunc) g_free, NULL);
3103 g_list_free (kf.fsdevice_keys);
3104 g_list_foreach (kf.mimetype_keys, (GFunc) g_free, NULL);
3105 g_list_free (kf.mimetype_keys);
3107 g_hash_table_destroy (kf.action_values);
3108 g_hash_table_destroy (kf.action_groups);
3110 g_assert (kf.current_keys == NULL);
3111 /* we can't add an automatic destroy handler for the value because we replace
3112 * it when adding keys, and this means we'd have to copy the value each time
3114 g_hash_table_foreach_remove (kf.groups, groups_hashtable_free, NULL);
3115 g_hash_table_destroy (kf.groups);
3116 g_free (kf.current_group);
3117 g_string_free (kf.parse_buffer, TRUE);
3119 return (!kf.fatal_error);
3122 /* return FALSE if we were unable to fix the file */
3124 desktop_file_fixup (GKeyFile *keyfile,
3125 const char *filename)
3127 if (g_key_file_has_group (keyfile, GROUP_KDE_DESKTOP_ENTRY)) {
3128 g_printerr ("%s: warning: renaming deprecated \"%s\" group to \"%s\"\n",
3129 filename, GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY);
3130 dfu_key_file_rename_group (keyfile,
3131 GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY);