summaryrefslogtreecommitdiff
path: root/src/validate.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/validate.c')
-rw-r--r--src/validate.c2580
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, &section);
-
- 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;
}