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;
136 validate_string_key (kf_validator *kf,
141 validate_localestring_key (kf_validator *kf,
146 validate_boolean_key (kf_validator *kf,
151 validate_numeric_key (kf_validator *kf,
156 validate_string_list_key (kf_validator *kf,
161 validate_regexp_list_key (kf_validator *kf,
166 validate_localestring_list_key (kf_validator *kf,
172 handle_type_key (kf_validator *kf,
173 const char *locale_key,
176 handle_version_key (kf_validator *kf,
177 const char *locale_key,
180 handle_comment_key (kf_validator *kf,
181 const char *locale_key,
184 handle_icon_key (kf_validator *kf,
185 const char *locale_key,
188 handle_show_in_key (kf_validator *kf,
189 const char *locale_key,
192 handle_desktop_exec_key (kf_validator *kf,
193 const char *locale_key,
196 handle_exec_key (kf_validator *kf,
197 const char *locale_key,
200 handle_path_key (kf_validator *kf,
201 const char *locale_key,
204 handle_mime_key (kf_validator *kf,
205 const char *locale_key,
208 handle_categories_key (kf_validator *kf,
209 const char *locale_key,
212 handle_actions_key (kf_validator *kf,
213 const char *locale_key,
216 handle_dbus_activatable_key (kf_validator *kf,
217 const char *locale_key,
220 handle_dev_key (kf_validator *kf,
221 const char *locale_key,
224 handle_mountpoint_key (kf_validator *kf,
225 const char *locale_key,
228 handle_encoding_key (kf_validator *kf,
229 const char *locale_key,
232 handle_autostart_condition_key (kf_validator *kf,
233 const char *locale_key,
236 handle_key_for_application (kf_validator *kf,
237 const char *locale_key,
240 handle_key_for_link (kf_validator *kf,
241 const char *locale_key,
244 handle_key_for_fsdevice (kf_validator *kf,
245 const char *locale_key,
248 handle_key_for_mimetype (kf_validator *kf,
249 const char *locale_key,
255 gboolean kde_reserved;
257 } registered_types[] = {
258 { APPLICATION_TYPE, "Application", FALSE, FALSE },
259 { LINK_TYPE, "Link", FALSE, FALSE },
260 { DIRECTORY_TYPE, "Directory", FALSE, FALSE },
261 { SERVICE_TYPE, "Service", TRUE, FALSE },
262 { SERVICE_TYPE_TYPE, "ServiceType", TRUE, FALSE },
263 { FSDEVICE_TYPE, "FSDevice", TRUE, FALSE },
264 { MIMETYPE_TYPE, "MimeType", FALSE, TRUE }
269 gboolean (* validate) (kf_validator *kf,
273 } validate_for_type[] = {
274 { DESKTOP_STRING_TYPE, validate_string_key },
275 { DESKTOP_LOCALESTRING_TYPE, validate_localestring_key },
276 { DESKTOP_BOOLEAN_TYPE, validate_boolean_key },
277 { DESKTOP_NUMERIC_TYPE, validate_numeric_key },
278 { DESKTOP_STRING_LIST_TYPE, validate_string_list_key },
279 { DESKTOP_REGEXP_LIST_TYPE, validate_regexp_list_key },
280 { DESKTOP_LOCALESTRING_LIST_TYPE, validate_localestring_list_key }
288 gboolean kde_reserved;
289 gboolean (* handle_and_validate) (kf_validator *kf,
290 const char *locale_key,
292 } DesktopKeyDefinition;
294 static DesktopKeyDefinition registered_desktop_keys[] = {
295 { DESKTOP_STRING_TYPE, "Type", TRUE, FALSE, FALSE, handle_type_key },
296 /* it is numeric according to the spec, but it's not true in previous
297 * versions of the spec. handle_version_key() will manage this */
298 { DESKTOP_STRING_TYPE, "Version", FALSE, FALSE, FALSE, handle_version_key },
299 { DESKTOP_LOCALESTRING_TYPE, "Name", TRUE, FALSE, FALSE, NULL },
300 { DESKTOP_LOCALESTRING_TYPE, "GenericName", FALSE, FALSE, FALSE, NULL },
301 { DESKTOP_BOOLEAN_TYPE, "NoDisplay", FALSE, FALSE, FALSE, NULL },
302 { DESKTOP_LOCALESTRING_TYPE, "Comment", FALSE, FALSE, FALSE, handle_comment_key },
303 { DESKTOP_LOCALESTRING_TYPE, "Icon", FALSE, FALSE, FALSE, handle_icon_key },
304 { DESKTOP_BOOLEAN_TYPE, "Hidden", FALSE, FALSE, FALSE, NULL },
305 { DESKTOP_STRING_LIST_TYPE, "OnlyShowIn", FALSE, FALSE, FALSE, handle_show_in_key },
306 { DESKTOP_STRING_LIST_TYPE, "NotShowIn", FALSE, FALSE, FALSE, handle_show_in_key },
307 { DESKTOP_STRING_TYPE, "TryExec", FALSE, FALSE, FALSE, handle_key_for_application },
308 { DESKTOP_STRING_TYPE, "Exec", FALSE, FALSE, FALSE, handle_desktop_exec_key },
309 { DESKTOP_STRING_TYPE, "Path", FALSE, FALSE, FALSE, handle_path_key },
310 { DESKTOP_BOOLEAN_TYPE, "Terminal", FALSE, FALSE, FALSE, handle_key_for_application },
311 { DESKTOP_STRING_LIST_TYPE, "MimeType", FALSE, FALSE, FALSE, handle_mime_key },
312 { DESKTOP_STRING_LIST_TYPE, "Categories", FALSE, FALSE, FALSE, handle_categories_key },
313 { DESKTOP_BOOLEAN_TYPE, "StartupNotify", FALSE, FALSE, FALSE, handle_key_for_application },
314 { DESKTOP_STRING_TYPE, "StartupWMClass", FALSE, FALSE, FALSE, handle_key_for_application },
315 { DESKTOP_STRING_TYPE, "URL", FALSE, FALSE, FALSE, handle_key_for_link },
316 /* since 1.1 (used to be a key reserved for KDE since 0.9.4) */
317 { DESKTOP_LOCALESTRING_LIST_TYPE, "Keywords", FALSE, FALSE, FALSE, NULL },
318 /* since 1.1 (used to be in the spec before 1.0, but was not really
320 { DESKTOP_STRING_LIST_TYPE, "Actions", FALSE, FALSE, FALSE, handle_actions_key },
322 { DESKTOP_STRING_LIST_TYPE, "Implements", FALSE, FALSE, FALSE, NULL },
324 { DESKTOP_BOOLEAN_TYPE, "DBusActivatable", FALSE, FALSE, FALSE, handle_dbus_activatable_key },
327 { DESKTOP_BOOLEAN_TYPE, "PrefersNonDefaultGPU", FALSE, FALSE, FALSE, NULL },
329 /* Keys reserved for KDE */
332 { DESKTOP_STRING_TYPE, "ServiceTypes", FALSE, FALSE, TRUE, NULL },
333 { DESKTOP_STRING_TYPE, "DocPath", FALSE, FALSE, TRUE, NULL },
334 { DESKTOP_STRING_TYPE, "InitialPreference", FALSE, FALSE, TRUE, NULL },
336 { DESKTOP_STRING_TYPE, "Dev", FALSE, FALSE, TRUE, handle_dev_key },
337 { DESKTOP_STRING_TYPE, "FSType", FALSE, FALSE, TRUE, handle_key_for_fsdevice },
338 { DESKTOP_STRING_TYPE, "MountPoint", FALSE, FALSE, TRUE, handle_mountpoint_key },
339 { DESKTOP_BOOLEAN_TYPE, "ReadOnly", FALSE, FALSE, TRUE, handle_key_for_fsdevice },
340 { DESKTOP_STRING_TYPE, "UnmountIcon", FALSE, FALSE, TRUE, handle_key_for_fsdevice },
342 /* Deprecated keys */
345 { DESKTOP_STRING_TYPE, "Protocols", FALSE, TRUE, FALSE, NULL },
346 { DESKTOP_STRING_TYPE, "Extensions", FALSE, TRUE, FALSE, NULL },
347 { DESKTOP_STRING_TYPE, "BinaryPattern", FALSE, TRUE, FALSE, NULL },
348 { DESKTOP_STRING_TYPE, "MapNotify", FALSE, TRUE, FALSE, NULL },
350 { DESKTOP_REGEXP_LIST_TYPE, "Patterns", FALSE, TRUE, FALSE, handle_key_for_mimetype },
351 { DESKTOP_STRING_TYPE, "DefaultApp", FALSE, TRUE, FALSE, handle_key_for_mimetype },
352 { DESKTOP_STRING_TYPE, "MiniIcon", FALSE, TRUE, FALSE, NULL },
353 { DESKTOP_STRING_TYPE, "TerminalOptions", FALSE, TRUE, FALSE, NULL },
355 { DESKTOP_STRING_TYPE, "Encoding", FALSE, TRUE, FALSE, handle_encoding_key },
356 { DESKTOP_LOCALESTRING_TYPE, "SwallowTitle", FALSE, TRUE, FALSE, NULL },
357 { DESKTOP_STRING_TYPE, "SwallowExec", FALSE, TRUE, FALSE, NULL },
359 { DESKTOP_STRING_LIST_TYPE, "SortOrder", FALSE, TRUE, FALSE, NULL },
360 { DESKTOP_REGEXP_LIST_TYPE, "FilePattern", FALSE, TRUE, FALSE, NULL },
362 { DESKTOP_BOOLEAN_TYPE, "X-KDE-RunOnDiscreteGpu", FALSE, TRUE, FALSE, NULL },
364 /* Keys from other specifications */
366 /* Autostart spec, currently proposed; adopted by GNOME */
367 { DESKTOP_STRING_TYPE, "AutostartCondition", FALSE, FALSE, FALSE, handle_autostart_condition_key }
370 static DesktopKeyDefinition registered_action_keys[] = {
371 { DESKTOP_LOCALESTRING_TYPE, "Name", TRUE, FALSE, FALSE, NULL },
372 { DESKTOP_LOCALESTRING_TYPE, "Icon", FALSE, FALSE, FALSE, handle_icon_key },
373 { DESKTOP_STRING_LIST_TYPE, "OnlyShowIn", FALSE, TRUE, FALSE, handle_show_in_key },
374 { DESKTOP_STRING_LIST_TYPE, "NotShowIn", FALSE, TRUE, FALSE, handle_show_in_key },
375 { DESKTOP_STRING_TYPE, "Exec", TRUE, FALSE, FALSE, handle_exec_key }
378 /* This should be the same list as in xdg-specs/menu/menu-spec.xml */
379 static const char *show_in_registered[] = {
380 "GNOME", "GNOME-Classic", "GNOME-Flashback", "KDE", "LXDE", "LXQt", "MATE", "Razor", "ROX", "TDE", "Unity", "XFCE", "EDE", "Cinnamon", "Pantheon", "Budgie", "Enlightenment", "Deepin", "Old"
385 const char *first_arg[3];
386 unsigned int additional_args;
387 } registered_autostart_condition[] = {
388 { "GNOME", { NULL }, 1 },
389 { "GNOME3", { "if-session", "unless-session", NULL }, 1},
390 { "GSettings", { NULL }, 2 }
396 gboolean require_only_show_in;
398 const char *requires[2];
399 const char *suggests[4];
400 } registered_categories[] = {
401 { "AudioVideo", TRUE, FALSE, FALSE, { NULL }, { NULL } },
402 { "Audio", TRUE, FALSE, FALSE, { "AudioVideo", NULL }, { NULL } },
403 { "Video", TRUE, FALSE, FALSE, { "AudioVideo", NULL }, { NULL } },
404 { "Development", TRUE, FALSE, FALSE, { NULL }, { NULL } },
405 { "Education", TRUE, FALSE, FALSE, { NULL }, { NULL } },
406 { "Game", TRUE, FALSE, FALSE, { NULL }, { NULL } },
407 { "Graphics", TRUE, FALSE, FALSE, { NULL }, { NULL } },
408 { "Network", TRUE, FALSE, FALSE, { NULL }, { NULL } },
409 { "Office", TRUE, FALSE, FALSE, { NULL }, { NULL } },
410 { "Science", TRUE, FALSE, FALSE, { NULL }, { NULL } },
411 { "Settings", TRUE, FALSE, FALSE, { NULL }, { NULL } },
412 { "System", TRUE, FALSE, FALSE, { NULL }, { NULL } },
413 { "Utility", TRUE, FALSE, FALSE, { NULL }, { NULL } },
414 { "Audio", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
415 { "Video", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
416 { "Building", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
417 { "Debugger", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
418 { "IDE", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
419 { "GUIDesigner", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
420 { "Profiling", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
421 { "RevisionControl", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
422 { "Translation", FALSE, FALSE, FALSE, { NULL }, { "Development", NULL } },
423 { "Calendar", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
424 { "ContactManagement", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
425 { "Database", FALSE, FALSE, FALSE, { NULL }, { "Office", "Development", "AudioVideo", NULL } },
426 { "Dictionary", FALSE, FALSE, FALSE, { NULL }, { "Office", "TextTools", NULL } },
427 { "Chart", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
428 { "Email", FALSE, FALSE, FALSE, { NULL }, { "Office", "Network", NULL } },
429 { "Finance", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
430 { "FlowChart", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
431 { "PDA", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
432 { "ProjectManagement", FALSE, FALSE, FALSE, { NULL }, { "Office", "Development", NULL } },
433 { "Presentation", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
434 { "Spreadsheet", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
435 { "WordProcessor", FALSE, FALSE, FALSE, { NULL }, { "Office", NULL } },
436 { "2DGraphics", FALSE, FALSE, FALSE, { NULL }, { "Graphics", NULL } },
437 { "VectorGraphics", FALSE, FALSE, FALSE, { NULL }, { "Graphics;2DGraphics", NULL } },
438 { "RasterGraphics", FALSE, FALSE, FALSE, { NULL }, { "Graphics;2DGraphics", NULL } },
439 { "3DGraphics", FALSE, FALSE, FALSE, { NULL }, { "Graphics", NULL } },
440 { "Scanning", FALSE, FALSE, FALSE, { NULL }, { "Graphics", NULL } },
441 { "OCR", FALSE, FALSE, FALSE, { NULL }, { "Graphics;Scanning", NULL } },
442 { "Photography", FALSE, FALSE, FALSE, { NULL }, { "Graphics", "Office", NULL } },
443 { "Publishing", FALSE, FALSE, FALSE, { NULL }, { "Graphics", "Office", NULL } },
444 { "Viewer", FALSE, FALSE, FALSE, { NULL }, { "Graphics", "Office", NULL } },
445 { "TextTools", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
446 { "DesktopSettings", FALSE, FALSE, FALSE, { NULL }, { "Settings", NULL } },
447 { "HardwareSettings", FALSE, FALSE, FALSE, { NULL }, { "Settings", NULL } },
448 { "Printing", FALSE, FALSE, FALSE, { NULL }, { "HardwareSettings;Settings", NULL } },
449 { "PackageManager", FALSE, FALSE, FALSE, { NULL }, { "Settings", NULL } },
450 { "Dialup", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
451 { "InstantMessaging", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
452 { "Chat", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
453 { "IRCClient", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
454 { "Feed", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
455 { "FileTransfer", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
456 { "HamRadio", FALSE, FALSE, FALSE, { NULL }, { "Network", "Audio", NULL } },
457 { "News", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
458 { "P2P", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
459 { "RemoteAccess", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
460 { "Telephony", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
461 { "TelephonyTools", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
462 { "VideoConference", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
463 { "WebBrowser", FALSE, FALSE, FALSE, { NULL }, { "Network", NULL } },
464 { "WebDevelopment", FALSE, FALSE, FALSE, { NULL }, { "Network", "Development", NULL } },
465 { "Midi", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
466 { "Mixer", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
467 { "Sequencer", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
468 { "Tuner", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Audio", NULL } },
469 { "TV", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo;Video", NULL } },
470 { "AudioVideoEditing", FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
471 { "Player", FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
472 { "Recorder", FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
473 { "DiscBurning", FALSE, FALSE, FALSE, { NULL }, { "Audio", "Video", "AudioVideo", NULL } },
474 { "ActionGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
475 { "AdventureGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
476 { "ArcadeGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
477 { "BoardGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
478 { "BlocksGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
479 { "CardGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
480 { "KidsGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
481 { "LogicGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
482 { "RolePlaying", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
483 { "Shooter", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
484 { "Simulation", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
485 { "SportsGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
486 { "StrategyGame", FALSE, FALSE, FALSE, { NULL }, { "Game", NULL } },
487 { "Art", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
488 { "Construction", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
489 { "Music", FALSE, FALSE, FALSE, { NULL }, { "AudioVideo", "Education", NULL } },
490 { "Languages", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
491 { "ArtificialIntelligence", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
492 { "Astronomy", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
493 { "Biology", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
494 { "Chemistry", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
495 { "ComputerScience", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
496 { "DataVisualization", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
497 { "Economy", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
498 { "Electricity", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
499 { "Geography", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
500 { "Geology", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
501 { "Geoscience", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
502 { "History", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
503 { "Humanities", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
504 { "ImageProcessing", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
505 { "Literature", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
506 { "Maps", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", "Utility", NULL } },
507 { "Math", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
508 { "NumericalAnalysis", FALSE, FALSE, FALSE, { NULL }, { "Education;Math", "Science;Math", NULL } },
509 { "MedicalSoftware", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
510 { "Physics", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
511 { "Robotics", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
512 { "Spirituality", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", "Utility", NULL } },
513 { "Sports", FALSE, FALSE, FALSE, { NULL }, { "Education", "Science", NULL } },
514 { "ParallelComputing", FALSE, FALSE, FALSE, { NULL }, { "Education;ComputerScience", "Science;ComputerScience", NULL } },
515 { "Amusement", FALSE, FALSE, FALSE, { NULL }, { NULL } },
516 { "Archiving", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
517 { "Compression", FALSE, FALSE, FALSE, { NULL }, { "Utility;Archiving", NULL } },
518 { "Electronics", FALSE, FALSE, FALSE, { NULL }, { NULL } },
519 { "Emulator", FALSE, FALSE, FALSE, { NULL }, { "System", "Game", NULL } },
520 { "Engineering", FALSE, FALSE, FALSE, { NULL }, { NULL } },
521 { "FileTools", FALSE, FALSE, FALSE, { NULL }, { "Utility", "System", NULL } },
522 { "FileManager", FALSE, FALSE, FALSE, { NULL }, { "System;FileTools", NULL } },
523 { "TerminalEmulator", FALSE, FALSE, FALSE, { NULL }, { "System", NULL } },
524 { "Filesystem", FALSE, FALSE, FALSE, { NULL }, { "System", NULL } },
525 { "Monitor", FALSE, FALSE, FALSE, { NULL }, { "System", "Network", NULL } },
526 { "Security", FALSE, FALSE, FALSE, { NULL }, { "Settings", "System", NULL } },
527 { "Accessibility", FALSE, FALSE, FALSE, { NULL }, { "Settings", "Utility", NULL } },
528 { "Calculator", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
529 { "Clock", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
530 { "TextEditor", FALSE, FALSE, FALSE, { NULL }, { "Utility", NULL } },
531 { "Documentation", FALSE, FALSE, FALSE, { NULL }, { NULL } },
532 { "Adult", FALSE, FALSE, FALSE, { NULL }, { NULL } },
533 { "Core", FALSE, FALSE, FALSE, { NULL }, { NULL } },
534 { "KDE", FALSE, FALSE, FALSE, { NULL }, { "Qt", NULL } },
535 { "GNOME", FALSE, FALSE, FALSE, { NULL }, { "GTK", NULL } },
536 { "XFCE", FALSE, FALSE, FALSE, { NULL }, { "GTK", NULL } },
537 { "GTK", FALSE, FALSE, FALSE, { NULL }, { NULL } },
538 { "Qt", FALSE, FALSE, FALSE, { NULL }, { NULL } },
539 { "Motif", FALSE, FALSE, FALSE, { NULL }, { NULL } },
540 { "Java", FALSE, FALSE, FALSE, { NULL }, { NULL } },
541 { "ConsoleOnly", FALSE, FALSE, FALSE, { NULL }, { NULL } },
542 { "Screensaver", FALSE, TRUE, FALSE, { NULL }, { NULL } },
543 { "TrayIcon", FALSE, TRUE, FALSE, { NULL }, { NULL } },
544 { "Applet", FALSE, TRUE, FALSE, { NULL }, { NULL } },
545 { "Shell", FALSE, TRUE, FALSE, { NULL }, { NULL } },
546 { "Application", FALSE, FALSE, TRUE, { NULL }, { NULL } },
547 { "Applications", FALSE, FALSE, TRUE, { NULL }, { NULL } }
550 /* Escape values for console colors */
551 #define UNDERLINE "\033[4m"
552 #define MAGENTA "\033[35m"
553 #define RED "\033[31m"
554 #define YELLOW "\033[33m"
556 /* Colour definitions */
557 #define RESET_COLOR (kf->use_colors ? "\033[0m" : "")
558 #define FILENAME_COLOR (kf->use_colors ? UNDERLINE : "")
559 #define FATAL_COLOR (kf->use_colors ? RED : "")
560 #define FUTURE_FATAL_COLOR (kf->use_colors ? RED : "")
561 #define WARNING_COLOR (kf->use_colors ? MAGENTA : "")
562 #define HINT_COLOR (kf->use_colors ? YELLOW : "")
565 print_fatal (kf_validator *kf, const char *format, ...)
570 g_return_if_fail (kf != NULL && format != NULL);
572 kf->fatal_error = TRUE;
574 va_start (args, format);
575 str = g_strdup_vprintf (format, args);
578 g_print ("%s%s%s: %serror%s: %s",
579 FILENAME_COLOR, kf->filename, RESET_COLOR,
580 FATAL_COLOR, RESET_COLOR, str);
586 print_future_fatal (kf_validator *kf, const char *format, ...)
591 g_return_if_fail (kf != NULL && format != NULL);
593 va_start (args, format);
594 str = g_strdup_vprintf (format, args);
597 g_print ("%s%s%s: %serror%s: (will be fatal in the future): %s",
598 FILENAME_COLOR, kf->filename, RESET_COLOR,
599 FUTURE_FATAL_COLOR, RESET_COLOR, str);
605 print_warning (kf_validator *kf, const char *format, ...)
610 g_return_if_fail (kf != NULL && format != NULL);
612 va_start (args, format);
613 str = g_strdup_vprintf (format, args);
616 g_print ("%s%s%s: %swarning%s: %s",
617 FILENAME_COLOR, kf->filename, RESET_COLOR,
618 WARNING_COLOR, RESET_COLOR, str);
624 print_hint (kf_validator *kf, const char *format, ...)
629 g_return_if_fail (kf != NULL && format != NULL);
634 va_start (args, format);
635 str = g_strdup_vprintf (format, args);
638 g_print ("%s%s%s: %shint%s: %s",
639 FILENAME_COLOR, kf->filename, RESET_COLOR,
640 HINT_COLOR, RESET_COLOR, str);
645 /* + Key names must contain only the characters A-Za-z0-9-.
649 key_is_valid (const char *key,
655 for (i = 0; i < len; i++) {
657 if (!g_ascii_isalnum (c) && c != '-')
664 /* + Values of type string may contain all ASCII characters except for control
669 validate_string_key (kf_validator *kf,
679 for (i = 0; value[i] != '\0'; i++) {
680 if (g_ascii_iscntrl (value[i])) {
687 print_fatal (kf, "value \"%s\" for string key \"%s\" in group \"%s\" "
688 "contains invalid characters, string values may contain "
689 "all ASCII characters except for control characters\n",
690 value, key, kf->current_group);
698 /* + Values of type localestring are user displayable, and are encoded in
701 * + If a postfixed key occurs, the same key must be also present without the
706 validate_localestring_key (kf_validator *kf,
714 locale_key = g_strdup_printf ("%s[%s]", key, locale);
716 locale_key = g_strdup_printf ("%s", key);
718 if (!g_utf8_validate (value, -1, NULL)) {
719 print_fatal (kf, "value \"%s\" for locale string key \"%s\" in group "
720 "\"%s\" contains invalid UTF-8 characters, locale string "
721 "values should be encoded in UTF-8\n",
722 value, locale_key, kf->current_group);
728 if (!g_hash_table_lookup (kf->current_keys, key)) {
729 print_fatal (kf, "key \"%s\" in group \"%s\" is a localized key, but "
730 "there is no non-localized key \"%s\"\n",
731 locale_key, kf->current_group, key);
742 /* + Values of type boolean must either be the string true or false.
744 * + Historically some booleans have been represented by the numeric entries 0
745 * or 1. With this version of the standard they are now to be represented as
746 * a boolean string. However, if an implementation is reading a pre-1.0
747 * desktop entry, it should interpret 0 and 1 as false and true,
752 validate_boolean_key (kf_validator *kf,
757 if (strcmp (value, "true") && strcmp (value, "false") &&
758 strcmp (value, "0") && strcmp (value, "1")) {
759 print_fatal (kf, "value \"%s\" for boolean key \"%s\" in group \"%s\" "
760 "contains invalid characters, boolean values must be "
761 "\"false\" or \"true\"\n",
762 value, key, kf->current_group);
766 if (!kf->no_deprecated_warnings &&
767 (!strcmp (value, "0") || !strcmp (value, "1")))
768 print_warning (kf, "boolean key \"%s\" in group \"%s\" has value \"%s\", "
769 "which is deprecated: boolean values should be "
770 "\"false\" or \"true\"\n",
771 key, kf->current_group, value);
776 /* + Values of type numeric must be a valid floating point number as recognized
777 * by the %f specifier for scanf.
781 validate_numeric_key (kf_validator *kf,
789 res = sscanf (value, "%f", &d);
791 print_fatal (kf, "value \"%s\" for numeric key \"%s\" in group \"%s\" "
792 "contains invalid characters, numeric values must be "
793 "valid floating point numbers\n",
794 value, key, kf->current_group);
801 /* + Values of type string may contain all ASCII characters except for control
804 * + FIXME: how should an empty list be handled?
807 validate_string_regexp_list_key (kf_validator *kf,
818 for (i = 0; value[i] != '\0'; i++) {
819 if (g_ascii_iscntrl (value[i])) {
826 print_fatal (kf, "value \"%s\" for %s list key \"%s\" in group \"%s\" "
827 "contains invalid character '%c', %s list values may "
828 "contain all ASCII characters except for control "
830 value, type, key, kf->current_group, value[i], type);
839 validate_string_list_key (kf_validator *kf,
844 return validate_string_regexp_list_key (kf, key, locale, value, "string");
848 validate_regexp_list_key (kf_validator *kf,
853 return validate_string_regexp_list_key (kf, key, locale, value, "regexp");
856 /* + Values of type localestring are user displayable, and are encoded in
858 * FIXME: partly checked; we checked the whole value is encored in UTF-8, but
859 * not that each value of the list is. Although this might be equivalent?
860 * + If a postfixed key occurs, the same key must be also present without the
863 * + FIXME: how should an empty list be handled?
866 validate_localestring_list_key (kf_validator *kf,
874 locale_key = g_strdup_printf ("%s[%s]", key, locale);
876 locale_key = g_strdup_printf ("%s", key);
879 if (!g_utf8_validate (value, -1, NULL)) {
880 print_fatal (kf, "value \"%s\" for locale string list key \"%s\" in group "
881 "\"%s\" contains invalid UTF-8 characters, locale string "
882 "list values should be encoded in UTF-8\n",
883 value, locale_key, kf->current_group);
889 if (!g_hash_table_lookup (kf->current_keys, key)) {
890 print_fatal (kf, "key \"%s\" in group \"%s\" is a localized key, but "
891 "there is no non-localized key \"%s\"\n",
892 locale_key, kf->current_group, key);
903 /* + This specification defines 3 types of desktop entries: Application
904 * (type 1), Link (type 2) and Directory (type 3). To allow the addition of
905 * new types in the future, implementations should ignore desktop entries
906 * with an unknown type.
908 * + KDE specific types: ServiceType, Service and FSDevice
912 handle_type_key (kf_validator *kf,
913 const char *locale_key,
918 for (i = 0; i < G_N_ELEMENTS (registered_types); i++) {
919 if (!strcmp (value, registered_types[i].name))
923 if (i == G_N_ELEMENTS (registered_types)) {
924 /* force the type, since the key might be present multiple times... */
925 kf->type = INVALID_TYPE;
927 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
928 "is not a registered type value (\"Application\", "
929 "\"Link\" and \"Directory\")\n",
930 value, locale_key, kf->current_group);
934 if (registered_types[i].kde_reserved && kf->kde_reserved_warnings)
935 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
936 "is a reserved value for KDE\n",
937 value, locale_key, kf->current_group);
939 if (registered_types[i].deprecated && !kf->no_deprecated_warnings)
940 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
942 value, locale_key, kf->current_group);
944 kf->type = registered_types[i].type;
945 kf->type_string = registered_types[i].name;
950 /* + Entries that confirm with this version of the specification should use
953 * + Previous versions of the spec: 0.9.x where 3 <= x <= 8
957 handle_version_key (kf_validator *kf,
958 const char *locale_key,
961 if (!strcmp (value, "1.4"))
964 if (!strcmp (value, "1.3"))
967 if (!strcmp (value, "1.2"))
970 if (!strcmp (value, "1.1"))
973 if (!strcmp (value, "1.0"))
976 if (!strncmp (value, "0.9.", strlen ("0.9."))) {
979 c = value[strlen ("0.9.")];
980 if ('3' <= c && c <= '8' && value[strlen ("0.9.") + 1] == '\0')
984 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
985 "is not a known version\n",
986 value, locale_key, kf->current_group);
990 /* + Tooltip for the entry, for example "View sites on the Internet", should
991 * not be redundant with Name or GenericName.
995 handle_comment_key (kf_validator *kf,
996 const char *locale_key,
999 char *locale_compare_key;
1000 kf_keyvalue *keyvalue;
1002 locale_compare_key = g_strdup_printf ("Name%s",
1003 locale_key + strlen ("Comment"));
1004 keyvalue = g_hash_table_lookup (kf->current_keys, locale_compare_key);
1005 g_free (locale_compare_key);
1007 if (keyvalue && g_ascii_strcasecmp (value, keyvalue->value) == 0) {
1008 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1009 "looks the same as that of key \"%s\"\n",
1010 value, locale_key, kf->current_group,
1015 locale_compare_key = g_strdup_printf ("GenericName%s",
1016 locale_key + strlen ("Comment"));
1017 keyvalue = g_hash_table_lookup (kf->current_keys, locale_compare_key);
1018 g_free (locale_compare_key);
1020 if (keyvalue && g_ascii_strcasecmp (value, keyvalue->value) == 0) {
1021 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1022 "looks the same as that of key \"%s\"\n",
1023 value, locale_key, kf->current_group,
1031 /* + If the name is an absolute path, the given file will be used.
1033 * + If the name is not an absolute path, the algorithm described in the Icon
1034 * Theme Specification will be used to locate the icon.
1036 * FIXME: add clarification to desktop entry spec that the name doesn't
1037 * contain an extension
1040 handle_icon_key (kf_validator *kf,
1041 const char *locale_key,
1044 if (g_path_is_absolute (value)) {
1045 if (g_str_has_suffix (value, "/")) {
1046 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" is an "
1047 "absolute path to a directory, instead of being an "
1048 "absolute path to an icon or an icon name\n",
1049 value, locale_key, kf->current_group);
1055 if (g_utf8_strchr (value, -1, G_DIR_SEPARATOR)) {
1056 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" looks like "
1057 "a relative path, instead of being an absolute path to "
1058 "an icon or an icon name\n",
1059 value, locale_key, kf->current_group);
1063 if (g_str_has_suffix (value, ".png") ||
1064 g_str_has_suffix (value, ".xpm") ||
1065 g_str_has_suffix (value, ".svg")) {
1066 print_future_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" is an "
1067 "icon name with an extension, but there should be "
1068 "no extension as described in the Icon Theme "
1069 "Specification if the value is not an absolute "
1071 value, locale_key, kf->current_group);
1078 /* + Only one of these keys, either OnlyShowIn or NotShowIn, may appear in a
1081 * + (for possible values see the Desktop Menu Specification)
1083 * FIXME: this is not perfect because it could fail if a new value with
1084 * a semicolon is registered.
1085 * + All values extending the format should start with "X-".
1087 * + FIXME: is this okay to have only ";"? (gnome-theme-installer.desktop does)
1090 handle_show_in_key (kf_validator *kf,
1091 const char *locale_key,
1096 GHashTable *hashtable;
1103 print_fatal (kf, "only one of \"OnlyShowIn\" and \"NotShowIn\" keys "
1104 "may appear in group \"%s\"\n",
1110 hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
1111 show = g_strsplit (value, ";", 0);
1113 for (i = 0; show[i]; i++) {
1114 /* since the value ends with a semicolon, we'll have an empty string
1116 if (*show[i] == '\0' && show[i + 1] == NULL)
1119 if (g_hash_table_lookup (hashtable, show[i])) {
1120 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1121 "contains \"%s\" more than once\n",
1122 value, locale_key, kf->current_group, show[i]);
1126 g_hash_table_insert (hashtable, show[i], show[i]);
1128 if (!strncmp (show[i], "X-", 2))
1131 for (j = 0; j < G_N_ELEMENTS (show_in_registered); j++) {
1132 if (!strcmp (show[i], show_in_registered[j]))
1136 if (j == G_N_ELEMENTS (show_in_registered)) {
1137 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1138 "contains an unregistered value \"%s\"; values "
1139 "extending the format should start with \"X-\"\n",
1140 value, locale_key, kf->current_group, show[i]);
1146 g_hash_table_destroy (hashtable);
1151 /* + A command line consists of an executable program optionally followed by
1152 * one or more arguments. The executable program can either be specified with
1153 * its full path or with the name of the executable only. If no full path is
1154 * provided the executable is looked up in the $PATH used by the desktop
1155 * environment. The name or path of the executable program may not contain
1156 * the equal sign ("=").
1158 * + Arguments are separated by a space.
1160 * + Arguments may be quoted in whole.
1162 * + If an argument contains a reserved character the argument must be quoted.
1164 * + The rules for quoting of arguments is also applicable to the executable
1165 * name or path of the executable program as provided.
1167 * + Quoting must be done by enclosing the argument between double quotes and
1168 * escaping the double quote character, backtick character ("`"), dollar sign
1169 * ("$") and backslash character ("\") by preceding it with an additional
1170 * backslash character. Implementations must undo quoting before expanding
1171 * field codes and before passing the argument to the executable program.
1172 * Reserved characters are space (" "), tab, newline, double quote, single
1173 * quote ("'"), backslash character ("\"), greater-than sign (">"), less-than
1174 * sign ("<"), tilde ("~"), vertical bar ("|"), ampersand ("&"), semicolon
1175 * (";"), dollar sign ("$"), asterisk ("*"), question mark ("?"), hash mark
1176 * ("#"), parenthesis ("(") and (")") and backtick character ("`").
1178 * + Note that the general escape rule for values of type string states that
1179 * the backslash character can be escaped as ("\\") as well and that this
1180 * escape rule is applied before the quoting rule. As such, to unambiguously
1181 * represent a literal backslash character in a quoted argument in a desktop
1182 * entry file requires the use of four successive backslash characters
1183 * ("\\\\"). Likewise, a literal dollar sign in a quoted argument in a
1184 * desktop entry file is unambiguously represented with ("\\$").
1186 * + Field codes consist of the percentage character ("%") followed by an alpha
1187 * character. Literal percentage characters must be escaped as %%.
1189 * + Command lines that contain a field code that is not listed in this
1190 * specification are invalid and must not be processed, in particular
1191 * implementations may not introduce support for field codes not listed in
1192 * this specification. Extensions, if any, should be introduced by means of a
1195 * + A command line may contain at most one %f, %u, %F or %U field code.
1197 * + The %F and %U field codes may only be used as an argument on their own.
1201 handle_exec_key (kf_validator *kf,
1202 const char *locale_key,
1219 #define PRINT_INVALID_IF_FLAG \
1221 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " \
1222 "contains an invalid field code \"%%%c\"\n", \
1223 value, locale_key, kf->current_group, *c); \
1232 /* quotes and escaped characters in quotes */
1234 PRINT_INVALID_IF_FLAG;
1242 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1243 "contains an escaped double quote (\\\\\") "
1244 "outside of a quote, but the double quote is "
1245 "a reserved character\n",
1246 value, locale_key, kf->current_group);
1255 PRINT_INVALID_IF_FLAG;
1258 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1259 "contains a non-escaped character '%c' in a "
1260 "quote, but it should be escaped with two "
1261 "backslashes (\"\\\\%c\")\n",
1262 value, locale_key, kf->current_group, *c, *c);
1267 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1268 "contains a reserved character '%c' outside of a "
1270 value, locale_key, kf->current_group, *c);
1275 PRINT_INVALID_IF_FLAG;
1277 /* Escape character immediately followed by \0? */
1278 if (*(c + 1) == '\0') {
1279 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1280 "ends in an incomplete escape sequence\n",
1281 value, locale_key, kf->current_group);
1287 if (*c == '\\' && in_quote)
1291 /* reserved characters */
1309 PRINT_INVALID_IF_FLAG;
1311 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1312 "contains a reserved character '%c' outside of a "
1314 value, locale_key, kf->current_group, *c);
1327 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1328 "may contain at most one \"%f\", \"%u\", "
1329 "\"%F\" or \"%U\" field code\n",
1330 value, locale_key, kf->current_group);
1342 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1343 "may contain at most one \"%f\", \"%u\", "
1344 "\"%F\" or \"%U\" field code\n",
1345 value, locale_key, kf->current_group);
1366 if (!kf->no_deprecated_warnings)
1367 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1368 "contains a deprecated field code \"%%%c\"\n",
1369 value, locale_key, kf->current_group, *c);
1375 PRINT_INVALID_IF_FLAG;
1383 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" contains a "
1384 "quote which is not closed\n",
1385 value, locale_key, kf->current_group);
1390 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" contains a "
1391 "non-complete field code\n",
1392 value, locale_key, kf->current_group);
1399 /* See checks for handle_exec_key().
1402 handle_desktop_exec_key (kf_validator *kf,
1403 const char *locale_key,
1406 handle_key_for_application (kf, locale_key, value);
1408 return handle_exec_key (kf, locale_key, value);
1411 /* + If entry is of type Application, the working directory to run the program
1412 * in. (probably implies an absolute path)
1414 * + FIXME: is it okay to have an empty string here? (wireshark.desktop does)
1417 handle_path_key (kf_validator *kf,
1418 const char *locale_key,
1421 handle_key_for_application (kf, locale_key, value);
1423 if (!g_path_is_absolute (value))
1424 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1425 "does not look like an absolute path\n",
1426 value, locale_key, kf->current_group);
1431 /* + The MIME type(s) supported by this application. Check they are valid
1436 handle_mime_key (kf_validator *kf,
1437 const char *locale_key,
1442 GHashTable *hashtable;
1445 MimeUtilsValidity valid;
1447 handle_key_for_application (kf, locale_key, value);
1451 hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
1452 types = g_strsplit (value, ";", 0);
1454 for (i = 0; types[i]; i++) {
1455 /* since the value ends with a semicolon, we'll have an empty string
1457 if (*types[i] == '\0' && types[i + 1] == NULL)
1460 if (g_hash_table_lookup (hashtable, types[i])) {
1461 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1462 "contains \"%s\" more than once\n",
1463 value, locale_key, kf->current_group, types[i]);
1467 g_hash_table_insert (hashtable, types[i], types[i]);
1469 valid = mu_mime_type_is_valid (types[i], &valid_error);
1473 case MU_DISCOURAGED:
1474 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1475 "contains value \"%s\" which is a MIME type that "
1476 "should probably not be used: %s\n",
1477 value, locale_key, kf->current_group,
1478 types[i], valid_error);
1480 g_free (valid_error);
1483 print_future_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1484 "contains value \"%s\" which is an invalid "
1486 value, locale_key, kf->current_group,
1487 types[i], valid_error);
1490 g_free (valid_error);
1493 g_assert_not_reached ();
1498 g_hash_table_destroy (hashtable);
1503 /* + FIXME: are there restrictions on how a category should be named?
1504 * + Categories in which the entry should be shown in a menu (for possible
1505 * values see the Desktop Menu Specification).
1507 * + The table below describes Reserved Categories. Reserved Categories have a
1508 * specific desktop specific meaning that has not been standardized (yet).
1509 * Desktop entry files that use a reserved category MUST also include an
1510 * appropriate OnlyShowIn= entry to restrict themselves to those environments
1511 * that properly support the reserved category as used.
1513 * + Accept "Application" as a deprecated category.
1515 * FIXME: it's not really deprecated, so the error message is wrong
1516 * + All categories extending the format should start with "X-".
1518 * + Using multiple main categories may lead to appearing more than once in
1521 * + One main category should be included, otherwise application will appear in
1522 * "catch-all" section of application menu.
1524 * FIXME: decide if it's okay to have an empty list of categories.
1525 * + Some categories, if included, require that another category is included.
1526 * Eg: if Audio is there, AudioVideo must be there.
1528 * + Some categories, if included, suggest that another category is included.
1529 * Eg: Debugger suggests Development.
1530 * This is the case for most additional categories.
1534 handle_categories_key (kf_validator *kf,
1535 const char *locale_key,
1540 GHashTable *hashtable;
1543 int main_categories_nb;
1545 handle_key_for_application (kf, locale_key, value);
1549 /* accept empty value as valid: this is like having no category at all */
1550 if (value[0] == '\0')
1553 hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
1554 categories = g_strsplit (value, ";", 0);
1556 /* this is a two-pass check: we first put the categories in a hash table so
1557 * that they are easy-to-find, and we then do many checks */
1560 for (i = 0; categories[i]; i++) {
1561 /* since the value ends with a semicolon, we'll have an empty string
1563 if (*categories[i] == '\0' && categories[i + 1] == NULL)
1566 if (g_hash_table_lookup (hashtable, categories[i])) {
1567 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1568 "contains \"%s\" more than once\n",
1569 value, locale_key, kf->current_group, categories[i]);
1573 g_hash_table_insert (hashtable, categories[i], categories[i]);
1577 main_categories_nb = 0;
1579 for (i = 0; categories[i]; i++) {
1582 /* since the value ends with a semicolon, we'll have an empty string
1584 if (*categories[i] == '\0' && categories[i + 1] == NULL)
1587 if (!strncmp (categories[i], "X-", 2))
1590 for (j = 0; j < G_N_ELEMENTS (registered_categories); j++) {
1591 if (!strcmp (categories[i], registered_categories[j].name))
1595 if (j == G_N_ELEMENTS (registered_categories)) {
1596 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1597 "contains an unregistered value \"%s\"; values "
1598 "extending the format should start with \"X-\"\n",
1599 value, locale_key, kf->current_group, categories[i]);
1604 if (registered_categories[j].main) {
1605 /* only count it as a main category if none of the required categories
1606 * for this one is also a main category (and is present) */
1607 gboolean required_main_category_present = FALSE;
1609 for (k = 0; registered_categories[j].requires[k] != NULL; k++) {
1610 char **required_categories;
1613 required_categories = g_strsplit (registered_categories[j].requires[k],
1616 for (l = 0; required_categories[l]; l++) {
1619 if (!g_hash_table_lookup (hashtable, required_categories[l]))
1622 for (m = 0; m < G_N_ELEMENTS (registered_categories); m++) {
1623 if (strcmp (required_categories[l],
1624 registered_categories[m].name) != 0)
1627 if (registered_categories[m].main)
1628 required_main_category_present = TRUE;
1633 if (required_main_category_present)
1637 if (required_main_category_present) {
1638 g_strfreev (required_categories);
1642 g_strfreev (required_categories);
1645 if (!required_main_category_present)
1646 main_categories_nb++;
1649 if (registered_categories[j].main && main_categories_nb > 1)
1650 print_hint (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1651 "contains more than one main category; application "
1652 "might appear more than once in the application menu\n",
1653 value, locale_key, kf->current_group);
1656 if (registered_categories[j].deprecated) {
1657 if (!kf->no_deprecated_warnings)
1658 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1659 "contains a deprecated value \"%s\"\n",
1660 value, locale_key, kf->current_group,
1664 if (registered_categories[j].require_only_show_in) {
1665 if (!g_hash_table_lookup (kf->current_keys, "OnlyShowIn")) {
1666 print_fatal (kf, "value item \"%s\" in key \"%s\" in group \"%s\" "
1667 "is a reserved category, so a \"OnlyShowIn\" key "
1668 "must be included\n",
1669 categories[i], locale_key, kf->current_group);
1674 /* required categories */
1676 for (k = 0; registered_categories[j].requires[k] != NULL; k++) {
1677 char **required_categories;
1680 required_categories = g_strsplit (registered_categories[j].requires[k],
1683 for (l = 0; required_categories[l]; l++) {
1684 if (!g_hash_table_lookup (hashtable, required_categories[l]))
1688 /* we've reached the end of a list of required categories, so
1689 * the condition is satisfied */
1690 if (required_categories[l] == NULL) {
1691 g_strfreev (required_categories);
1695 g_strfreev (required_categories);
1698 /* we've reached the end of a non-empty set of required categories; this
1699 * means none of the possible required category (or list of required
1700 * categories) was found */
1701 if (k != 0 && registered_categories[j].requires[k] == NULL) {
1702 GString *output_required;
1704 output_required = g_string_new (registered_categories[j].requires[0]);
1705 for (k = 1; registered_categories[j].requires[k] != NULL; k++)
1706 g_string_append_printf (output_required, ", or %s",
1707 registered_categories[j].requires[k]);
1709 print_future_fatal (kf, "value item \"%s\" in key \"%s\" in group \"%s\" "
1710 "requires another category to be present among "
1711 "the following categories: %s\n",
1712 categories[i], locale_key, kf->current_group,
1713 output_required->str);
1715 g_string_free (output_required, TRUE);
1719 /* suggested categories */
1721 for (k = 0; registered_categories[j].suggests[k] != NULL; k++) {
1722 char **suggested_categories;
1725 suggested_categories = g_strsplit (registered_categories[j].suggests[k],
1728 for (l = 0; suggested_categories[l]; l++) {
1729 if (!g_hash_table_lookup (hashtable, suggested_categories[l]))
1733 /* we've reached the end of a list of suggested categories, so
1734 * the condition is satisfied */
1735 if (suggested_categories[l] == NULL) {
1736 g_strfreev (suggested_categories);
1740 g_strfreev (suggested_categories);
1743 /* we've reached the end of a non-empty set of suggested categories; this
1744 * means none of the possible suggested category (or list of suggested
1745 * categories) was found */
1746 if (k != 0 && registered_categories[j].suggests[k] == NULL) {
1747 GString *output_suggested;
1749 output_suggested = g_string_new (registered_categories[j].suggests[0]);
1750 for (k = 1; registered_categories[j].suggests[k] != NULL; k++)
1751 g_string_append_printf (output_suggested, ", or %s",
1752 registered_categories[j].suggests[k]);
1754 print_hint (kf, "value item \"%s\" in key \"%s\" in group \"%s\" "
1755 "can be extended with another category among the "
1756 "following categories: %s\n",
1757 categories[i], locale_key, kf->current_group,
1758 output_suggested->str);
1760 g_string_free (output_suggested, TRUE);
1765 g_strfreev (categories);
1766 g_hash_table_destroy (hashtable);
1768 g_assert (main_categories_nb >= 0);
1770 if (main_categories_nb == 0)
1771 print_hint (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1772 "does not contain a registered main category; application "
1773 "might only show up in a \"catch-all\" section of the "
1774 "application menu\n",
1775 value, locale_key, kf->current_group);
1780 /* + Identifiers for application actions. Check they are using a valid format.
1783 * Note that we will check later on (in * validate_actions()) that there is a
1784 * "Desktop Action foobar" group for each "foobar" identifier.
1787 handle_actions_key (kf_validator *kf,
1788 const char *locale_key,
1796 handle_key_for_application (kf, locale_key, value);
1799 actions = g_strsplit (value, ";", 0);
1801 for (i = 0; actions[i]; i++) {
1802 /* since the value ends with a semicolon, we'll have an empty string
1804 if (*actions[i] == '\0') {
1805 if (actions[i + 1] != NULL) {
1806 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1807 "contains an empty action\n",
1808 value, locale_key, kf->current_group);
1816 if (g_hash_table_lookup (kf->action_values, actions[i])) {
1817 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1818 "contains action \"%s\" more than once\n",
1819 value, locale_key, kf->current_group, actions[i]);
1823 if (!key_is_valid (actions[i], strlen (actions[i]))) {
1824 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1825 "contains invalid action identifier \"%s\", only "
1826 "alphanumeric characters and '-' are allowed\n",
1827 value, locale_key, kf->current_group, actions[i]);
1832 action = g_strdup (actions[i]);
1833 g_hash_table_insert (kf->action_values, action, action);
1836 g_strfreev (actions);
1841 /* + If the file describes a D-Bus activatable service, the filename must be in
1842 * reverse-DNS notation, i.e. contain at least two dots including the dot
1847 handle_dbus_activatable_key (kf_validator *kf,
1848 const char *locale_key,
1851 gchar *basename_utf8;
1853 const gchar *p = NULL;
1854 gboolean retval = TRUE;
1856 /* If DBusActivatable=false, don't check */
1857 if (strcmp (value, "true") && strcmp (value, "1"))
1860 basename = g_path_get_basename (kf->filename);
1861 basename_utf8 = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL);
1865 p = g_utf8_strchr (basename_utf8, -1, '.');
1868 p = g_utf8_strchr (p + 1, -1, '.');
1872 print_fatal (kf, "DBusActivatable filename must conform to reverse-DNS notation\n");
1876 g_free (basename_utf8);
1881 /* + The device to mount. (probably implies an absolute path)
1885 handle_dev_key (kf_validator *kf,
1886 const char *locale_key,
1889 handle_key_for_fsdevice (kf, locale_key, value);
1891 if (!g_path_is_absolute (value))
1892 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1893 "does not look like an absolute path\n",
1894 value, locale_key, kf->current_group);
1899 /* + The mount point of the device in question. (probably implies an absolute
1904 handle_mountpoint_key (kf_validator *kf,
1905 const char *locale_key,
1908 handle_key_for_fsdevice (kf, locale_key, value);
1910 if (!g_path_is_absolute (value))
1911 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1912 "does not look like an absolute path\n",
1913 value, locale_key, kf->current_group);
1918 /* + Possible values are UTF-8 and Legacy-Mixed.
1922 handle_encoding_key (kf_validator *kf,
1923 const char *locale_key,
1926 if (!strcmp (value, "UTF-8") || !strcmp (value, "Legacy-Mixed"))
1929 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1930 "is not a registered encoding value (\"UTF-8\", and "
1931 "\"Legacy-Mixed\")\n",
1932 value, locale_key, kf->current_group);
1937 /* + See http://lists.freedesktop.org/archives/xdg/2007-January/007436.html
1938 * + Value is one of:
1940 * - unless-exists FILE
1941 * - DESKTOP-ENVIRONMENT-NAME [DESKTOP-SPECIFIC-TEST]
1942 * - other known conditions (GNOME3, GSettings, etc.)
1944 * + FILE must be a path to a filename, relative to $XDG_CONFIG_HOME.
1946 * + DESKTOP-ENVIRONMENT-NAME should be a registered value (in Desktop Menu
1947 * Specification) or start with "X-".
1949 * + [DESKTOP-SPECIFIC-TEST] is optional.
1953 handle_autostart_condition_key (kf_validator *kf,
1954 const char *locale_key,
1961 handle_key_for_application (kf, locale_key, value);
1965 condition = g_strdup (value);
1966 argument = g_utf8_strchr (condition, -1, ' ');
1969 /* make condition a 0-ended string */
1972 /* skip the space(s) */
1974 while (*argument == ' ') {
1979 if (!strcmp (condition, "if-exists") || !strcmp (condition, "unless-exists")) {
1980 if (!argument || argument[0] == '\0') {
1981 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1982 "does not contain a path to a file to test the "
1984 value, locale_key, kf->current_group);
1986 } else if (argument[0] == G_DIR_SEPARATOR) {
1987 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1988 "contains a path \"%s\" that is absolute, while it "
1989 "should be relative (to $XDG_CONFIG_HOME)\n",
1990 value, locale_key, kf->current_group, argument);
1992 } else if (argument[0] == '.' &&
1993 ((strlen (argument) == 2 &&
1994 argument[1] == '.') ||
1995 (strlen (argument) >= 3 &&
1996 argument[1] == '.' &&
1997 argument[2] == G_DIR_SEPARATOR))) {
1998 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
1999 "contains a path \"%s\" that depends on the value "
2000 "of $XDG_CONFIG_HOME (\"..\" should be avoided)\n",
2001 value, locale_key, kf->current_group, argument);
2004 } else if (strncmp (condition, "X-", 2) == 0) {
2005 if (argument && argument[0] == '\0')
2006 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2007 "has trailing space(s)\n",
2008 value, locale_key, kf->current_group);
2013 /* Look if it's a registered AutostartCondition */
2015 for (i = 0; i < G_N_ELEMENTS (registered_autostart_condition); i++) {
2017 if (strcmp (condition, registered_autostart_condition[i].name) != 0)
2020 /* check if first argument is one of the expected ones */
2021 for (j = 0; registered_autostart_condition[i].first_arg[j] != NULL; j++) {
2022 const char *first = registered_autostart_condition[i].first_arg[j];
2023 char *after_first = argument;
2025 if (argument && !strncmp (argument, first, strlen (first))) {
2026 after_first += strlen (first);
2027 if (after_first[0] == '\0' || after_first[0] == ' ') {
2028 /* find next argument */
2029 argument = after_first;
2030 while (*argument == ' ')
2038 /* we've reached the end of a non-empty set of first arguments; this
2039 * means none of the possible first arguments was found */
2040 if (j != 0 && registered_autostart_condition[i].first_arg[j] == NULL) {
2043 output = g_string_new (registered_autostart_condition[i].first_arg[0]);
2044 for (j = 1; registered_autostart_condition[i].first_arg[j] != NULL; j++)
2045 g_string_append_printf (output, ", or %s",
2046 registered_autostart_condition[i].first_arg[j]);
2048 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2049 "does not contain a valid first argument for "
2050 "condition \"%s\"; valid first arguments are: %s\n",
2051 value, locale_key, kf->current_group,
2052 condition, output->str);
2055 g_string_free (output, TRUE);
2059 switch (registered_autostart_condition[i].additional_args) {
2061 if (argument && argument[0] != '\0') {
2062 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2063 "has too many arguments for condition \"%s\"\n",
2064 value, locale_key, kf->current_group, condition);
2070 /* we handle the "one argument" case specially, as spaces might be
2071 * normal there, and therefore we don't want to split the string
2072 * based on spaces */
2073 if (!argument || argument[0] == '\0') {
2074 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2075 "is missing a last argument for condition "
2077 value, locale_key, kf->current_group, condition);
2084 int argc_diff = -registered_autostart_condition[i].additional_args;
2086 while (argument && argument[0] != '\0') {
2088 argument = g_utf8_strchr (argument, -1, ' ');
2089 while (argument && *argument == ' ')
2093 if (argc_diff > 0) {
2094 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2095 "has %d too many arguments for condition "
2097 value, locale_key, kf->current_group,
2098 argc_diff, condition);
2100 } else if (argc_diff < 0) {
2101 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2102 "has %d too few arguments for condition "
2104 value, locale_key, kf->current_group,
2105 -argc_diff, condition);
2118 /* Now, if we didn't find condition in list of registered
2119 * AutostartCondition... */
2120 if (i == G_N_ELEMENTS (registered_autostart_condition)) {
2121 /* Accept conditions with same name as OnlyShowIn values */
2123 for (i = 0; i < G_N_ELEMENTS (show_in_registered); i++) {
2124 if (!strcmp (condition, show_in_registered[i]))
2128 if (i == G_N_ELEMENTS (show_in_registered)) {
2129 print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2130 "contains an unregistered value \"%s\" for the "
2131 "condition; values extending the format should "
2132 "start with \"X-\"\n",
2133 value, locale_key, kf->current_group, condition);
2137 if (argument && argument[0] == '\0')
2138 print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
2139 "has trailing space(s)\n",
2140 value, locale_key, kf->current_group);
2150 handle_key_for_application (kf_validator *kf,
2151 const char *locale_key,
2154 kf->application_keys = g_list_append (kf->application_keys,
2155 g_strdup (locale_key));
2160 handle_key_for_link (kf_validator *kf,
2161 const char *locale_key,
2164 kf->link_keys = g_list_append (kf->link_keys,
2165 g_strdup (locale_key));
2170 handle_key_for_fsdevice (kf_validator *kf,
2171 const char *locale_key,
2174 kf->fsdevice_keys = g_list_append (kf->fsdevice_keys,
2175 g_strdup (locale_key));
2180 handle_key_for_mimetype (kf_validator *kf,
2181 const char *locale_key,
2184 kf->mimetype_keys = g_list_append (kf->mimetype_keys,
2185 g_strdup (locale_key));
2189 /* + Key names must contain only the characters A-Za-z0-9-.
2190 * Checked (through key_is_valid()).
2191 * + LOCALE must be of the form lang_COUNTRY.ENCODING@MODIFIER, where _COUNTRY,
2192 * .ENCODING, and @MODIFIER may be omitted.
2196 key_extract_locale (const char *key,
2200 const char *start_locale;
2210 start_locale = g_strrstr (key, "[");
2213 len = start_locale - key;
2217 if (!key_is_valid(key, len))
2220 if (!start_locale) {
2222 *real_key = g_strdup (key);
2229 len = strlen (start_locale);
2230 if (len <= 2 || start_locale[len - 1] != ']')
2233 /* ignore first [ and last ] */
2234 for (i = 1; i < len - 2; i++) {
2235 c = start_locale[i];
2236 if (!g_ascii_isalnum (c) && c != '-' && c != '_' && c != '.' && c != '@')
2241 *real_key = g_strndup (key, strlen (key) - len);
2243 *locale = g_strndup (start_locale + 1, len - 2);
2248 /* + All keys extending the format should start with "X-".
2252 validate_known_key (kf_validator *kf,
2253 const char *locale_key,
2257 DesktopKeyDefinition *keys,
2258 unsigned int n_keys)
2263 for (i = 0; i < n_keys; i++) {
2264 if (strcmp (key, keys[i].name))
2267 if (keys[i].type != DESKTOP_LOCALESTRING_TYPE &&
2268 keys[i].type != DESKTOP_LOCALESTRING_LIST_TYPE &&
2270 if (!strncmp (key, "X-", 2))
2272 print_fatal (kf, "file contains key \"%s\" in group \"%s\", "
2273 "but \"%s\" is not defined as a locale string\n",
2274 locale_key, kf->current_group, key);
2278 for (j = 0; j < G_N_ELEMENTS (validate_for_type); j++) {
2279 if (validate_for_type[j].type == keys[i].type)
2283 g_assert (j != G_N_ELEMENTS (validate_for_type));
2285 if (!kf->no_deprecated_warnings && keys[i].deprecated)
2286 print_warning (kf, "key \"%s\" in group \"%s\" is deprecated\n",
2287 locale_key, kf->current_group);
2289 if (keys[i].kde_reserved && kf->kde_reserved_warnings)
2290 print_warning (kf, "key \"%s\" in group \"%s\" is a reserved key for "
2292 locale_key, kf->current_group);
2294 if (!strncmp (key, "X-", 2))
2297 if (!validate_for_type[j].validate (kf, key, locale, value))
2300 if (keys[i].handle_and_validate != NULL) {
2301 if (!keys[i].handle_and_validate (kf, locale_key, value))
2308 if (i == n_keys && strncmp (key, "X-", 2)) {
2309 print_fatal (kf, "file contains key \"%s\" in group \"%s\", but "
2310 "keys extending the format should start with "
2311 "\"X-\"\n", key, kf->current_group);
2319 validate_desktop_key (kf_validator *kf,
2320 const char *locale_key,
2325 return validate_known_key (kf, locale_key, key, locale, value,
2326 registered_desktop_keys,
2327 G_N_ELEMENTS (registered_desktop_keys));
2331 validate_action_key (kf_validator *kf,
2332 const char *locale_key,
2337 return validate_known_key (kf, locale_key, key, locale, value,
2338 registered_action_keys,
2339 G_N_ELEMENTS (registered_action_keys));
2342 /* + Multiple keys in the same group may not have the same name.
2346 validate_keys_for_current_group (kf_validator *kf)
2348 gboolean desktop_group;
2349 gboolean action_group;
2351 GHashTable *duplicated_keys_hash;
2360 desktop_group = (!strcmp (kf->current_group, GROUP_DESKTOP_ENTRY) ||
2361 !strcmp (kf->current_group, GROUP_KDE_DESKTOP_ENTRY));
2362 action_group = (!strncmp (kf->current_group, GROUP_DESKTOP_ACTION,
2363 strlen (GROUP_DESKTOP_ACTION)));
2365 keys = g_slist_copy (g_hash_table_lookup (kf->groups, kf->current_group));
2366 /* keys were prepended, so reverse the list (that's why we use a
2368 keys = g_slist_reverse (keys);
2370 kf->current_keys = g_hash_table_new_full (g_str_hash, g_str_equal,
2372 duplicated_keys_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
2375 /* we need two passes: some checks are looking if another key exists in the
2377 for (sl = keys; sl != NULL; sl = sl->next) {
2378 kf_keyvalue *keyvalue;
2380 keyvalue = (kf_keyvalue *) sl->data;
2381 g_hash_table_insert (kf->current_keys, keyvalue->key, keyvalue);
2383 /* we could display the error about duplicate keys here, but it's better
2384 * to display it with the first occurence of this key */
2385 hashvalue = g_hash_table_lookup (duplicated_keys_hash, keyvalue->key);
2387 g_hash_table_insert (duplicated_keys_hash, keyvalue->key,
2388 GINT_TO_POINTER (1));
2390 g_hash_table_replace (duplicated_keys_hash, keyvalue->key,
2391 GINT_TO_POINTER (GPOINTER_TO_INT (hashvalue) + 1));
2395 for (sl = keys; sl != NULL; sl = sl->next) {
2396 kf_keyvalue *keyvalue;
2397 gboolean skip_desktop_check;
2399 keyvalue = (kf_keyvalue *) sl->data;
2401 skip_desktop_check = FALSE;
2403 if (!key_extract_locale (keyvalue->key, &key, &locale)) {
2404 print_fatal (kf, "file contains key \"%s\" in group \"%s\", but "
2405 "key names must contain only the characters "
2406 "A-Za-z0-9- (they may have a \"[LOCALE]\" postfix)\n",
2407 keyvalue->key, kf->current_group);
2409 skip_desktop_check = TRUE;
2411 key = g_strdup (keyvalue->key);
2414 g_assert (key != NULL);
2416 hashvalue = g_hash_table_lookup (duplicated_keys_hash, keyvalue->key);
2417 if (GPOINTER_TO_INT (hashvalue) > 1) {
2418 g_hash_table_remove (duplicated_keys_hash, keyvalue->key);
2419 print_fatal (kf, "file contains multiple keys named \"%s\" in "
2420 "group \"%s\"\n", keyvalue->key, kf->current_group);
2424 if (desktop_group && !skip_desktop_check) {
2425 if (!validate_desktop_key (kf, keyvalue->key,
2426 key, locale, keyvalue->value))
2428 } else if (action_group && !skip_desktop_check) {
2429 if (!validate_action_key (kf, keyvalue->key,
2430 key, locale, keyvalue->value))
2440 g_slist_free (keys);
2441 g_hash_table_destroy (duplicated_keys_hash);
2442 g_hash_table_destroy (kf->current_keys);
2443 kf->current_keys = NULL;
2444 /* Clear ShowIn flag, so that different groups can each have a OnlyShowIn /
2446 kf->show_in = FALSE;
2451 /* + Using [KDE Desktop Entry] instead of [Desktop Entry] as header is
2454 * + Group names may contain all ASCII characters except for [ and ] and
2455 * control characters.
2457 * + All groups extending the format should start with "X-".
2459 * + Accept "Desktop Action foobar" group, where foobar is a valid key
2463 * Note that for "Desktop Action foobar" group, we will check later on (in
2464 * validate_actions()) that the Actions key contains "foobar".
2467 validate_group_name (kf_validator *kf,
2473 for (i = 0; group[i] != '\0'; i++) {
2475 if (g_ascii_iscntrl (c) || c == '[' || c == ']') {
2476 print_fatal (kf, "file contains group \"%s\", but group names "
2477 "may contain all ASCII characters except for [ "
2478 "and ] and control characters\n", group);
2483 if (!strncmp (group, "X-", 2))
2486 if (!strcmp (group, GROUP_DESKTOP_ENTRY)) {
2487 if (kf->main_group && !strcmp (kf->main_group, GROUP_KDE_DESKTOP_ENTRY))
2488 print_warning (kf, "file contains groups \"%s\" and \"%s\", which play "
2490 GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY);
2492 kf->main_group = GROUP_DESKTOP_ENTRY;
2497 if (!strcmp (group, GROUP_KDE_DESKTOP_ENTRY)) {
2498 if (kf->kde_reserved_warnings || !kf->no_deprecated_warnings)
2499 print_warning (kf, "file contains group \"%s\", which is deprecated "
2500 "in favor of \"%s\"\n", group, GROUP_DESKTOP_ENTRY);
2502 if (kf->main_group && !strcmp (kf->main_group, GROUP_DESKTOP_ENTRY))
2503 print_warning (kf, "file contains groups \"%s\" and \"%s\", which play "
2505 GROUP_DESKTOP_ENTRY, GROUP_KDE_DESKTOP_ENTRY);
2507 kf->main_group = GROUP_KDE_DESKTOP_ENTRY;
2512 if (!strncmp (group, GROUP_DESKTOP_ACTION, strlen (GROUP_DESKTOP_ACTION))) {
2513 if (group[strlen (GROUP_DESKTOP_ACTION) - 1] == '\0') {
2514 print_fatal (kf, "file contains group \"%s\", which is an action "
2515 "group with no action name\n", group);
2520 action = g_strdup (group + strlen (GROUP_DESKTOP_ACTION));
2522 if (!key_is_valid (action, strlen (action))) {
2523 print_fatal (kf, "file contains group \"%s\", which has an invalid "
2524 "action identifier, only alphanumeric characters and "
2525 "'-' are allowed\n", group);
2530 g_hash_table_insert (kf->action_groups, action, action);
2536 print_fatal (kf, "file contains group \"%s\", but groups extending "
2537 "the format should start with \"X-\"\n", group);
2542 validate_required_keys (kf_validator *kf,
2543 const char *group_name,
2544 DesktopKeyDefinition *key_definitions,
2545 unsigned int n_keys)
2551 GHashTable *hashtable;
2555 hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
2556 keys = g_hash_table_lookup (kf->groups, group_name);
2558 for (sl = keys; sl != NULL; sl = sl->next) {
2559 kf_keyvalue *keyvalue;
2561 keyvalue = (kf_keyvalue *) sl->data;
2562 g_hash_table_insert (hashtable, keyvalue->key, keyvalue->key);
2565 for (i = 0; i < n_keys; i++) {
2566 if (key_definitions[i].required) {
2567 if (!g_hash_table_lookup (hashtable,
2568 key_definitions[i].name)) {
2569 print_fatal (kf, "required key \"%s\" in group \"%s\" is not "
2571 key_definitions[i].name, group_name);
2577 g_hash_table_destroy (hashtable);
2583 validate_required_desktop_keys (kf_validator *kf)
2585 return validate_required_keys (kf, kf->main_group,
2586 registered_desktop_keys,
2587 G_N_ELEMENTS (registered_desktop_keys));
2590 #define PRINT_ERROR_FOREACH_KEY(lower, real) \
2592 print_error_foreach_##lower##_key (const char *name, \
2595 print_fatal (kf, "key \"%s\" is present in group \"%s\", but the type is " \
2596 "\"%s\" while this key is only valid for type \"%s\"\n", \
2597 name, kf->main_group, kf->type_string, real); \
2600 PRINT_ERROR_FOREACH_KEY (application, "Application")
2601 PRINT_ERROR_FOREACH_KEY (link, "Link")
2602 PRINT_ERROR_FOREACH_KEY (fsdevice, "FSDevice")
2603 PRINT_ERROR_FOREACH_KEY (mimetype, "MimeType")
2606 validate_type_keys (kf_validator *kf)
2615 case APPLICATION_TYPE:
2616 g_list_foreach (kf->link_keys,
2617 (GFunc) print_error_foreach_link_key, kf);
2618 g_list_foreach (kf->fsdevice_keys,
2619 (GFunc) print_error_foreach_fsdevice_key, kf);
2620 g_list_foreach (kf->mimetype_keys,
2621 (GFunc) print_error_foreach_mimetype_key, kf);
2622 retval = (g_list_length (kf->link_keys) +
2623 g_list_length (kf->fsdevice_keys) +
2624 g_list_length (kf->mimetype_keys) == 0);
2627 g_list_foreach (kf->application_keys,
2628 (GFunc) print_error_foreach_application_key, kf);
2629 g_list_foreach (kf->fsdevice_keys,
2630 (GFunc) print_error_foreach_fsdevice_key, kf);
2631 g_list_foreach (kf->mimetype_keys,
2632 (GFunc) print_error_foreach_mimetype_key, kf);
2633 retval = (g_list_length (kf->application_keys) +
2634 g_list_length (kf->fsdevice_keys) +
2635 g_list_length (kf->mimetype_keys) == 0);
2637 case DIRECTORY_TYPE:
2639 case SERVICE_TYPE_TYPE:
2640 g_list_foreach (kf->application_keys,
2641 (GFunc) print_error_foreach_application_key, kf);
2642 g_list_foreach (kf->link_keys,
2643 (GFunc) print_error_foreach_link_key, kf);
2644 g_list_foreach (kf->fsdevice_keys,
2645 (GFunc) print_error_foreach_fsdevice_key, kf);
2646 g_list_foreach (kf->mimetype_keys,
2647 (GFunc) print_error_foreach_mimetype_key, kf);
2648 retval = (g_list_length (kf->application_keys) +
2649 g_list_length (kf->link_keys) +
2650 g_list_length (kf->fsdevice_keys) +
2651 g_list_length (kf->mimetype_keys) == 0);
2654 g_list_foreach (kf->application_keys,
2655 (GFunc) print_error_foreach_application_key, kf);
2656 g_list_foreach (kf->link_keys,
2657 (GFunc) print_error_foreach_link_key, kf);
2658 g_list_foreach (kf->mimetype_keys,
2659 (GFunc) print_error_foreach_mimetype_key, kf);
2660 retval = (g_list_length (kf->application_keys) +
2661 g_list_length (kf->link_keys) +
2662 g_list_length (kf->mimetype_keys) == 0);
2665 g_list_foreach (kf->application_keys,
2666 (GFunc) print_error_foreach_application_key, kf);
2667 g_list_foreach (kf->link_keys,
2668 (GFunc) print_error_foreach_link_key, kf);
2669 g_list_foreach (kf->fsdevice_keys,
2670 (GFunc) print_error_foreach_fsdevice_key, kf);
2671 retval = (g_list_length (kf->application_keys) +
2672 g_list_length (kf->link_keys) +
2673 g_list_length (kf->fsdevice_keys) == 0);
2676 g_assert_not_reached ();
2683 lookup_group_foreach_action (char *key,
2687 if (g_hash_table_lookup (kf->action_groups, key)) {
2690 group_name = g_strconcat (GROUP_DESKTOP_ACTION, key, NULL);
2691 validate_required_keys (kf, group_name,
2692 registered_action_keys,
2693 G_N_ELEMENTS (registered_action_keys));
2694 g_free (group_name);
2696 g_hash_table_remove (kf->action_groups, key);
2704 print_error_foreach_action (char *key,
2708 print_fatal (kf, "action \"%s\" is defined, but there is no matching "
2709 "\"%s%s\" group\n", key, GROUP_DESKTOP_ACTION, key);
2713 print_error_foreach_group (char *key,
2717 print_fatal (kf, "action group \"%s%s\" exists, but there is no matching "
2718 "action \"%s\"\n", GROUP_DESKTOP_ACTION, key, key);
2722 validate_actions (kf_validator *kf)
2724 g_hash_table_foreach_remove (kf->action_values,
2725 (GHRFunc) lookup_group_foreach_action, kf);
2727 g_hash_table_foreach (kf->action_values,
2728 (GHFunc) print_error_foreach_action, kf);
2730 g_hash_table_foreach (kf->action_groups,
2731 (GHFunc) print_error_foreach_group, kf);
2733 return (g_hash_table_size (kf->action_values) +
2734 g_hash_table_size (kf->action_groups) == 0);
2737 /* + These desktop entry files should have the extension .desktop.
2739 * + Desktop entries which describe how a directory is to be
2740 * formatted/displayed should be simply called .directory.
2742 * + Using .kdelnk instead of .desktop as the file extension is deprecated.
2744 * FIXME: we're not doing what the spec says wrt Directory.
2747 validate_filename (kf_validator *kf)
2749 if (kf->type == DIRECTORY_TYPE) {
2750 if (g_str_has_suffix (kf->filename, ".directory"))
2753 print_fatal (kf, "file is of type \"Directory\", but filename does not "
2754 "have a .directory extension\n");
2759 if (g_str_has_suffix (kf->filename, ".desktop"))
2762 if (g_str_has_suffix (kf->filename, ".kdelnk")) {
2763 if (kf->kde_reserved_warnings || !kf->no_deprecated_warnings)
2764 print_warning (kf, "filename has a .kdelnk extension, which is "
2765 "deprecated in favor of .desktop\n");
2769 print_fatal (kf, "filename does not have a .desktop extension\n");
2773 /* + Lines beginning with a # and blank lines are considered comments.
2777 validate_line_is_comment (kf_validator *kf,
2780 return (*line == '#' || *line == '\0');
2783 /* + A group header with name groupname is a line in the format: [groupname]
2785 * + Group names may contain all ASCII characters except for [ and ] and
2786 * control characters.
2787 * This is done in validate_group_name().
2790 validate_line_looks_like_group (kf_validator *kf,
2797 chomped = g_strdup (line);
2798 g_strchomp (chomped);
2800 result = (*chomped == '[' && chomped[strlen (chomped) - 1] == ']');
2802 if (result && strcmp (chomped, line))
2803 print_fatal (kf, "line \"%s\" ends with a space, but looks like a group. "
2804 "The validation will continue, with the trailing spaces "
2805 "ignored.\n", line);
2807 if (group && result)
2808 *group = g_strndup (chomped + 1, strlen (chomped) - 2);
2815 /* + Space before and after the equals sign should be ignored; the = sign is
2816 * the actual delimiter.
2820 validate_line_looks_like_entry (kf_validator *kf,
2827 p = g_utf8_strchr (line, -1, '=');
2832 /* key must be non-empty */
2837 *key = g_strndup (line, p - line);
2841 *value = g_strdup (p + 1);
2848 /* + Only comments are accepted before the first group.
2850 * + The first group should be "Desktop Entry".
2852 * + Multiple groups may not have the same name.
2856 validate_parse_line (kf_validator *kf)
2864 line = kf->parse_buffer->str;
2865 len = kf->parse_buffer->len;
2867 if (!kf->utf8_warning && !g_utf8_validate (line, len, NULL)) {
2868 print_warning (kf, "file contains lines that are not UTF-8 encoded. There "
2869 "is no guarantee the validator will correctly work.\n");
2870 kf->utf8_warning = TRUE;
2873 if (g_ascii_isspace (*line)) {
2874 print_fatal (kf, "line \"%s\" starts with a space. Comment, group and "
2875 "key-value lines should not start with a space. The "
2876 "validation will continue, with the leading spaces "
2877 "ignored.\n", line);
2878 while (g_ascii_isspace (*line))
2882 if (validate_line_is_comment (kf, line))
2886 if (validate_line_looks_like_group (kf, line, &group)) {
2887 if (!kf->current_group &&
2888 (strcmp (group, GROUP_DESKTOP_ENTRY) &&
2889 strcmp (group, GROUP_KDE_DESKTOP_ENTRY)))
2890 print_fatal (kf, "first group is not \"" GROUP_DESKTOP_ENTRY "\"\n");
2892 if (kf->current_group && strcmp (kf->current_group, group))
2893 validate_keys_for_current_group (kf);
2895 if (g_hash_table_lookup_extended (kf->groups, group, NULL, NULL)) {
2896 print_fatal (kf, "file contains multiple groups named \"%s\", but "
2897 "multiple groups may not have the same name\n", group);
2899 validate_group_name (kf, group);
2900 g_hash_table_insert (kf->groups, g_strdup (group), NULL);
2903 if (kf->current_group)
2904 g_free (kf->current_group);
2905 kf->current_group = group;
2912 if (validate_line_looks_like_entry (kf, line, &key, &value)) {
2913 if (kf->current_group) {
2915 kf_keyvalue *keyvalue;
2917 keyvalue = g_slice_new (kf_keyvalue);
2918 keyvalue->key = key;
2919 keyvalue->value = value;
2921 keys = g_hash_table_lookup (kf->groups, kf->current_group);
2922 keys = g_slist_prepend (keys, keyvalue);
2923 g_hash_table_replace (kf->groups, g_strdup (kf->current_group), keys);
2930 print_fatal (kf, "file contains entry \"%s\" before the first group, "
2931 "but only comments are accepted before the first "
2938 print_fatal (kf, "file contains line \"%s\", which is not a comment, "
2939 "a group or an entry\n", line);
2942 /* + Desktop entry files are encoded as lines of 8-bit characters separated by
2947 validate_parse_data (kf_validator *kf,
2953 for (i = 0; i < length; i++) {
2954 if (data[i] == '\n') {
2955 if (i > 0 && data[i - 1] == '\r') {
2956 g_string_erase (kf->parse_buffer, kf->parse_buffer->len - 1, 1);
2958 if (!kf->cr_error) {
2959 print_fatal (kf, "file contains at least one line ending with a "
2960 "carriage return before the line feed, while lines "
2961 "should only be separated by a line feed "
2962 "character. First such line is: \"%s\"\n",
2963 kf->parse_buffer->str);
2964 kf->cr_error = TRUE;
2968 if (kf->parse_buffer->len > 0) {
2969 validate_parse_line (kf);
2970 g_string_erase (kf->parse_buffer, 0, -1);
2973 } else if (data[i] == '\r') {
2974 if (!kf->cr_error) {
2975 print_fatal (kf, "file contains at least one line ending with a "
2976 "carriage return, while lines should only be "
2977 "separated by a line feed character. First such "
2978 "line is: \"%s\"\n", kf->parse_buffer->str);
2979 kf->cr_error = TRUE;
2985 g_string_append_c (kf->parse_buffer, data[i]);
2990 validate_flush_parse_buffer (kf_validator *kf)
2992 if (kf->parse_buffer->len > 0) {
2993 validate_parse_line (kf);
2994 g_string_erase (kf->parse_buffer, 0, -1);
2997 if (kf->current_group)
2998 validate_keys_for_current_group (kf);
3001 #define VALIDATE_READ_SIZE 4096
3003 validate_parse_from_fd (kf_validator *kf,
3007 struct stat stat_buf;
3008 char read_buf[VALIDATE_READ_SIZE];
3010 if (fstat (fd, &stat_buf) < 0) {
3011 print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
3015 if (!S_ISREG (stat_buf.st_mode)) {
3016 print_fatal (kf, "file is not a regular file\n");
3020 if (stat_buf.st_size == 0) {
3021 print_fatal (kf, "file is empty\n");
3027 bytes_read = read (fd, read_buf, VALIDATE_READ_SIZE);
3029 if (bytes_read == 0) /* End of File */
3032 if (bytes_read < 0) {
3033 if (errno == EINTR || errno == EAGAIN)
3036 /* let's validate what we already have */
3037 validate_flush_parse_buffer (kf);
3039 print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
3043 validate_parse_data (kf, read_buf, bytes_read);
3046 validate_flush_parse_buffer (kf);
3052 validate_load_and_parse (kf_validator *kf)
3057 fd = g_open (kf->filename, O_RDONLY, 0);
3060 print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
3064 ret = validate_parse_from_fd (kf, fd);
3072 groups_hashtable_free (gpointer key,
3079 list = (GSList *) value;
3080 for (sl = list; sl != NULL; sl = sl->next) {
3081 kf_keyvalue *keyvalue;
3083 keyvalue = (kf_keyvalue *) sl->data;
3084 g_free (keyvalue->key);
3085 g_free (keyvalue->value);
3086 g_slice_free (kf_keyvalue, keyvalue);
3089 g_slist_free (list);
3095 desktop_file_validate (const char *filename,
3097 gboolean no_warn_deprecated,
3102 /* just a consistency check */
3103 g_assert (G_N_ELEMENTS (registered_types) == LAST_TYPE - 1);
3105 kf.filename = filename;
3106 kf.parse_buffer = g_string_new ("");
3107 kf.utf8_warning = FALSE;
3108 kf.cr_error = FALSE;
3109 kf.current_group = NULL;
3110 kf.groups = g_hash_table_new_full (g_str_hash, g_str_equal,
3112 kf.current_keys = NULL;
3113 kf.kde_reserved_warnings = warn_kde;
3114 kf.no_deprecated_warnings = no_warn_deprecated;
3115 kf.no_hints = no_hints;
3117 kf.main_group = NULL;
3118 kf.type = INVALID_TYPE;
3119 kf.type_string = NULL;
3121 kf.application_keys = NULL;
3122 kf.link_keys = NULL;
3123 kf.fsdevice_keys = NULL;
3124 kf.mimetype_keys = NULL;
3125 kf.action_values = g_hash_table_new_full (g_str_hash, g_str_equal,
3127 kf.action_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
3129 kf.fatal_error = FALSE;
3130 #if GLIB_CHECK_VERSION(2, 50, 0)
3131 kf.use_colors = g_log_writer_supports_color (fileno (stdout));
3133 kf.use_colors = FALSE;
3136 validate_load_and_parse (&kf);
3137 //FIXME: this does not work well if there are both a Desktop Entry and a KDE
3138 //Desktop Entry groups since only the last one will be validated for this.
3139 if (kf.main_group) {
3140 validate_required_desktop_keys (&kf);
3141 validate_type_keys (&kf);
3143 validate_actions (&kf);
3144 validate_filename (&kf);
3146 g_list_foreach (kf.application_keys, (GFunc) g_free, NULL);
3147 g_list_free (kf.application_keys);
3148 g_list_foreach (kf.link_keys, (GFunc) g_free, NULL);
3149 g_list_free (kf.link_keys);
3150 g_list_foreach (kf.fsdevice_keys, (GFunc) g_free, NULL);
3151 g_list_free (kf.fsdevice_keys);
3152 g_list_foreach (kf.mimetype_keys, (GFunc) g_free, NULL);
3153 g_list_free (kf.mimetype_keys);
3155 g_hash_table_destroy (kf.action_values);
3156 g_hash_table_destroy (kf.action_groups);
3158 g_assert (kf.current_keys == NULL);
3159 /* we can't add an automatic destroy handler for the value because we replace
3160 * it when adding keys, and this means we'd have to copy the value each time
3162 g_hash_table_foreach_remove (kf.groups, groups_hashtable_free, NULL);
3163 g_hash_table_destroy (kf.groups);
3164 g_free (kf.current_group);
3165 g_string_free (kf.parse_buffer, TRUE);
3167 return (!kf.fatal_error);
3170 /* return FALSE if we were unable to fix the file */
3172 desktop_file_fixup (GKeyFile *keyfile,
3173 const char *filename)
3175 if (g_key_file_has_group (keyfile, GROUP_KDE_DESKTOP_ENTRY)) {
3176 g_printerr ("%s: warning: renaming deprecated \"%s\" group to \"%s\"\n",
3177 filename, GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY);
3178 dfu_key_file_rename_group (keyfile,
3179 GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY);