diff options
Diffstat (limited to 'src/validate.c')
-rw-r--r-- | src/validate.c | 2580 |
1 files changed, 1755 insertions, 825 deletions
diff --git a/src/validate.c b/src/validate.c index bb5dbbd..173c77f 100644 --- a/src/validate.c +++ b/src/validate.c @@ -1,1009 +1,1939 @@ -#include <stdlib.h> +/* validate.c: validate a desktop entry file + * + * Copyright (C) 2007 Vincent Untz <vuntz@gnome.org> + * + * A really small portion of this code comes from the old validate.c. + * Authors of the old validate.c are: + * Mark McLoughlin + * Havoc Pennington + * Ray Strode + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + #include <stdio.h> #include <string.h> -#include "desktop_file.h" + +#include "keyfileutils.h" #include "validate.h" -#include <libintl.h> -#define _(x) gettext ((x)) -#define N_(x) x +/* We're trying to not use GKeyFile APIs as much as possible, so we don't + * have to "trust" what GKeyFile is doing, and GKeyFile can be less + * strict than this validator. + * FIXME: maybe it's a good idea to just not use GKeyFile and do all the + * parsing ourselves. This way, we know we're doing exactly what we want. + */ + +//FIXME: document where GKeyFile is stricter than the spec +// * only UTF-8 (so no Legacy-Mixed encoding) +// * "Historically lists have been comma separated." +// Add this information to --usage + +/*TODO: + * + Desktop entry files are encoded as lines of 8-bit characters separated by + * LF characters. + * + Multiple groups may not have the same name. + * + Lecagy-Mixed Encoding (annexe D) + * + The escape sequences \s, \n, \t, \r, and \\ are supported for values of + * type string and localestring, meaning ASCII space, newline, tab, carriage + * return, and backslash, respectively. + * GKeyFile handles this, but we don't. + */ + +typedef enum { + INVALID_TYPE = 0, + + APPLICATION_TYPE, + LINK_TYPE, + DIRECTORY_TYPE, + + /* Types reserved for KDE */ + /* since 0.9.4 */ + SERVICE_TYPE, + SERVICE_TYPE_TYPE, + /* since 0.9.6 */ + FSDEVICE_TYPE, + + /* Deprecated types */ + /* since 0.9.4 */ + MIMETYPE_TYPE, + + LAST_TYPE +} DesktopType; + +typedef enum { + DESKTOP_STRING_TYPE, + DESKTOP_LOCALESTRING_TYPE, + DESKTOP_BOOLEAN_TYPE, + DESKTOP_NUMERIC_TYPE, + DESKTOP_STRING_LIST_TYPE, + /* Deprecated types */ + /* since 0.9.6 */ + DESKTOP_REGEXP_LIST_TYPE +} DesktopKeyType; + +typedef struct _kf_validator kf_validator; + +struct _kf_validator { + const char *filename; + GKeyFile *keyfile; + + gboolean kde_reserved_warnings; + gboolean no_deprecated_warnings; + + char *main_group; + DesktopType type; + char *type_string; + + gboolean show_in; + GList *application_keys; + GList *link_keys; + GList *fsdevice_keys; + GList *mimetype_keys; + + GHashTable *action_values; + GHashTable *action_groups; + + gboolean fatal_error; +}; +static gboolean +validate_string_key (kf_validator *kf, + const char *key, + const char *locale, + const char *value); +static gboolean +validate_localestring_key (kf_validator *kf, + const char *key, + const char *locale, + const char *value); +static gboolean +validate_boolean_key (kf_validator *kf, + const char *key, + const char *locale, + const char *value); +static gboolean +validate_numeric_key (kf_validator *kf, + const char *key, + const char *locale, + const char *value); +static gboolean +validate_string_list_key (kf_validator *kf, + const char *key, + const char *locale, + const char *value); +static gboolean +validate_regexp_list_key (kf_validator *kf, + const char *key, + const char *locale, + const char *value); + +static gboolean +handle_type_key (kf_validator *kf, + const char *locale_key, + const char *value); +static gboolean +handle_version_key (kf_validator *kf, + const char *locale_key, + const char *value); +static gboolean +handle_comment_key (kf_validator *kf, + const char *locale_key, + const char *value); +static gboolean +handle_show_in_key (kf_validator *kf, + const char *locale_key, + const char *value); +static gboolean +handle_exec_key (kf_validator *kf, + const char *locale_key, + const char *value); +static gboolean +handle_path_key (kf_validator *kf, + const char *locale_key, + const char *value); +static gboolean +handle_mime_key (kf_validator *kf, + const char *locale_key, + const char *value); +static gboolean +handle_categories_key (kf_validator *kf, + const char *locale_key, + const char *value); +static gboolean +handle_actions_key (kf_validator *kf, + const char *locale_key, + const char *value); +static gboolean +handle_dev_key (kf_validator *kf, + const char *locale_key, + const char *value); +static gboolean +handle_mountpoint_key (kf_validator *kf, + const char *locale_key, + const char *value); +static gboolean +handle_encoding_key (kf_validator *kf, + const char *locale_key, + const char *value); +static gboolean +handle_key_for_application (kf_validator *kf, + const char *locale_key, + const char *value); +static gboolean +handle_key_for_link (kf_validator *kf, + const char *locale_key, + const char *value); +static gboolean +handle_key_for_fsdevice (kf_validator *kf, + const char *locale_key, + const char *value); +static gboolean +handle_key_for_mimetype (kf_validator *kf, + const char *locale_key, + const char *value); -struct KeyHashData { - gboolean has_non_translated; - gboolean has_translated; +struct { + DesktopType type; + char *name; + gboolean kde_reserved; + gboolean deprecated; +} registered_types[] = { + { APPLICATION_TYPE, "Application", FALSE, FALSE }, + { LINK_TYPE, "Link", FALSE, FALSE }, + { DIRECTORY_TYPE, "Directory", FALSE, FALSE }, + { SERVICE_TYPE, "Service", TRUE, FALSE }, + { SERVICE_TYPE_TYPE, "ServiceType", TRUE, FALSE }, + { FSDEVICE_TYPE, "FSDevice", TRUE, FALSE }, + { MIMETYPE_TYPE, "MimeType", FALSE, TRUE } }; -struct KeyData { - GHashTable *hash; - const char *filename; - gboolean deprecated; +struct { + DesktopKeyType type; + gboolean (* validate) (kf_validator *kf, + const char *key, + const char *locale, + const char *value); +} validate_for_type[] = { + { DESKTOP_STRING_TYPE, validate_string_key }, + { DESKTOP_LOCALESTRING_TYPE, validate_localestring_key }, + { DESKTOP_BOOLEAN_TYPE, validate_boolean_key }, + { DESKTOP_NUMERIC_TYPE, validate_numeric_key }, + { DESKTOP_STRING_LIST_TYPE, validate_string_list_key }, + { DESKTOP_REGEXP_LIST_TYPE, validate_regexp_list_key } }; -static gboolean fatal_error_occurred = FALSE; +struct { + DesktopKeyType type; + char *name; + gboolean required; + gboolean deprecated; + gboolean kde_reserved; + gboolean (* handle_and_validate) (kf_validator *kf, + const char *locale_key, + const char *value); +} registered_desktop_keys[] = { + { DESKTOP_STRING_TYPE, "Type", TRUE, FALSE, FALSE, handle_type_key }, + /* it is numeric according to the spec, but it's not true in previous + * versions of the spec. handle_version_key() will manage this */ + { DESKTOP_STRING_TYPE, "Version", FALSE, FALSE, FALSE, handle_version_key }, + { DESKTOP_LOCALESTRING_TYPE, "Name", TRUE, FALSE, FALSE, NULL }, + { DESKTOP_LOCALESTRING_TYPE, "GenericName", FALSE, FALSE, FALSE, NULL }, + { DESKTOP_BOOLEAN_TYPE, "NoDisplay", FALSE, FALSE, FALSE, NULL }, + { DESKTOP_LOCALESTRING_TYPE, "Comment", FALSE, FALSE, FALSE, handle_comment_key }, + { DESKTOP_LOCALESTRING_TYPE, "Icon", FALSE, FALSE, FALSE, NULL }, + { DESKTOP_BOOLEAN_TYPE, "Hidden", FALSE, FALSE, FALSE, NULL }, + { DESKTOP_STRING_LIST_TYPE, "OnlyShowIn", FALSE, FALSE, FALSE, handle_show_in_key }, + { DESKTOP_STRING_LIST_TYPE, "NotShowIn", FALSE, FALSE, FALSE, handle_show_in_key }, + { DESKTOP_STRING_TYPE, "TryExec", FALSE, FALSE, FALSE, handle_key_for_application }, + { DESKTOP_STRING_TYPE, "Exec", FALSE, FALSE, FALSE, handle_exec_key }, + { DESKTOP_STRING_TYPE, "Path", FALSE, FALSE, FALSE, handle_path_key }, + { DESKTOP_BOOLEAN_TYPE, "Terminal", FALSE, FALSE, FALSE, handle_key_for_application }, + { DESKTOP_STRING_LIST_TYPE, "MimeType", FALSE, FALSE, FALSE, handle_mime_key }, + { DESKTOP_STRING_LIST_TYPE, "Categories", FALSE, FALSE, FALSE, handle_categories_key }, + { DESKTOP_BOOLEAN_TYPE, "StartupNotify", FALSE, FALSE, FALSE, handle_key_for_application }, + { DESKTOP_STRING_TYPE, "StartupWMClass", FALSE, FALSE, FALSE, handle_key_for_application }, + { DESKTOP_STRING_TYPE, "URL", FALSE, FALSE, FALSE, handle_key_for_link }, + + //FIXME: it's not deprecated, but got removed from the spec temporarly + { DESKTOP_STRING_LIST_TYPE, "Actions", FALSE, FALSE, FALSE, handle_actions_key }, + + /* Keys reserved for KDE */ + + /* since 0.9.4 */ + { DESKTOP_STRING_TYPE, "ServiceTypes", FALSE, FALSE, TRUE, NULL }, + { DESKTOP_STRING_TYPE, "DocPath", FALSE, FALSE, TRUE, NULL }, + { DESKTOP_LOCALESTRING_TYPE, "Keywords", FALSE, FALSE, TRUE, NULL }, + { DESKTOP_STRING_TYPE, "InitialPreference", FALSE, FALSE, TRUE, NULL }, + /* since 0.9.6 */ + { DESKTOP_STRING_TYPE, "Dev", FALSE, FALSE, TRUE, handle_dev_key }, + { DESKTOP_STRING_TYPE, "FSType", FALSE, FALSE, TRUE, handle_key_for_fsdevice }, + { DESKTOP_STRING_TYPE, "MountPoint", FALSE, FALSE, TRUE, handle_mountpoint_key }, + { DESKTOP_BOOLEAN_TYPE, "ReadOnly", FALSE, FALSE, TRUE, handle_key_for_fsdevice }, + { DESKTOP_STRING_TYPE, "UnmountIcon", FALSE, FALSE, TRUE, handle_key_for_fsdevice }, + + /* Deprecated keys */ + + /* since 0.9.3 */ + { DESKTOP_STRING_TYPE, "Protocols", FALSE, TRUE, FALSE, NULL }, + { DESKTOP_STRING_TYPE, "Extensions", FALSE, TRUE, FALSE, NULL }, + { DESKTOP_STRING_TYPE, "BinaryPattern", FALSE, TRUE, FALSE, NULL }, + { DESKTOP_STRING_TYPE, "MapNotify", FALSE, TRUE, FALSE, NULL }, + /* since 0.9.4 */ + { DESKTOP_REGEXP_LIST_TYPE, "Patterns", FALSE, TRUE, FALSE, handle_key_for_mimetype }, + { DESKTOP_STRING_TYPE, "DefaultApp", FALSE, TRUE, FALSE, handle_key_for_mimetype }, + { DESKTOP_STRING_TYPE, "MiniIcon", FALSE, TRUE, FALSE, NULL }, + { DESKTOP_STRING_TYPE, "TerminalOptions", FALSE, TRUE, FALSE, NULL }, + /* since 0.9.5 */ + { DESKTOP_STRING_TYPE, "Encoding", FALSE, TRUE, FALSE, handle_encoding_key }, + { DESKTOP_LOCALESTRING_TYPE, "SwallowTitle", FALSE, TRUE, FALSE, NULL }, + { DESKTOP_STRING_TYPE, "SwallowExec", FALSE, TRUE, FALSE, NULL }, + /* since 0.9.6 */ + { DESKTOP_STRING_LIST_TYPE, "SortOrder", FALSE, TRUE, FALSE, NULL }, + { DESKTOP_REGEXP_LIST_TYPE, "FilePattern", FALSE, TRUE, FALSE, NULL } +}; +static const char *show_in_registered[] = { + "KDE", "GNOME", "ROX", "XFCE", "Old" +}; + +static const char *main_categories_registered[] = { + "AudioVideo", "Audio", "Video", "Development", "Education", "Game", + "Graphics", "Network", "Office", "Settings", "System", "Utility" +}; + +static const char *additional_categories_registered[] = { + "Building", "Debugger", "IDE", "GUIDesigner", "Profiling", "RevisionControl", + "Translation", "Calendar", "ContactManagement", "Database", "Dictionary", + "Chart", "Email", "Finance", "FlowChart", "PDA", "ProjectManagement", + "Presentation", "Spreadsheet", "WordProcessor", "2DGraphics", + "VectorGraphics", "RasterGraphics", "3DGraphics", "Scanning", "OCR", + "Photography", "Publishing", "Viewer", "TextTools", "DesktopSettings", + "HardwareSettings", "Printing", "PackageManager", "Dialup", + "InstantMessaging", "Chat", "IRCClient", "FileTransfer", "HamRadio", "News", + "P2P", "RemoteAccess", "Telephony", "TelephonyTools", "VideoConference", + "WebBrowser", "WebDevelopment", "Midi", "Mixer", "Sequencer", "Tuner", "TV", + "AudioVideoEditing", "Player", "Recorder", "DiscBurning", "ActionGame", + "AdventureGame", "ArcadeGame", "BoardGame", "BlocksGame", "CardGame", + "KidsGame", "LogicGame", "RolePlaying", "Simulation", "SportsGame", + "StrategyGame", "Art", "Construction", "Music", "Languages", "Science", + "ArtificialIntelligence", "Astronomy", "Biology", "Chemistry", + "ComputerScience", "DataVisualization", "Economy", "Electricity", + "Geography", "Geology", "Geoscience", "History", "ImageProcessing", + "Literature", "Math", "NumericalAnalysis", "MedicalSoftware", "Physics", + "Robotics", "Sports", "ParallelComputing", "Amusement", "Archiving", + "Compression", "Electronics", "Emulator", "Engineering", "FileTools", + "FileManager", "TerminalEmulator", "Filesystem", "Monitor", "Security", + "Accessibility", "Calculator", "Clock", "TextEditor", "Documentation", + "Core", "KDE", "GNOME", "GTK", "Qt", "Motif", "Java", "ConsoleOnly" +}; + +static const char *reserved_categories_registered[] = { + "Screensaver", "TrayIcon", "Applet", "Shell" +}; + +static const char *deprecated_categories_registered[] = { + "Application", "Applications" +}; static void -print_fatal (const char *filename, const char *format, ...) +print_fatal (kf_validator *kf, const char *format, ...) { va_list args; gchar *str; - g_return_if_fail (filename != NULL && format != NULL); + g_return_if_fail (kf != NULL && format != NULL); + + kf->fatal_error = TRUE; - fputs (filename, stdout); - fputs (": error: ", stdout); - va_start (args, format); str = g_strdup_vprintf (format, args); va_end (args); - fputs (str, stdout); - - fflush (stdout); + g_print ("%s: error: %s", kf->filename, str); g_free (str); - - fatal_error_occurred = TRUE; } static void -print_warning (const char* filename, const char *format, ...) +print_warning (kf_validator *kf, const char *format, ...) { va_list args; gchar *str; - g_return_if_fail (filename != NULL && format != NULL); - - fputs (filename, stdout); - fputs (": warning: ", stdout); + g_return_if_fail (kf != NULL && format != NULL); va_start (args, format); str = g_strdup_vprintf (format, args); va_end (args); - fputs (str, stdout); + g_print ("%s: warning: %s", kf->filename, str); - fflush (stdout); - g_free (str); } -static void -validate_string (const char *value, const char *key, const char *locale, const char *filename, GnomeDesktopFile *df) -{ - const char *p; - gboolean ok = TRUE; - char *k; - - p = value; - while (*p) - { - if (!(g_ascii_isprint (*p) || *p == '\n' || *p == '\t')) - { - ok = FALSE; - break; - } - - p++; +/* + Values of type string may contain all ASCII characters except for control + * characters. + * Checked. + */ +static gboolean +validate_string_key (kf_validator *kf, + const char *key, + const char *locale, + const char *value) +{ + int i; + gboolean error; + + error = FALSE; + + for (i = 0; value[i] != '\0'; i++) { + if (!g_ascii_isprint (value[i])) { + error = TRUE; + break; } + } - if (!ok) - { - if (locale) - k = g_strdup_printf ("%s[%s]", key, locale); - else - k = g_strdup_printf ("%s", key); - print_fatal (filename, "invalid characters in value of key \"%s\", keys of type string may contain ASCII characters except control characters\n", k); - g_free (k); + if (error) { + print_fatal (kf, "value \"%s\" for string key \"%s\" in group \"%s\" " + "contains invalid characters, string values may contain " + "all ASCII characters except for control characters\n", + value, key, kf->main_group); + + return FALSE; + } + + return TRUE; +} + +/* + Values of type localestring are user displayable, and are encoded in + * UTF-8. + * Checked. + * + If a postfixed key occurs, the same key must be also present without the + * postfix. + * Checked. + */ +static gboolean +validate_localestring_key (kf_validator *kf, + const char *key, + const char *locale, + const char *value) +{ + char *locale_key; + + if (locale) + locale_key = g_strdup_printf ("%s[%s]", key, locale); + else + locale_key = g_strdup_printf ("%s", key); + + if (!g_utf8_validate (value, -1, NULL)) { + print_fatal (kf, "value \"%s\" for locale string key \"%s\" in group " + "\"%s\" contains invalid UTF-8 characters, locale string " + "values should be encoded in UTF-8\n", + value, locale_key, kf->main_group); + g_free (locale_key); + + return FALSE; + } + + if (!g_key_file_has_key (kf->keyfile, kf->main_group, key, NULL)) { + print_fatal (kf, "key \"%s\" in group \"%s\" is a localized key, but " + "there is no non-localized key \"%s\"\n", + locale_key, kf->main_group, key); + g_free (locale_key); + + return FALSE; + } + + g_free (locale_key); + + return TRUE; +} + +/* + Values of type boolean must either be the string true or false. + * Checked. + * + Historically some booleans have been represented by the numeric entries 0 + * or 1. With this version of the standard they are now to be represented as + * a boolean string. However, if an implementation is reading a pre-1.0 + * desktop entry, it should interpret 0 and 1 as false and true, + * respectively. + * Checked. + */ +static gboolean +validate_boolean_key (kf_validator *kf, + const char *key, + const char *locale, + const char *value) +{ + if (strcmp (value, "true") && strcmp (value, "false") && + strcmp (value, "0") && strcmp (value, "1")) { + print_fatal (kf, "value \"%s\" for boolean key \"%s\" in group \"%s\" " + "contains invalid characters, boolean values must be " + "\"false\" or \"true\"\n", + value, key, kf->main_group); + return FALSE; + } + + if (!kf->no_deprecated_warnings && + (!strcmp (value, "0") || !strcmp (value, "1"))) + print_warning (kf, "boolean key \"%s\" in group \"%s\" has value \"%s\", " + "which is deprecated: boolean values should be " + "\"false\" or \"true\"\n", + key, kf->main_group, value); + + return TRUE; +} + +/* + Values of type numeric must be a valid floating point number as recognized + * by the %f specifier for scanf. + * Checked. + */ +static gboolean +validate_numeric_key (kf_validator *kf, + const char *key, + const char *locale, + const char *value) +{ + float d; + int res; + + res = sscanf (value, "%f", &d); + if (res == 0) { + print_fatal (kf, "value \"%s\" for numeric key \"%s\" in group \"%s\" " + "contains invalid characters, numeric values must be " + "valid floating point numbers\n", + value, key, kf->main_group); + return FALSE; + } + + return TRUE; +} + +/* + Values of type string may contain all ASCII characters except for control + * characters. + * Checked. + * + The multiple values should be separated by a semicolon. Those keys which + * have several values should have a semicolon as the trailing character. + * Checked. + * + FIXME: how should an empty list be handled? + */ +static gboolean +validate_string_regexp_list_key (kf_validator *kf, + const char *key, + const char *locale, + const char *value, + const char *type) +{ + int i; + gboolean error; + + error = FALSE; + + for (i = 0; value[i] != '\0'; i++) { + if (!g_ascii_isprint (value[i])) { + error = TRUE; + break; } + } + + if (error) { + print_fatal (kf, "value \"%s\" for %s list key \"%s\" in group \"%s\" " + "contains invalid character '%c', %s list values may " + "contain all ASCII characters except for control " + "characters\n", + value, type, key, kf->main_group, value[i], type); + + return FALSE; + } + + if (i > 0 && value[i - 1] != ';') { + print_fatal (kf, "value \"%s\" for %s list key \"%s\" in group \"%s\" " + "does not have a semicolon (';') as trailing " + "character\n", + value, type, key, kf->main_group); + + return FALSE; + } + + if (i > 1 && value[i - 1] == ';' && value[i - 2] == '\\' && + (i < 3 || value[i - 3] != '\\')) { + print_fatal (kf, "value \"%s\" for %s list key \"%s\" in group \"%s\" " + "has an escaped semicolon (';') as trailing character\n", + value, type, key, kf->main_group); + + return FALSE; + } + + return TRUE; } -static void -validate_strings (const char *value, const char *key, const char *locale, const char *filename, GnomeDesktopFile *df) -{ - const char *p; - gboolean ok = TRUE; - char *k; - - p = value; - while (*p) - { - if (!(g_ascii_isprint (*p) || *p == '\n' || *p == '\t')) - { - ok = FALSE; - break; - } - - p++; +static gboolean +validate_string_list_key (kf_validator *kf, + const char *key, + const char *locale, + const char *value) +{ + return validate_string_regexp_list_key (kf, key, locale, value, "string"); +} + +static gboolean +validate_regexp_list_key (kf_validator *kf, + const char *key, + const char *locale, + const char *value) +{ + return validate_string_regexp_list_key (kf, key, locale, value, "regexp"); +} + +/* + This specification defines 3 types of desktop entries: Application + * (type 1), Link (type 2) and Directory (type 3). To allow the addition of + * new types in the future, implementations should ignore desktop entries + * with an unknown type. + * Checked. + * + KDE specific types: ServiceType, Service and FSDevice + * Checked. + */ +static gboolean +handle_type_key (kf_validator *kf, + const char *locale_key, + const char *value) +{ + unsigned int i; + + for (i = 0; i < G_N_ELEMENTS (registered_types); i++) { + if (!strcmp (value, registered_types[i].name)) + break; + } + + if (i == G_N_ELEMENTS (registered_types)) { + /* force the type, since the key might be present multiple times... */ + kf->type = INVALID_TYPE; + + print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "is not a registered type value (\"Application\", " + "\"Link\" and \"Directory\")\n", + value, locale_key, kf->main_group); + return FALSE; + } + + if (registered_types[i].kde_reserved && kf->kde_reserved_warnings) + print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "is a reserved value for KDE\n", + value, locale_key, kf->main_group); + + if (registered_types[i].deprecated && !kf->no_deprecated_warnings) + print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "is deprecated\n", + value, locale_key, kf->main_group); + + kf->type = registered_types[i].type; + kf->type_string = registered_types[i].name; + + return TRUE; +} + +/* + Entries that confirm with this version of the specification should use + * 1.0. + * Checked. + * + Previous versions of the spec: 0.9.x where 3 <= x <= 8 + * Checked. + */ +static gboolean +handle_version_key (kf_validator *kf, + const char *locale_key, + const char *value) +{ + if (!strcmp (value, "1.0")) + return TRUE; + + if (!strncmp (value, "0.9.", strlen ("0.9."))) { + char c; + + c = value[strlen ("0.9.")]; + if ('3' <= c && c <= '8' && value[strlen ("0.9.") + 1] == '\0') + return TRUE; + } + + print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "is not a known version\n", + value, locale_key, kf->main_group); + return FALSE; +} + +/* + Tooltip for the entry, for example "View sites on the Internet", should + * not be redundant with Name or GenericName. + * FIXME + */ +static gboolean +handle_comment_key (kf_validator *kf, + const char *locale_key, + const char *value) +{ + return TRUE; +} + +/* + Only one of these keys, either OnlyShowIn or NotShowIn, may appear in a + * group. + * Checked. + * + (for possible values see the Desktop Menu Specification) + * Checked. + * FIXME: this is not perfect because it could fail if a new value with + * a semicolon is registered. + */ +static gboolean +handle_show_in_key (kf_validator *kf, + const char *locale_key, + const char *value) +{ + gboolean retval; + char **show; + GHashTable *hashtable; + int i; + unsigned int j; + + retval = TRUE; + + if (kf->show_in) { + print_fatal (kf, "only one of \"OnlyShowIn\" and \"NotShowInkey\" keys " + "may appear in group \"%s\"\n", + kf->main_group); + retval = FALSE; + } + kf->show_in = TRUE; + + hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); + show = g_strsplit (value, ";", 0); + + for (i = 0; show[i]; i++) { + /* since the value ends with a semicolon, we'll have an empty string + * at the end */ + if (*show[i] == '\0' && show[i + 1] == NULL) + break; + + if (g_hash_table_lookup (hashtable, show[i])) { + print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "contains \"%s\" more than once\n", + value, locale_key, kf->main_group, show[i]); + continue; } - if (!ok) - { - if (locale) - k = g_strdup_printf ("%s[%s]", key, locale); - else - k = g_strdup_printf ("%s", key); - print_fatal (filename, "invalid characters in value of key \"%s\", keys of type strings may contain ASCII characters except control characters\n", k); - g_free (k); + g_hash_table_insert (hashtable, show[i], show[i]); + + for (j = 0; j < G_N_ELEMENTS (show_in_registered); j++) { + if (!strcmp (show[i], show_in_registered[j])) + break; } - /* Check that we end in a semicolon */ - if (p != value) - { - --p; - if (*p != ';') - { - if (locale) - k = g_strdup_printf ("%s[%s]", key, locale); - else - k = g_strdup_printf ("%s", key); - - print_fatal (filename, "value of key \"%s\" is a list of strings and must end with a semicolon\n", k); - g_free (k); - } + if (j == G_N_ELEMENTS (show_in_registered)) { + print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "contains an unregistered value \"%s\"\n", + value, locale_key, kf->main_group, show[i]); + retval = FALSE; } + } + + g_strfreev (show); + g_hash_table_destroy (hashtable); + + return retval; } -static void -validate_categories (const char *value, const char *key, const char *locale, const char *filename, GnomeDesktopFile *df) -{ - #define MAIN_CATEGORIES \ - "AudioVideo", "Audio", "Video", "Development", "Education", "Game", \ - "Graphics", "Network", "Office", "Settings", "System", "Utility" - - #define ADDITIONAL_CATEGORIES \ - "Building", "Debugger", "IDE", "GUIDesigner", "Profiling", \ - "RevisionControl", "Translation", "Calendar", "ContactManagement", \ - "Database", "Dictionary", "Chart", "Email", "Finance", "FlowChart", "PDA", \ - "ProjectManagement", "Presentation", "Spreadsheet", "WordProcessor", \ - "2DGraphics", "VectorGraphics", "RasterGraphics", "3DGraphics", \ - "Scanning", "OCR", "Photography", "Viewer", "DesktopSettings", \ - "HardwareSettings", "PackageManager", "Dialup", "InstantMessaging", \ - "IRCClient", "FileTransfer", "HamRadio", "News", "P2P", "RemoteAccess", \ - "Telephony", "WebBrowser", "WebDevelopment", "Midi", "Mixer", "Sequencer", \ - "Tuner", "TV", "AudioVideoEditing", "Player", "Recorder", "DiscBurning", \ - "ActionGame", "AdventureGame", "ArcadeGame", "BoardGame", "BlocksGame", \ - "CardGame", "KidsGame", "LogicGame", "RolePlaying", "Simulation", \ - "SportsGame", "StrategyGame", "Art", "Construction", "Music", "Languages", \ - "Science", "Astronomy", "Biology", "Chemistry", "Geology", "Math", \ - "MedicalSoftware", "Physics", "Amusement", "Archiving", "Electronics", \ - "Emulator", "Engineering", "FileManager", "TerminalEmulator", \ - "Filesystem", "Monitor", "Security", "Accessibility", "Calculator", \ - "Clock", "TextEditor", "Core", "KDE", "GNOME", "GTK", "Qt", "Motif", \ - "Java", "ConsoleOnly" - - #define RESERVED_CATEGORIES \ - "Screensaver", "TrayIcon", "Applet", "Shell" - - /* Category list from Desktop Menu Specification version 1.0 */ - static const char *categories_keys[] = { - MAIN_CATEGORIES, ADDITIONAL_CATEGORIES, RESERVED_CATEGORIES, NULL - }; - char **vals; - int i; - - validate_strings (value, key, locale, filename, df); - - vals = g_strsplit (value, ";", G_MAXINT); +/* + A command line consists of an executable program optionally followed by + * one or more arguments. The executable program can either be specified with + * its full path or with the name of the executable only. If no full path is + * provided the executable is looked up in the $PATH used by the desktop + * environment. The name or path of the executable program may not contain + * the equal sign ("="). + * FIXME + * + Arguments are separated by a space. + * FIXME + * + Arguments may be quoted in whole. + * FIXME + * + If an argument contains a reserved character the argument must be quoted. + * Checked. + * + The rules for quoting of arguments is also applicable to the executable + * name or path of the executable program as provided. + * FIXME + * + Quoting must be done by enclosing the argument between double quotes and + * escaping the double quote character, backtick character ("`"), dollar sign + * ("$") and backslash character ("\") by preceding it with an additional + * backslash character. Implementations must undo quoting before expanding + * field codes and before passing the argument to the executable program. + * Reserved characters are space (" "), tab, newline, double quote, single + * quote ("'"), backslash character ("\"), greater-than sign (">"), less-than + * sign ("<"), tilde ("~"), vertical bar ("|"), ampersand ("&"), semicolon + * (";"), dollar sign ("$"), asterisk ("*"), question mark ("?"), hash mark + * ("#"), parenthesis ("(") and (")") and backtick character ("`"). + * Checked. + * + Note that the general escape rule for values of type string states that + * the backslash character can be escaped as ("\\") as well and that this + * escape rule is applied before the quoting rule. As such, to unambiguously + * represent a literal backslash character in a quoted argument in a desktop + * entry file requires the use of four successive backslash characters + * ("\\\\"). Likewise, a literal dollar sign in a quoted argument in a + * desktop entry file is unambiguously represented with ("\\$"). + * Checked. + * + Field codes consist of the percentage character ("%") followed by an alpha + * character. Literal percentage characters must be escaped as %%. + * Checked. + * + Command lines that contain a field code that is not listed in this + * specification are invalid and must not be processed, in particular + * implementations may not introduce support for field codes not listed in + * this specification. Extensions, if any, should be introduced by means of a + * new key. + * Checked. + * + A command line may contain at most one %f, %u, %F or %U field code. + * Checked. + * + The %F and %U field codes may only be used as an argument on their own. + * FIXME + */ +static gboolean +handle_exec_key (kf_validator *kf, + const char *locale_key, + const char *value) +{ + gboolean retval; + gboolean file_uri; + gboolean in_quote; + gboolean escaped; + gboolean flag; + const char *c; - i = 0; - while (vals[i]) - ++i; + handle_key_for_application (kf, locale_key, value); - if (i == 0) - { - g_strfreev (vals); - return; - } - - /* Drop the empty string g_strsplit leaves in the vector since - * our list of strings ends in ";" - */ - --i; - g_free (vals[i]); - vals[i] = NULL; + retval = TRUE; - i = 0; - while (vals[i]) - { - int j = 0; - - if (strncmp ("X-", vals[i], 2) == 0) - { - i++; - continue; + file_uri = FALSE; + in_quote = FALSE; + escaped = FALSE; + flag = FALSE; + +#define PRINT_INVALID_IF_FLAG \ + if (flag) { \ + print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " \ + "contains an invalid field code \"%%%c\"\n", \ + value, locale_key, kf->main_group, *c); \ + retval = FALSE; \ + flag = FALSE; \ + break; \ + } + + c = value; + while (*c) { + switch (*c) { + /* quotes and escaped characters in quotes */ + case '"': + PRINT_INVALID_IF_FLAG; + if (in_quote) { + if (!escaped) + in_quote = FALSE; + } else { + if (!escaped) + in_quote = TRUE; + else { + print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "contains an escaped double quote (\\\\\") " + "outside of a quote, but the double quote is " + "a reserved character\n", + value, locale_key, kf->main_group); + retval = FALSE; + + escaped = FALSE; + } + } + break; + case '`': + case '$': + PRINT_INVALID_IF_FLAG; + if (in_quote) { + if (!escaped) { + print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "contains a non-escaped character '%c' in a " + "quote, but it should be escaped with two " + "backslashes (\"\\\\%c\")\n", + value, locale_key, kf->main_group, *c, *c); + retval = FALSE; + } else + escaped = FALSE; + } else { + print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "contains a reserved character '%c' outside of a " + "quote\n", + value, locale_key, kf->main_group, *c); + retval = FALSE; + } + break; + case '\\': + PRINT_INVALID_IF_FLAG; + c++; + if (*c == '\\' && in_quote) + escaped = !escaped; + break; + + /* reserved characters */ + case ' ': + //FIXME + break; + case '\t': + case '\n': + case '\'': + case '>': + case '<': + case '~': + case '|': + case '&': + case ';': + case '*': + case '?': + case '#': + case '(': + case ')': + PRINT_INVALID_IF_FLAG; + if (!in_quote) { + print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "contains a reserved character '%c' outside of a " + "quote\n", + value, locale_key, kf->main_group, *c); + retval = FALSE; } + break; + + /* flags */ + case '%': + flag = !flag; + break; + case 'f': + case 'u': + if (flag) { + if (file_uri) { + print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "may contain at most one \"%f\", \"%u\", " + "\"%F\" or \"%U\" field code\n", + value, locale_key, kf->main_group); + retval = FALSE; + } - while (categories_keys[j]) - { - if (g_ascii_strcasecmp (vals[i], categories_keys[j]) == 0) - { - if (strcmp (vals[i], categories_keys[j]) != 0) - { - print_warning (filename, "%s values are case sensitive (should be \"%s\" instead of \"%s\")\n", - key, categories_keys[j], vals[i]); - } - - break; - } - ++j; - } - - if (categories_keys[j] == NULL) - { - char *valid_categories; - - if ((g_ascii_strcasecmp (vals[i], "Application") == 0) || - (g_ascii_strcasecmp (vals[i], "Applications") == 0)) - { - valid_categories = g_strjoin ("\", \"", MAIN_CATEGORIES, NULL); - print_warning (filename, "The '%s' category is not defined by the desktop entry specification. Please use one of \"%s\" instead\n", - vals[i], valid_categories); - } else { - valid_categories = g_strjoinv ("\", \"", (gchar **) categories_keys); - print_warning (filename, "%s values must be one of \"%s\" (found \"%s\")\n", - key, valid_categories, vals[i]); + file_uri = TRUE; + flag = FALSE; + } + break; + case 'F': + case 'U': + if (flag) { + if (file_uri) { + print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "may contain at most one \"%f\", \"%u\", " + "\"%F\" or \"%U\" field code\n", + value, locale_key, kf->main_group); + retval = FALSE; } - g_free (valid_categories); - } - ++i; + + file_uri = TRUE; + flag = FALSE; + } + break; + case 'i': + case 'c': + case 'k': + if (flag) + flag = FALSE; + break; + case 'd': + case 'D': + case 'n': + case 'N': + case 'v': + case 'm': + if (flag) { + if (!kf->no_deprecated_warnings) + print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "contains a deprecated field code \"%%%c\"\n", + value, locale_key, kf->main_group, *c); + flag = FALSE; + } + break; + + default: + PRINT_INVALID_IF_FLAG; + break; } - g_strfreev (vals); + c++; + } + + if (in_quote) { + print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" contains a " + "quote which is not closed\n", + value, locale_key, kf->main_group); + retval = FALSE; + } + + if (flag) { + print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" contains a " + "non-complete field code\n", + value, locale_key, kf->main_group); + retval = FALSE; + } + + return retval; } -static void -validate_only_show_in (const char *value, const char *key, const char *locale, const char *filename, GnomeDesktopFile *df) +/* + If entry is of type Application, the working directory to run the program + * in. (probably implies an absolute path) + * Checked. + */ +static gboolean +handle_path_key (kf_validator *kf, + const char *locale_key, + const char *value) { - const char *onlyshowin_keys[] = { - "KDE", "GNOME", "ROX", "XFCE", "Old", NULL - }; - char **vals; - int i; + handle_key_for_application (kf, locale_key, value); - validate_strings (value, key, locale, filename, df); + if (!g_path_is_absolute (value)) + print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "does not look like an absolute path\n", + value, locale_key, kf->main_group); - vals = g_strsplit (value, ";", G_MAXINT); + return TRUE; +} - i = 0; - while (vals[i]) - ++i; +/* + The MIME type(s) supported by this application. Check they are valid + * MIME types. + * Checked. + * FIXME: need to verify what is the exact definition of a MIME type. + * Look at is_valid_mime_type() + */ +static gboolean +handle_mime_key (kf_validator *kf, + const char *locale_key, + const char *value) +{ + gboolean retval; + char **types; + char *slash; + GHashTable *hashtable; + int i; + + handle_key_for_application (kf, locale_key, value); - if (i == 0) - { - g_strfreev (vals); - return; + retval = TRUE; + + hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); + types = g_strsplit (value, ";", 0); + + for (i = 0; types[i]; i++) { + /* since the value ends with a semicolon, we'll have an empty string + * at the end */ + if (*types[i] == '\0' && types[i + 1] == NULL) + break; + + if (g_hash_table_lookup (hashtable, types[i])) { + print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "contains \"%s\" more than once\n", + value, locale_key, kf->main_group, types[i]); + continue; } - - /* Drop the empty string g_strsplit leaves in the vector since - * our list of strings ends in ";" - */ - --i; - g_free (vals[i]); - vals[i] = NULL; - i = 0; - while (vals[i]) - { - int j = 0; - - while (onlyshowin_keys[j]) - { - if (g_ascii_strcasecmp (vals[i], onlyshowin_keys[j]) == 0) - { - if (strcmp (vals[i], onlyshowin_keys[j]) != 0) - { - print_fatal (filename, "%s values are case sensitive (should be \"%s\" instead of \"%s\")\n", - key, onlyshowin_keys[j], vals[i]); - } - break; - } - ++j; - } - - if (onlyshowin_keys[j] == NULL) - { - char *valid_onlyshowins; - - valid_onlyshowins = g_strjoinv ("\", \"", (gchar **) onlyshowin_keys); - print_fatal (filename, "%s values must be one of \"%s\" (found \"%s\")\n", - key, valid_onlyshowins, vals[i]); - g_free (valid_onlyshowins); - } - ++i; + g_hash_table_insert (hashtable, types[i], types[i]); + + slash = strchr (types[i], '/'); + if (!slash || strchr (slash + 1, '/')) { + print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "contains value \"%s\" which does not look like " + "a MIME type\n", + value, locale_key, kf->main_group, types[i]); + retval = FALSE; } + } + + g_strfreev (types); + g_hash_table_destroy (hashtable); - g_strfreev (vals); + return retval; } -static void -validate_localestring (const char *value, const char *key, const char *locale, const char *filename, GnomeDesktopFile *df) +/* + FIXME: is there restrictions on how a category should be named? + * + Categories in which the entry should be shown in a menu (for possible + * values see the Desktop Menu Specification). + * Checked. + * + The table below describes Reserved Categories. Reserved Categories have a + * specific desktop specific meaning that has not been standardized (yet). + * Desktop entry files that use a reserved category MUST also include an + * appropriate OnlyShowIn= entry to restrict themselves to those environments + * that properly support the reserved category as used. + * Checked. + * + Accept "Application" as a deprecated category. + * Checked. + * FIXME: it's not really deprecated, so the error message is wrong + * + All categories extending the format should start with "X-". + * Checked. + */ +static gboolean +handle_categories_key (kf_validator *kf, + const char *locale_key, + const char *value) { - char *k; - const char *encoding; - char *res; - GError *error; + gboolean retval; + char **categories; + GHashTable *hashtable; + int i; + unsigned int j; - if (locale) - k = g_strdup_printf ("%s[%s]", key, locale); - else - k = g_strdup_printf ("%s", key); + handle_key_for_application (kf, locale_key, value); + + retval = TRUE; + + hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); + categories = g_strsplit (value, ";", 0); + for (i = 0; categories[i]; i++) { + /* since the value ends with a semicolon, we'll have an empty string + * at the end */ + if (*categories[i] == '\0' && categories[i + 1] == NULL) + break; - if (gnome_desktop_file_get_encoding (df) == GNOME_DESKTOP_FILE_ENCODING_UTF8) - { - if (!g_utf8_validate (value, -1, NULL)) - print_fatal (filename, "value for key \"%s\" contains invalid UTF-8 characters, even though the encoding is UTF-8\n", k); + if (g_hash_table_lookup (hashtable, categories[i])) { + print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "contains \"%s\" more than once\n", + value, locale_key, kf->main_group, categories[i]); + continue; } - else if (gnome_desktop_file_get_encoding (df) == GNOME_DESKTOP_FILE_ENCODING_LEGACY) - { - if (locale) - { - encoding = desktop_file_get_encoding_for_locale (locale); - - if (encoding) - { - error = NULL; - res = g_convert (value, -1, - "UTF-8", - encoding, - NULL, - NULL, - &error); - if (!res && error && error->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE) - print_fatal (filename, "value for key \"%s\" contains characters that are invalid in the \"%s\" encoding\n", k, encoding); - else if (!res && error && error->code == G_CONVERT_ERROR_NO_CONVERSION) - print_warning (filename, "encoding \"%s\" for key \"%s\" is not supported by iconv\n", encoding, k); - - g_free (res); - } - else - print_fatal (filename, "no encoding specified for locale \"%s\"\n", locale); - } - else - { - guchar *p = (guchar *)value; - gboolean ok = TRUE; - /* non-translated strings in legacy-mixed has to be ascii. */ - while (*p) - { - if (*p > 127) - { - ok = FALSE; - break; - } - - p++; - } - if (!ok) - print_fatal (filename, "untranslated localestring key \"%s\" has non-ASCII characters in its value\n", key); - } + + g_hash_table_insert (hashtable, categories[i], categories[i]); + + if (!strncmp (categories[i], "X-", 2)) + continue; + +#define IF_CHECK_REGISTERED_CATEGORIES(table) \ + for (j = 0; j < G_N_ELEMENTS (table); j++) { \ + if (!strcmp (categories[i], table[j])) \ + break; \ + } \ + if (j != G_N_ELEMENTS (table)) + + IF_CHECK_REGISTERED_CATEGORIES (main_categories_registered) + continue; + IF_CHECK_REGISTERED_CATEGORIES (additional_categories_registered) + continue; + IF_CHECK_REGISTERED_CATEGORIES (reserved_categories_registered) { + if (!g_key_file_has_key (kf->keyfile, kf->main_group, + "OnlyShowIn", NULL)) { + print_fatal (kf, "value \"%s\" in key \"%s\" in group \"%s\" " + "is a reserved category, so a \"OnlyShowIn\" key " + "must be included\n", + categories[i], locale_key, kf->main_group); + retval = FALSE; + } + continue; + } + IF_CHECK_REGISTERED_CATEGORIES (deprecated_categories_registered) { + if (!kf->no_deprecated_warnings) + print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "contains a deprecated value \"%s\"\n", + value, locale_key, kf->main_group, + categories[i]); + continue; } - g_free (k); + print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "contains an unregistered value \"%s\"\n", + value, locale_key, kf->main_group, categories[i]); + retval = FALSE; + } + + g_strfreev (categories); + g_hash_table_destroy (hashtable); + + return retval; } -static void -validate_regexps (const char *value, const char *key, const char *locale, const char *filename, GnomeDesktopFile *df) -{ - const char *p; - gboolean ok = TRUE; - char *k; - - p = value; - while (*p) - { - if (!(g_ascii_isprint (*p) || *p == '\n' || *p == '\t')) - { - ok = FALSE; - break; - } - - p++; +/* FIXME: we don't know the format for this, so we'll just assume that it's + * always valid... + * This could be wrong because we could use the characters that are + * valid for a group name. And also, since it's strings, it should be only + * characters accepted for string values. + */ +static gboolean +handle_actions_key (kf_validator *kf, + const char *locale_key, + const char *value) +{ + char **actions; + char *action; + int i; + + actions = g_strsplit (value, ";", 0); + + for (i = 0; actions[i]; i++) { + /* since the value ends with a semicolon, we'll have an empty string + * at the end */ + if (*actions[i] == '\0') { + if (actions[i + 1] != NULL) + print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "contains an empty action\n", + value, locale_key, kf->main_group); + continue; } - if (!ok) - { - if (locale) - k = g_strdup_printf ("%s[%s]", key, locale); - else - k = g_strdup_printf ("%s", key); - print_fatal (filename, "invalid characters in value of key \"%s\", keys of type regexps may contain ASCII characters except control characters\n", k); - g_free (k); + if (g_hash_table_lookup (kf->action_values, actions[i])) { + print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "contains action \"%s\" more than once\n", + value, locale_key, kf->main_group, actions[i]); + continue; } + + action = g_strdup (actions[i]); + g_hash_table_insert (kf->action_values, action, action); + } + + g_strfreev (actions); + + return TRUE; } -static void -validate_boolean (const char *value, const char *key, const char *locale, const char *filename, GnomeDesktopFile *df) +/* + The device to mount. (probably implies an absolute path) + * Checked. + */ +static gboolean +handle_dev_key (kf_validator *kf, + const char *locale_key, + const char *value) { - if (strcmp (value, "true") != 0 && - strcmp (value, "false") != 0) - print_fatal (filename, "invalid characters in value of key \"%s\", boolean values must be \"false\" or \"true\" (found \"%s\")\n", key, value); - + handle_key_for_fsdevice (kf, locale_key, value); + + if (!g_path_is_absolute (value)) + print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "does not look like an absolute path\n", + value, locale_key, kf->main_group); + + return TRUE; } -static void -validate_boolean_or_01 (const char *value, const char *key, const char *locale, const char *filename, GnomeDesktopFile *df) +/* + The mount point of the device in question. (probably implies an absolute + * path) + * Checked. + */ +static gboolean +handle_mountpoint_key (kf_validator *kf, + const char *locale_key, + const char *value) { - if (strcmp (value, "true") != 0 && - strcmp (value, "false") != 0 && - strcmp (value, "0") != 0 && - strcmp (value, "1") != 0) - print_fatal (filename, "invalid characters in value of key \"%s\", boolean values must be \"false\" or \"true\" (found \"%s\")\n", key, value); + handle_key_for_fsdevice (kf, locale_key, value); - if (strcmp (value, "0") == 0 || - strcmp (value, "1") == 0) - print_warning (filename, "boolean key \"%s\" has value \"%s\", boolean values should be \"false\" or \"true\", although \"0\" and \"1\" are allowed in this field for backwards compatibility\n", key, value); + if (!g_path_is_absolute (value)) + print_warning (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "does not look like an absolute path\n", + value, locale_key, kf->main_group); + + return TRUE; } -static void -validate_numeric (const char *value, const char *key, const char *locale, const char *filename, GnomeDesktopFile *df) +/* + Possible values are UTF-8 and Legacy-Mixed. + * Checked. + */ +static gboolean +handle_encoding_key (kf_validator *kf, + const char *locale_key, + const char *value) { - float d; - int res; - - res = sscanf( value, "%f", &d); - if (res == 0) - print_fatal (filename, "numeric key \"%s\" has value \"%s\", which doesn't look like a number\n", key, value); + if (!strcmp (value, "UTF-8") || !strcmp (value, "Legacy-Mixed")) + return TRUE; + + print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" " + "is not a registered encoding value (\"UTF-8\", and " + "\"Legacy-Mixed\")\n", + value, locale_key, kf->main_group); + + return FALSE; } -struct { - char *keyname; - void (*validate_type) (const char *value, const char *key, const char *locale, const char *filename, GnomeDesktopFile *df); - gboolean deprecated; -} key_table[] = { - { "Encoding", validate_string }, - { "Version", validate_numeric }, - { "Name", validate_localestring }, - { "GenericName", validate_localestring }, - { "Type", validate_string }, - { "FilePattern", validate_regexps }, - { "TryExec", validate_string }, - { "NoDisplay", validate_boolean }, - { "Comment", validate_localestring }, - { "Exec", validate_string }, - { "Actions", validate_strings }, - { "Icon", validate_string }, - { "MiniIcon", validate_string, TRUE }, /* 0.9.4: deprecated */ - { "Hidden", validate_boolean }, - { "Path", validate_string }, - { "Terminal", validate_boolean_or_01 }, - { "TerminalOptions", validate_string, TRUE }, /* 0.9.4: deprecated */ - { "SwallowTitle", validate_localestring }, - { "SwallowExec", validate_string }, - { "MimeType", validate_regexps }, - { "Patterns", validate_regexps, TRUE }, /* 0.9.4: deprecated */ - { "DefaultApp", validate_string, TRUE }, /* 0.9.4: deprecated */ - { "Dev", validate_string }, - { "FSType", validate_string }, - { "MountPoint", validate_string }, - { "ReadOnly", validate_boolean_or_01 }, - { "UnmountIcon", validate_string }, - { "SortOrder", validate_strings /* FIXME: Also comma-separated */}, - { "URL", validate_string }, - { "Categories", validate_categories }, - { "OnlyShowIn", validate_only_show_in }, - { "NotShowIn", validate_only_show_in }, - { "StartupNotify", validate_boolean }, - { "StartupWMClass", validate_string }, - { "BinaryPattern", validate_string, TRUE }, /* 0.9.3: deprecated */ - { "DocPath", validate_string }, /* 0.9.4: within KDE only */ - { "Extensions", validate_string, TRUE }, /* 0.9.3: deprecated */ - { "InitialPreference", validate_string }, /* 0.9.4: within KDE only */ - { "Keywords", validate_localestring }, /* 0.9.4: within KDE only */ - { "MapNotify", validate_string, TRUE }, /* 0.9.3: deprecated */ - { "Protocols", validate_string, TRUE }, /* 0.9.3: deprecated */ - { "ServiceTypes", validate_string }, /* 0.9.4: within KDE only */ -}; +static gboolean +handle_key_for_application (kf_validator *kf, + const char *locale_key, + const char *value) +{ + kf->application_keys = g_list_append (kf->application_keys, + g_strdup (locale_key)); + return TRUE; +} -static void -enum_keys (GnomeDesktopFile *df, - const char *key, /* If NULL, value is comment line */ - const char *locale, - const char *value, /* This is raw unescaped data */ - gpointer user_data) -{ - struct KeyData *data = user_data; - struct KeyHashData *hash_data; - const char *p; - int i; - - if (key == NULL) - { - if (!g_utf8_validate (value, -1, NULL)) - print_warning (data->filename, "file contains non-UTF-8 comments\n"); +static gboolean +handle_key_for_link (kf_validator *kf, + const char *locale_key, + const char *value) +{ + kf->link_keys = g_list_append (kf->link_keys, + g_strdup (locale_key)); + return TRUE; +} - return; - } +static gboolean +handle_key_for_fsdevice (kf_validator *kf, + const char *locale_key, + const char *value) +{ + kf->fsdevice_keys = g_list_append (kf->fsdevice_keys, + g_strdup (locale_key)); + return TRUE; +} - hash_data = g_hash_table_lookup (data->hash, key); - if (hash_data == NULL) - { - hash_data = g_new0 (struct KeyHashData, 1); - g_hash_table_insert (data->hash, (char *)key, hash_data); - } +static gboolean +handle_key_for_mimetype (kf_validator *kf, + const char *locale_key, + const char *value) +{ + kf->mimetype_keys = g_list_append (kf->mimetype_keys, + g_strdup (locale_key)); + return TRUE; +} - if (locale == NULL) { - if (hash_data->has_non_translated) - print_fatal (data->filename, "file contains multiple assignments of key \"%s\"\n", key); - - hash_data->has_non_translated = TRUE; - } else { - hash_data->has_translated = TRUE; +/* + Key names must contain only the characters A-Za-z0-9-. + * Checked. + * + LOCALE must be of the form lang_COUNTRY.ENCODING@MODIFIER, where _COUNTRY, + * .ENCODING, and @MODIFIER may be omitted. + * Checked. + */ +static gboolean +key_extract_locale (const char *key, + char **real_key, + char **locale) +{ + const char *start_locale; + char c; + int len; + int i; + + if (real_key) + *real_key = NULL; + if (locale) + *locale = NULL; + + start_locale = g_strrstr (key, "["); + + if (start_locale) + len = start_locale - key; + else + len = strlen (key); + + for (i = 0; i < len; i++) { + c = key[i]; + if (!g_ascii_isalnum (c) && c != '-') + return FALSE; + } + + if (!start_locale) { + if (real_key) + *real_key = g_strdup (key); + if (locale) + *locale = NULL; + + return TRUE; + } + + len = strlen (start_locale); + if (len <= 2 || start_locale[len - 1] != ']') + return FALSE; + + /* ignore first [ and last ] */ + for (i = 1; i < len - 2; i++) { + c = start_locale[i]; + if (!g_ascii_isalnum (c) && c != '-' && c != '_' && c != '.' && c != '@') + return FALSE; } -#ifdef VERIFY_CANONICAL_ENCODING_NAME + if (real_key) + *real_key = g_strndup (key, strlen (key) - len); if (locale) - { - const char *encoding; - const char *canonical; - - encoding = strchr(locale, '.'); - - if (encoding) - { - encoding++; - - canonical = get_canonical_encoding (encoding); - if (strcmp (encoding, canonical) != 0) - print_warning (data->filename, "non-canonical encoding \"%s\" specified, the canonical name of the encoding is \"%s\"\n", encoding, canonical); - } - } -#endif + *locale = g_strndup (start_locale + 1, len - 2); - for (i = 0; i < (int) G_N_ELEMENTS (key_table); i++) - { - if (strcmp (key_table[i].keyname, key) == 0) - break; - } + return TRUE; +} - if (i < (int) G_N_ELEMENTS (key_table)) - { - if (key_table[i].validate_type) - (*key_table[i].validate_type) (value, key, locale, data->filename, df); - if (key_table[i].deprecated) - print_warning (data->filename, "file contains key \"%s\", usage of this key is not recommended, since it has been deprecated\n", key); - if (strcmp (key, "ServiceTypes") == 0 || - strcmp (key, "DocPath") == 0 || - strcmp (key, "Keywords") == 0 || - strcmp (key, "InitialPreference") == 0) - print_warning (data->filename, "file contains key \"%s\", this key is currently reserved for use within KDE, and should in the future KDE releases be prefixed by \"X-\"\n", key); +/* + All keys extending the format should start with "X-". + * Checked. + */ +static gboolean +validate_desktop_key (kf_validator *kf, + const char *locale_key, + const char *key, + const char *locale) +{ + unsigned int i; + unsigned int j; + char *value; + + if (!strncmp (key, "X-", 2)) + return TRUE; + + for (i = 0; i < G_N_ELEMENTS (registered_desktop_keys); i++) { + if (strcmp (key, registered_desktop_keys[i].name)) + continue; + + if (registered_desktop_keys[i].type != DESKTOP_LOCALESTRING_TYPE && + locale != NULL) { + print_fatal (kf, "file contains key \"%s\" in group \"%s\", " + "but \"%s\" is not defined as a locale string\n", + locale_key, kf->main_group, key); + return FALSE; } - else - { - if (strncmp (key, "X-", 2) != 0) - print_warning (data->filename, "non-standard key \"%s\" lacks the \"X-\" prefix\n", key); + + for (j = 0; j < G_N_ELEMENTS (validate_for_type); j++) { + if (validate_for_type[j].type == registered_desktop_keys[i].type) + break; } - /* Validation of specific keys */ + g_assert (j != G_N_ELEMENTS (validate_for_type)); + + if (!kf->no_deprecated_warnings && registered_desktop_keys[i].deprecated) + print_warning (kf, "key \"%s\" in group \"%s\" is deprecated\n", + locale_key, kf->main_group); + + if (registered_desktop_keys[i].kde_reserved && kf->kde_reserved_warnings) + print_warning (kf, "key \"%s\" in group \"%s\" is a reserved key for " + "KDE\n", + locale_key, kf->main_group); + + value = g_key_file_get_value (kf->keyfile, kf->main_group, + locale_key, NULL); + + /* this is not supposed to happen since we got the key from the list of + * existing keys */ + g_assert (value != NULL); - if (strcmp (key, "Icon") == 0) - { -#if 0 - /* With new icon theme spec we allow this */ - if (strchr (value, '.') == NULL) - print_warning (data->filename, "icon \"%s\" specified does not seem to contain a filename extension\n", value); -#endif + if (!validate_for_type[j].validate (kf, key, locale, value)) { + g_free (value); + return FALSE; } - - if (strcmp (key, "Exec") == 0) - { - if (strstr (value, "NO_XALF") != NULL) - print_fatal (data->filename, "the Exec string includes the nonstandard, broken NO_XALF prefix\n"); - - p = value; - while (*p) - { - if (*p == '%') - { - p++; - if (*p != 'f' && *p != 'F' && - *p != 'u' && *p != 'U' && - *p != 'd' && *p != 'D' && - *p != 'n' && *p != 'N' && - *p != 'i' && *p != 'm' && - *p != 'c' && *p != 'k' && - *p != 'v' && *p != '%') - print_fatal (data->filename, "the Exec string includes the non-standard parameter \"%%%c\"\n", *p); - if (*p == 0) - break; - } - p++; - } + + if (registered_desktop_keys[i].handle_and_validate != NULL) { + if (!registered_desktop_keys[i].handle_and_validate (kf, locale_key, + value)) { + g_free (value); + return FALSE; + } } - -} + g_free (value); + break; + } -static void -enum_hash_keys (gpointer key, - gpointer value, - gpointer user_data) -{ - struct KeyData *data = user_data; - struct KeyHashData *hash_data = value; + if (i == G_N_ELEMENTS (registered_desktop_keys)) { + print_fatal (kf, "file contains key \"%s\" in group \"%s\", but " + "keys extending the format should start with " + "\"X-\"\n", key, kf->main_group); + return FALSE; + } - if (hash_data->has_translated && - !hash_data->has_non_translated) - print_fatal (data->filename, "key \"%s\" is translated, but no untranslated version exists\n", (char *)key); - + return TRUE; } -static void -generic_keys (GnomeDesktopFile *df, const char *filename) +/* + Multiple keys in the same group may not have the same name. + * Checked. + */ +static gboolean +validate_keys_for_group (kf_validator *kf, + const char *group) { - struct KeyData data = {0 }; - - data.hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); - data.filename = filename; - - gnome_desktop_file_foreach_key (df, NULL, TRUE, - enum_keys, &data); + gboolean desktop_group; + gboolean retval; + int i; + char *key; + char *locale; + char **keys; + GHashTable *hashtable; - g_hash_table_foreach (data.hash, - enum_hash_keys, - &data); + retval = TRUE; - g_hash_table_destroy (data.hash); -} + desktop_group = (!strcmp (group, GROUP_DESKTOP_ENTRY) || + !strcmp (group, GROUP_KDE_DESKTOP_ENTRY)); -struct SectionData { - GHashTable *hash; - const char *filename; - const char *main_section; - gboolean has_kde_desktop_entry; -}; + keys = g_key_file_get_keys (kf->keyfile, group, NULL, NULL); -static void -enum_sections (GnomeDesktopFile *df, - const char *name, - gpointer data) -{ - struct SectionData *section = data; - - /* Initial sections (for any comments at top of - * file, etc) have name == NULL. - */ - if (!name) - return; - - if (strcmp (name, "Desktop Entry") == 0 || - strcmp (name, "KDE Desktop Entry") == 0) - { - if (!section->main_section) - { - section->main_section = name; - } - else - { - print_fatal (section->filename, "file already contains section %s, should not contain another section %s\n", section->main_section, name); - } - - if (strcmp (name, "KDE Desktop Entry") == 0) - section->has_kde_desktop_entry = TRUE; - } - else if (strncmp (name, "Desktop Action ", 15) != 0 && - strncmp (name, "X-", 2) != 0) - { - print_fatal (section->filename, "file contains section %s, extensions to the spec should use section names starting with \"X-\".\n", name); + g_assert (keys != NULL); + + hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); + + for (i = 0; keys[i] != NULL; i++) { + if (!key_extract_locale (keys[i], &key, &locale)) { + print_fatal (kf, "file contains key \"%s\" in group \"%s\", but " + "key names must contain only the characters " + "A-Za-z0-9- (they may have a \"[LOCALE]\" postfix)\n", + keys[i], group); + retval = FALSE; + + key = g_strdup (keys[i]); } - if (g_hash_table_lookup (section->hash, name)) - print_fatal (section->filename, "file contains multiple sections named %s\n", name); - else - g_hash_table_insert (section->hash, (char *)name, (char *)name); -} + g_assert (key != NULL); -static const char * -required_section (GnomeDesktopFile *df, const char *filename) -{ - struct SectionData section; - - section.hash = g_hash_table_new (g_str_hash, g_str_equal); - section.filename = filename; - section.main_section = NULL; - section.has_kde_desktop_entry = FALSE; - - gnome_desktop_file_foreach_section (df, enum_sections, §ion); - - if (!section.main_section) - { - print_fatal (filename, "file doesn't contain a \"Desktop Entry\" section\n"); + if (g_hash_table_lookup (hashtable, keys[i])) { + print_fatal (kf, "file contains multiple keys named \"%s\" in " + "group \"%s\"\n", keys[i], group); + retval = FALSE; } - else if (section.has_kde_desktop_entry) - { - print_warning (filename, "file contains a \"KDE Desktop Entry\" section, this has been deprecated in favor of \"Desktop Entry\"\n"); + + if (desktop_group) { + if (!validate_desktop_key (kf, keys[i], key, locale)) + retval = FALSE; } - g_hash_table_destroy (section.hash); - - return section.main_section; + g_hash_table_insert (hashtable, keys[i], keys[i]); + + g_free (key); + key = NULL; + g_free (locale); + locale = NULL; + } + + g_hash_table_destroy (hashtable); + g_strfreev (keys); + + return retval; } static gboolean -required_keys (GnomeDesktopFile *df, const char *section, const char *filename) +validate_group_name (kf_validator *kf, + const char *group) { - const char *val; - - if (gnome_desktop_file_get_raw (df, section, - "Encoding", - NULL, &val)) - { - if (strcmp (val, "UTF-8") != 0 && - strcmp (val, "Legacy-Mixed") != 0) - print_fatal (filename, "unknown Encoding type \"%s\", should be one of \"UTF-8\", \"Legacy-Mixed\"\n", val); - } - else - { - print_fatal (filename, "required key \"Encoding\" not found\n"); + int i; + char c; + + for (i = 0; group[i] != '\0'; i++) { + c = group[i]; + if (!g_ascii_isprint (c) || c == '[' || c == ']') { + print_fatal (kf, "file contains group \"%s\", but group names " + "may contain all ASCII characters except for [ " + "and ] and control characters\n", group); + return FALSE; } + } - if (!gnome_desktop_file_get_raw (df, section, - "Name", - NULL, &val)) - { - print_fatal (filename, "required key \"Name\" not found\n"); - } + if (!strncmp (group, "X-", 2)) + return TRUE; - if (gnome_desktop_file_get_raw (df, section, - "Type", - NULL, &val)) - { - if (strcmp (val, "Application") != 0 && - strcmp (val, "Link") != 0 && - strcmp (val, "FSDevice") != 0 && - strcmp (val, "Directory") != 0) - { - if (strcmp (val, "MimeType") == 0) - { - print_warning (filename, "file specifies \"Type=MimeType\", usage of the \"MimeType\" value for \"Type\" is not recommended, since it has been deprecated\n"); - } - else if (strcmp (val, "Service") == 0 || - strcmp (val, "ServiceType") == 0) - { - print_warning (filename, "file specifies \"Type=%s\", this value for \"Type\" is currently reserved for use within KDE, and should in future KDE releases be prefixed by \"X-\"\n", val); - } - else - { - print_fatal (filename, "invalid Type \"%s\"\n", val); - } - } - } - else - { - print_fatal (filename, "required key \"Type\" not found\n"); + if (!strcmp (group, GROUP_DESKTOP_ENTRY)) { + if (kf->main_group && !strcmp (kf->main_group, GROUP_KDE_DESKTOP_ENTRY)) + print_warning (kf, "file contains groups \"%s\" and \"%s\", which play " + "the same role\n", + GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY); + + kf->main_group = GROUP_DESKTOP_ENTRY; + + return TRUE; + } + + if (!strcmp (group, GROUP_KDE_DESKTOP_ENTRY)) { + if (kf->kde_reserved_warnings || !kf->no_deprecated_warnings) + print_warning (kf, "file contains group \"%s\", which is deprecated " + "in favor of \"%s\"\n", group, GROUP_DESKTOP_ENTRY); + + if (kf->main_group && !strcmp (kf->main_group, GROUP_DESKTOP_ENTRY)) + print_warning (kf, "file contains groups \"%s\" and \"%s\", which play " + "the same role\n", + GROUP_DESKTOP_ENTRY, GROUP_KDE_DESKTOP_ENTRY); + + kf->main_group = GROUP_KDE_DESKTOP_ENTRY; + + return TRUE; + } + + if (!strncmp (group, GROUP_DESKTOP_ACTION, strlen (GROUP_DESKTOP_ACTION))) { + if (group[strlen (GROUP_DESKTOP_ACTION) - 1] == '\0') { + print_fatal (kf, "file contains group \"%s\", which is an action " + "group with no action name\n", group); + return FALSE; + } else { + char *action; + + action = g_strdup (group + strlen (GROUP_DESKTOP_ACTION)); + g_hash_table_insert (kf->action_groups, action, action); + + return TRUE; } - return TRUE; -} + } -struct ActionsData { - GHashTable *hash; - const char *filename; -}; + print_fatal (kf, "file contains group \"%s\", but groups extending " + "the format should start with \"X-\"\n", group); + return FALSE; +} -static void -enum_actions (GnomeDesktopFile *df, - const char *section, - gpointer data) +/* + Only comments are accepted before the first group. + * FIXME: verify that GKeyFile handles this. + * + Using [KDE Desktop Entry] instead of [Desktop Entry] as header is + * deprecated. + * Checked. + * + The first group should be "Desktop Entry". + * Checked. + * + Group names may contain all ASCII characters except for [ and ] and + * control characters. + * Checked. + * + Multiple groups may not have the same name. + * FIXME: GKeyFile can't let us verify this. + * + All groups extending the format should start with "X-". + * Checked. + * + Accept "Desktop Action foobar" group if the value for the Action key + * contains "foobar". (This is not in spec 1.0, but it was there before and + * it wasn't deprecated) + * Checked. + */ +static gboolean +validate_groups_and_keys (kf_validator *kf) { - struct ActionsData *actions_data = data; - const char *val; - const char *action; + gboolean retval; + int i; + char *group; + char **groups; - /* Initial sections (for any comments at top of - * file, etc) have name == NULL. - */ - if (!section) - return; + retval = TRUE; + + group = g_key_file_get_start_group (kf->keyfile); + if (!group || + (strcmp (group, GROUP_DESKTOP_ENTRY) && + strcmp (group, GROUP_KDE_DESKTOP_ENTRY))) + print_fatal (kf, "first group is not \"" GROUP_DESKTOP_ENTRY "\"\n"); + g_free (group); + + groups = g_key_file_get_groups (kf->keyfile, NULL); + i = 0; + for (i = 0; groups[i] != NULL; i++) { + group = groups[i]; + + if (!validate_group_name (kf, group)) + retval = FALSE; - if (strncmp (section, "Desktop Action ", 15) != 0) - return; + if (!validate_keys_for_group (kf, group)) + retval = FALSE; + } - action = section + 15; + g_strfreev (groups); + return retval; +} - /* Already verified this */ - g_assert (!g_hash_table_lookup (actions_data->hash, action)); +static gboolean +validate_required_keys (kf_validator *kf) +{ + gboolean retval; + unsigned int i; - g_hash_table_insert (actions_data->hash, (char *) action, (char *) action); + retval = TRUE; - if (!gnome_desktop_file_get_raw (df, section, - "Exec", - NULL, &val)) - { - print_fatal (actions_data->filename, "file contains \"Desktop Action %s\" section which lacks an Exec key\n", section); + for (i = 0; i < G_N_ELEMENTS (registered_desktop_keys); i++) { + if (registered_desktop_keys[i].required) { + if (!g_key_file_has_key (kf->keyfile, kf->main_group, + registered_desktop_keys[i].name, NULL)) { + print_fatal (kf, "required key \"%s\" in group \"%s\" is not " + "present\n", + registered_desktop_keys[i].name, kf->main_group); + retval = FALSE; + } } + } + + return retval; } -static void -error_orphaned_action (gpointer key, - gpointer value, - gpointer user_data) +#define PRINT_ERROR_FOREACH_KEY(lower, real) \ +static void \ +print_error_foreach_##lower##_key (const char *name, \ + kf_validator *kf) \ +{ \ + print_fatal (kf, "key \"%s\" is present in group \"%s\", but the type is " \ + "\"%s\" while this key is only valid for type \"%s\"\n", \ + name, kf->main_group, kf->type_string, real); \ +} + +PRINT_ERROR_FOREACH_KEY (application, "Application") +PRINT_ERROR_FOREACH_KEY (link, "Link") +PRINT_ERROR_FOREACH_KEY (fsdevice, "FSDevice") +PRINT_ERROR_FOREACH_KEY (mimetype, "MimeType") + +static gboolean +validate_type_keys (kf_validator *kf) { - const char *action = key; - const char *filename = user_data; + gboolean retval; - print_fatal (filename, "file contains \"Desktop Action %s\" but Actions key does not contain '%s'\n", action, action); + retval = TRUE; + + switch (kf->type) { + case INVALID_TYPE: + break; + case APPLICATION_TYPE: + g_list_foreach (kf->link_keys, + (GFunc) print_error_foreach_link_key, kf); + g_list_foreach (kf->fsdevice_keys, + (GFunc) print_error_foreach_fsdevice_key, kf); + g_list_foreach (kf->mimetype_keys, + (GFunc) print_error_foreach_mimetype_key, kf); + retval = (g_list_length (kf->link_keys) + + g_list_length (kf->fsdevice_keys) + + g_list_length (kf->mimetype_keys) == 0); + break; + case LINK_TYPE: + g_list_foreach (kf->application_keys, + (GFunc) print_error_foreach_application_key, kf); + g_list_foreach (kf->fsdevice_keys, + (GFunc) print_error_foreach_fsdevice_key, kf); + g_list_foreach (kf->mimetype_keys, + (GFunc) print_error_foreach_mimetype_key, kf); + retval = (g_list_length (kf->application_keys) + + g_list_length (kf->fsdevice_keys) + + g_list_length (kf->mimetype_keys) == 0); + break; + case DIRECTORY_TYPE: + case SERVICE_TYPE: + case SERVICE_TYPE_TYPE: + g_list_foreach (kf->application_keys, + (GFunc) print_error_foreach_application_key, kf); + g_list_foreach (kf->link_keys, + (GFunc) print_error_foreach_link_key, kf); + g_list_foreach (kf->fsdevice_keys, + (GFunc) print_error_foreach_fsdevice_key, kf); + g_list_foreach (kf->mimetype_keys, + (GFunc) print_error_foreach_mimetype_key, kf); + retval = (g_list_length (kf->application_keys) + + g_list_length (kf->link_keys) + + g_list_length (kf->fsdevice_keys) + + g_list_length (kf->mimetype_keys) == 0); + break; + case FSDEVICE_TYPE: + g_list_foreach (kf->application_keys, + (GFunc) print_error_foreach_application_key, kf); + g_list_foreach (kf->link_keys, + (GFunc) print_error_foreach_link_key, kf); + g_list_foreach (kf->mimetype_keys, + (GFunc) print_error_foreach_mimetype_key, kf); + retval = (g_list_length (kf->application_keys) + + g_list_length (kf->link_keys) + + g_list_length (kf->mimetype_keys) == 0); + break; + case MIMETYPE_TYPE: + g_list_foreach (kf->application_keys, + (GFunc) print_error_foreach_application_key, kf); + g_list_foreach (kf->link_keys, + (GFunc) print_error_foreach_link_key, kf); + g_list_foreach (kf->fsdevice_keys, + (GFunc) print_error_foreach_fsdevice_key, kf); + retval = (g_list_length (kf->application_keys) + + g_list_length (kf->link_keys) + + g_list_length (kf->fsdevice_keys) == 0); + break; + case LAST_TYPE: + g_assert_not_reached (); + } + + return retval; } static gboolean -required_actions (GnomeDesktopFile *df, const char *filename) +lookup_group_foreach_action (char *key, + char *value, + kf_validator *kf) { - struct ActionsData actions_data; - gboolean retval = FALSE; + if (g_hash_table_lookup (kf->action_groups, key)) { + g_hash_table_remove (kf->action_groups, key); + return TRUE; + } - actions_data.hash = g_hash_table_new (g_str_hash, g_str_equal); - actions_data.filename = filename; - - gnome_desktop_file_foreach_section (df, enum_actions, &actions_data); + return FALSE; +} - if (g_hash_table_size (actions_data.hash) > 0) - { - const char *val; - char **actions; - int i; +static void +print_error_foreach_action (char *key, + char *value, + kf_validator *kf) +{ + print_fatal (kf, "action \"%s\" is defined, but there is no matching " + "\"%s%s\" group\n", key, GROUP_DESKTOP_ACTION, key); +} - if (!gnome_desktop_file_get_raw (df, NULL, "Actions", NULL, &val)) - { - print_fatal (filename, "file has \"Desktop Action\" sections but no Action key\n"); - goto out; - } +static void +print_error_foreach_group (char *key, + char *value, + kf_validator *kf) +{ + print_fatal (kf, "action group \"%s%s\" exists, but there is no matching " + "action \"%s\"\n", GROUP_DESKTOP_ACTION, key, key); +} - actions = g_strsplit (val, ";", G_MAXINT); - for (i = 0; actions [i]; i++) - { - if (*actions [i] == '\0') - continue; +static gboolean +validate_actions (kf_validator *kf) +{ + g_hash_table_foreach_remove (kf->action_values, + (GHRFunc) lookup_group_foreach_action, kf); - if (!g_hash_table_lookup (actions_data.hash, actions [i])) - { - print_fatal (filename, "Action key contains '%s' but file has \"Desktop Action %s\" section\n", - actions [i], actions [i]); - goto out; - } + g_hash_table_foreach (kf->action_values, + (GHFunc) print_error_foreach_action, kf); - g_hash_table_remove (actions_data.hash, actions [i]); - } + g_hash_table_foreach (kf->action_groups, + (GHFunc) print_error_foreach_group, kf); - g_strfreev (actions); + return (g_hash_table_size (kf->action_values) + + g_hash_table_size (kf->action_groups) == 0); +} - if (g_hash_table_size (actions_data.hash) > 0) - { - g_hash_table_foreach (actions_data.hash, - error_orphaned_action, - (char *) filename); - goto out; - } +/* + These desktop entry files should have the extension .desktop. + * Checked. + * + Desktop entries which describe how a directory is to be + * formatted/displayed should be simply called .directory. + * Checked. + * + Using .kdelnk instead of .desktop as the file extension is deprecated. + * Checked. + * FIXME: we're not doing what the spec says wrt Directory. + */ +static gboolean +validate_filename (kf_validator *kf) +{ + if (kf->type == DIRECTORY_TYPE) { + if (g_str_has_suffix (kf->filename, ".directory")) + return TRUE; + else { + print_fatal (kf, "file is of type \"Directory\", but filename does not " + "have a .directory extension\n"); + return FALSE; } + } - retval = TRUE; + if (g_str_has_suffix (kf->filename, ".desktop")) + return TRUE; - out: - g_hash_table_destroy (actions_data.hash); + if (g_str_has_suffix (kf->filename, ".kdelnk")) { + if (kf->kde_reserved_warnings || !kf->no_deprecated_warnings) + print_warning (kf, "filename has a .kdelnk extension, which is " + "deprecated in favor of .desktop\n"); + return TRUE; + } - return retval; + print_fatal (kf, "filename does not have a .desktop extension\n"); + return FALSE; } gboolean -desktop_file_validate (GnomeDesktopFile *df, const char *filename) +desktop_file_validate (const char *filename, + gboolean warn_kde, + gboolean no_warn_deprecated) { - const char *name; - const char *comment; - const char *main_section; + GError *error; + kf_validator kf; + + /* just a consistency check */ + g_assert (G_N_ELEMENTS (registered_types) == LAST_TYPE - 1); + + kf.filename = filename; + kf.keyfile = g_key_file_new (); + kf.kde_reserved_warnings = warn_kde; + kf.no_deprecated_warnings = no_warn_deprecated; + + error = NULL; + if (!g_key_file_load_from_file (kf.keyfile, filename, + G_KEY_FILE_KEEP_COMMENTS| + G_KEY_FILE_KEEP_TRANSLATIONS, + &error)) { + print_fatal (&kf, "parse error: %s\n", error->message); + g_error_free (error); + g_key_file_free (kf.keyfile); + + return FALSE; + } - /* FIXME global variable cruft */ - fatal_error_occurred = FALSE; - - if ((main_section = required_section (df, filename)) == NULL) - return !fatal_error_occurred; - if (!required_keys (df, main_section, filename)) - return !fatal_error_occurred; - if (!required_actions (df, filename)) - return !fatal_error_occurred; - - generic_keys (df, filename); - - if (gnome_desktop_file_get_raw (df, NULL, "Name", NULL, &name) && - gnome_desktop_file_get_raw (df, NULL, "Comment", NULL, &comment)) - { - if (strcmp (name, comment) == 0) - print_warning (filename, "the fields \"Name\" and \"Comment\" have the same value\n"); - } + kf.main_group = NULL; + kf.type = INVALID_TYPE; + kf.type_string = NULL; + kf.show_in = FALSE; + kf.application_keys = NULL; + kf.link_keys = NULL; + kf.fsdevice_keys = NULL; + kf.mimetype_keys = NULL; + kf.action_values = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, g_free); + kf.action_groups = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, g_free); + kf.fatal_error = FALSE; + + validate_groups_and_keys (&kf); + //FIXME: this does not work well if there are both a Desktop Entry and a KDE + //Desktop Entry groups since only the last one will be validated for this. + if (kf.main_group) { + validate_required_keys (&kf); + validate_type_keys (&kf); + } + validate_actions (&kf); + validate_filename (&kf); + + g_list_foreach (kf.application_keys, (GFunc) g_free, NULL); + g_list_free (kf.application_keys); + g_list_foreach (kf.link_keys, (GFunc) g_free, NULL); + g_list_free (kf.link_keys); + g_list_foreach (kf.fsdevice_keys, (GFunc) g_free, NULL); + g_list_free (kf.fsdevice_keys); + g_list_foreach (kf.mimetype_keys, (GFunc) g_free, NULL); + g_list_free (kf.mimetype_keys); + + g_hash_table_destroy (kf.action_values); + g_hash_table_destroy (kf.action_groups); - return !fatal_error_occurred; + g_key_file_free (kf.keyfile); + + return (!kf.fatal_error); } /* return FALSE if we were unable to fix the file */ gboolean -desktop_file_fixup (GnomeDesktopFile *df, - const char *filename) +desktop_file_fixup (GKeyFile *kf, + const char *filename) { - const char *val; - gboolean fix_encoding; - const char *string_list_keys[] = { "Actions", "SortOrder", "Categories" }; - int i; - - if (gnome_desktop_file_has_section (df, "KDE Desktop Entry")) - { - g_printerr (_("%s: changing deprecated [KDE Desktop Entry] to plain [Desktop Entry]\n"), - filename); - gnome_desktop_file_rename_section (df, - "KDE Desktop Entry", - "Desktop Entry"); - } + char *value; + unsigned int i; - fix_encoding = FALSE; + if (g_key_file_has_group (kf, GROUP_KDE_DESKTOP_ENTRY)) { + g_printerr ("%s: renaming deprecated \"%s\" group to \"%s\"\n", + filename, GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY); + dfu_key_file_rename_group (kf, + GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY); + } - if (gnome_desktop_file_get_raw (df, NULL, - "Encoding", - NULL, &val)) - { - if (strcmp (val, "UTF-8") != 0 && - strcmp (val, "Legacy-Mixed") != 0) - { - g_printerr (_("%s: bogus encoding \"%s\" "), - filename, val); - fix_encoding = TRUE; - } - } - else - { - g_printerr (_("%s: missing encoding "), - filename); - fix_encoding = TRUE; - } - - if (fix_encoding) - { - /* If Encoding was missing or bogus, the desktop file parser guessed */ - switch (gnome_desktop_file_get_encoding (df)) - { - case GNOME_DESKTOP_FILE_ENCODING_LEGACY: - g_printerr (_(" (guessed Legacy-Mixed)\n")); - gnome_desktop_file_set_raw (df, NULL, "Encoding", NULL, "Legacy-Mixed"); - break; - case GNOME_DESKTOP_FILE_ENCODING_UTF8: - g_printerr (_(" (guessed UTF-8)\n")); - gnome_desktop_file_set_raw (df, NULL, "Encoding", NULL, "UTF-8"); - break; - case GNOME_DESKTOP_FILE_ENCODING_UNKNOWN: - g_printerr (_("\n%s: not enough data to guess encoding!\n"), - filename); - return FALSE; - break; - } + /* Fix lists to have a ';' at the end if they don't */ + for (i = 0; i < G_N_ELEMENTS (registered_desktop_keys); i++) { + if (registered_desktop_keys[i].type != DESKTOP_STRING_LIST_TYPE && + registered_desktop_keys[i].type != DESKTOP_REGEXP_LIST_TYPE) + continue; + + value = g_key_file_get_value (kf, GROUP_DESKTOP_ENTRY, + registered_desktop_keys[i].name, NULL); + if (value) { + int len; + + len = strlen (value); + + if (len > 0 && (value[len - 1] != ';' || + (len > 1 && value[len - 2] == '\\' && + (len < 3 || value[len - 3] != '\\')))) { + char *str; + + g_printerr ("%s: key \"%s\" is a list and does not have a " + "semicolon as trailing character, fixing\n", + filename, registered_desktop_keys[i].name); + + str = g_strconcat (value, ";", NULL); + g_key_file_set_value (kf, GROUP_DESKTOP_ENTRY, + registered_desktop_keys[i].name, str); + g_free (str); + } } + } - /* Fix string lists to have a ';' at the end if they don't */ - i = 0; - while (i < (int) G_N_ELEMENTS (string_list_keys)) - { - if (gnome_desktop_file_get_raw (df, NULL, - string_list_keys[i], - NULL, &val)) - { - int len; - - len = strlen (val); - - if (len > 0 && val[len-1] != ';') - { - char *str; - - g_printerr ("%s: key \"%s\" string list not semicolon-terminated, fixing\n", - filename, string_list_keys[i]); - - str = g_strconcat (val, ";", NULL); - gnome_desktop_file_set_raw (df, NULL, - string_list_keys[i], - NULL, str); - g_free (str); - } - } - - ++i; - } - return TRUE; } |