summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVincent Untz <vuntz@gnome.org>2007-06-04 17:24:51 +0000
committerVincent Untz <vuntz@gnome.org>2007-06-04 17:24:51 +0000
commit14f6fe3f1eb4699d5020e2b4ea729419b898abc2 (patch)
tree4ba98e24611bedf0867a23bd03a60a211fd2cd15
parent4aab76b0fb470a1fcda17a6458ce210d44548478 (diff)
Don't use GKeyFile in the validator, so we really control everything.
2007-06-04 Vincent Untz <vuntz@gnome.org> Don't use GKeyFile in the validator, so we really control everything. * src/validate.c: remove some FIXME/TODO (validate_string_key): use g_ascii_iscntrl() instead of !g_ascii_isprint(), small update for the current group (validate_localestring_key): small update for the current group, don't use GKeyFile (validate_boolean_key): small update for the current group (validate_numeric_key): ditto (validate_string_regexp_list_key): use g_ascii_iscntrl() instead of !g_ascii_isprint(), small update for the current group (handle_type_key): small update for the current group (handle_version_key): ditto (handle_show_in_key): ditto (handle_exec_key): ditto (handle_path_key): ditto (handle_mime_key): ditto (handle_categories_key): small update for the current group, don't use GKeyFile (handle_actions_key): ditto (handle_dev_key): ditto (handle_mountpoint_key): ditto (handle_encoding_key): ditto (validate_desktop_key): ditto, the value is an argument now (validate_keys_for_current_group): renamed from validate_keys_for_group(), small update for the current group, don't use GKeyFile and build a hashtable of all the keys in the current group, also don't validate the key for Desktop Entry groups if the name of the key couldn't be validated since this means we'll get another error (validate_group_name): use g_ascii_iscntrl() instead of !g_ascii_isprint() (validate_groups_and_keys): killed (validate_required_keys): don't use GKeyFile (validate_line_is_comment): new (validate_line_looks_like_group): new (validate_line_looks_like_entry): new (validate_parse_line): new (validate_parse_data): new (inspired from gkeyfile.c) (validate_flush_parse_buffer): new (inspired from gkeyfile.c) (validate_parse_from_fd): new (inspired from gkeyfile.c) (validate_load_and_parse): new (inspired from gkeyfile.c) (groups_hashtable_free): new (desktop_file_validate): updated (desktop_file_fixup): small update to avoid confusion * src/validator.c: (main): fix leak
-rw-r--r--ChangeLog49
-rw-r--r--src/validate.c666
-rw-r--r--src/validator.c2
3 files changed, 539 insertions, 178 deletions
diff --git a/ChangeLog b/ChangeLog
index 223683d..d44dd70 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,52 @@
+2007-06-04 Vincent Untz <vuntz@gnome.org>
+
+ Don't use GKeyFile in the validator, so we really control everything.
+
+ * src/validate.c: remove some FIXME/TODO
+ (validate_string_key): use g_ascii_iscntrl() instead of
+ !g_ascii_isprint(), small update for the current group
+ (validate_localestring_key): small update for the current group, don't
+ use GKeyFile
+ (validate_boolean_key): small update for the current group
+ (validate_numeric_key): ditto
+ (validate_string_regexp_list_key): use g_ascii_iscntrl() instead of
+ !g_ascii_isprint(), small update for the current group
+ (handle_type_key): small update for the current group
+ (handle_version_key): ditto
+ (handle_show_in_key): ditto
+ (handle_exec_key): ditto
+ (handle_path_key): ditto
+ (handle_mime_key): ditto
+ (handle_categories_key): small update for the current group, don't
+ use GKeyFile
+ (handle_actions_key): ditto
+ (handle_dev_key): ditto
+ (handle_mountpoint_key): ditto
+ (handle_encoding_key): ditto
+ (validate_desktop_key): ditto, the value is an argument now
+ (validate_keys_for_current_group): renamed from
+ validate_keys_for_group(), small update for the current group, don't
+ use GKeyFile and build a hashtable of all the keys in the current
+ group, also don't validate the key for Desktop Entry groups if the
+ name of the key couldn't be validated since this means we'll get
+ another error
+ (validate_group_name): use g_ascii_iscntrl() instead of
+ !g_ascii_isprint()
+ (validate_groups_and_keys): killed
+ (validate_required_keys): don't use GKeyFile
+ (validate_line_is_comment): new
+ (validate_line_looks_like_group): new
+ (validate_line_looks_like_entry): new
+ (validate_parse_line): new
+ (validate_parse_data): new (inspired from gkeyfile.c)
+ (validate_flush_parse_buffer): new (inspired from gkeyfile.c)
+ (validate_parse_from_fd): new (inspired from gkeyfile.c)
+ (validate_load_and_parse): new (inspired from gkeyfile.c)
+ (groups_hashtable_free): new
+ (desktop_file_validate): updated
+ (desktop_file_fixup): small update to avoid confusion
+ * src/validator.c: (main): fix leak
+
2007-03-15 Vincent Untz <vuntz@gnome.org>
* README: remove mention of desktop-menu-tool
diff --git a/src/validate.c b/src/validate.c
index 173c77f..e07c8cf 100644
--- a/src/validate.c
+++ b/src/validate.c
@@ -8,6 +8,11 @@
* Havoc Pennington
* Ray Strode
*
+ * A portion of this code comes from glib (gkeyfile.c)
+ * Authors of gkeyfile.c are:
+ * Ray Strode
+ * Matthias Clasen
+ *
* 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
@@ -24,33 +29,27 @@
* USA.
*/
+#include <errno.h>
+#include <fcntl.h>
#include <stdio.h>
#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
#include "keyfileutils.h"
#include "validate.h"
-/* 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
+//FIXME: document where we are 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 {
@@ -85,11 +84,25 @@ typedef enum {
DESKTOP_REGEXP_LIST_TYPE
} DesktopKeyType;
+typedef struct _kf_keyvalue kf_keyvalue;
+
+struct _kf_keyvalue {
+ char *key;
+ char *value;
+};
+
typedef struct _kf_validator kf_validator;
struct _kf_validator {
const char *filename;
- GKeyFile *keyfile;
+
+ GString *parse_buffer;
+ gboolean utf8_warning;
+ gboolean cr_error;
+
+ char *current_group;
+ GHashTable *groups;
+ GHashTable *current_keys;
gboolean kde_reserved_warnings;
gboolean no_deprecated_warnings;
@@ -401,7 +414,7 @@ validate_string_key (kf_validator *kf,
error = FALSE;
for (i = 0; value[i] != '\0'; i++) {
- if (!g_ascii_isprint (value[i])) {
+ if (g_ascii_iscntrl (value[i])) {
error = TRUE;
break;
}
@@ -411,7 +424,7 @@ validate_string_key (kf_validator *kf,
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);
+ value, key, kf->current_group);
return FALSE;
}
@@ -443,16 +456,16 @@ validate_localestring_key (kf_validator *kf,
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);
+ value, locale_key, kf->current_group);
g_free (locale_key);
return FALSE;
}
- if (!g_key_file_has_key (kf->keyfile, kf->main_group, key, NULL)) {
+ if (!g_hash_table_lookup (kf->current_keys, key)) {
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);
+ locale_key, kf->current_group, key);
g_free (locale_key);
return FALSE;
@@ -483,7 +496,7 @@ validate_boolean_key (kf_validator *kf,
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);
+ value, key, kf->current_group);
return FALSE;
}
@@ -492,7 +505,7 @@ validate_boolean_key (kf_validator *kf,
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);
+ key, kf->current_group, value);
return TRUE;
}
@@ -515,7 +528,7 @@ validate_numeric_key (kf_validator *kf,
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);
+ value, key, kf->current_group);
return FALSE;
}
@@ -543,7 +556,7 @@ validate_string_regexp_list_key (kf_validator *kf,
error = FALSE;
for (i = 0; value[i] != '\0'; i++) {
- if (!g_ascii_isprint (value[i])) {
+ if (g_ascii_iscntrl (value[i])) {
error = TRUE;
break;
}
@@ -554,7 +567,7 @@ validate_string_regexp_list_key (kf_validator *kf,
"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);
+ value, type, key, kf->current_group, value[i], type);
return FALSE;
}
@@ -563,7 +576,7 @@ validate_string_regexp_list_key (kf_validator *kf,
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);
+ value, type, key, kf->current_group);
return FALSE;
}
@@ -572,7 +585,7 @@ validate_string_regexp_list_key (kf_validator *kf,
(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);
+ value, type, key, kf->current_group);
return FALSE;
}
@@ -625,19 +638,19 @@ handle_type_key (kf_validator *kf,
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);
+ value, locale_key, kf->current_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);
+ value, locale_key, kf->current_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);
+ value, locale_key, kf->current_group);
kf->type = registered_types[i].type;
kf->type_string = registered_types[i].name;
@@ -669,7 +682,7 @@ handle_version_key (kf_validator *kf,
print_fatal (kf, "value \"%s\" for key \"%s\" in group \"%s\" "
"is not a known version\n",
- value, locale_key, kf->main_group);
+ value, locale_key, kf->current_group);
return FALSE;
}
@@ -692,6 +705,7 @@ handle_comment_key (kf_validator *kf,
* Checked.
* FIXME: this is not perfect because it could fail if a new value with
* a semicolon is registered.
+ * + FIXME: is this okay to have only ";"? (gnome-theme-installer.desktop does)
*/
static gboolean
handle_show_in_key (kf_validator *kf,
@@ -709,7 +723,7 @@ handle_show_in_key (kf_validator *kf,
if (kf->show_in) {
print_fatal (kf, "only one of \"OnlyShowIn\" and \"NotShowInkey\" keys "
"may appear in group \"%s\"\n",
- kf->main_group);
+ kf->current_group);
retval = FALSE;
}
kf->show_in = TRUE;
@@ -726,7 +740,7 @@ handle_show_in_key (kf_validator *kf,
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]);
+ value, locale_key, kf->current_group, show[i]);
continue;
}
@@ -740,7 +754,7 @@ handle_show_in_key (kf_validator *kf,
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]);
+ value, locale_key, kf->current_group, show[i]);
retval = FALSE;
}
}
@@ -825,7 +839,7 @@ handle_exec_key (kf_validator *kf,
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); \
+ value, locale_key, kf->current_group, *c); \
retval = FALSE; \
flag = FALSE; \
break; \
@@ -848,7 +862,7 @@ handle_exec_key (kf_validator *kf,
"contains an escaped double quote (\\\\\") "
"outside of a quote, but the double quote is "
"a reserved character\n",
- value, locale_key, kf->main_group);
+ value, locale_key, kf->current_group);
retval = FALSE;
escaped = FALSE;
@@ -864,7 +878,7 @@ handle_exec_key (kf_validator *kf,
"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);
+ value, locale_key, kf->current_group, *c, *c);
retval = FALSE;
} else
escaped = FALSE;
@@ -872,7 +886,7 @@ handle_exec_key (kf_validator *kf,
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);
+ value, locale_key, kf->current_group, *c);
retval = FALSE;
}
break;
@@ -906,7 +920,7 @@ handle_exec_key (kf_validator *kf,
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);
+ value, locale_key, kf->current_group, *c);
retval = FALSE;
}
break;
@@ -922,7 +936,7 @@ handle_exec_key (kf_validator *kf,
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);
+ value, locale_key, kf->current_group);
retval = FALSE;
}
@@ -937,7 +951,7 @@ handle_exec_key (kf_validator *kf,
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);
+ value, locale_key, kf->current_group);
retval = FALSE;
}
@@ -961,7 +975,7 @@ handle_exec_key (kf_validator *kf,
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);
+ value, locale_key, kf->current_group, *c);
flag = FALSE;
}
break;
@@ -977,14 +991,14 @@ handle_exec_key (kf_validator *kf,
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);
+ value, locale_key, kf->current_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);
+ value, locale_key, kf->current_group);
retval = FALSE;
}
@@ -994,6 +1008,7 @@ handle_exec_key (kf_validator *kf,
/* + If entry is of type Application, the working directory to run the program
* in. (probably implies an absolute path)
* Checked.
+ * + FIXME: is it okay to have an empty string here? (wireshark.desktop does)
*/
static gboolean
handle_path_key (kf_validator *kf,
@@ -1005,7 +1020,7 @@ handle_path_key (kf_validator *kf,
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);
+ value, locale_key, kf->current_group);
return TRUE;
}
@@ -1043,7 +1058,7 @@ handle_mime_key (kf_validator *kf,
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]);
+ value, locale_key, kf->current_group, types[i]);
continue;
}
@@ -1054,7 +1069,7 @@ handle_mime_key (kf_validator *kf,
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]);
+ value, locale_key, kf->current_group, types[i]);
retval = FALSE;
}
}
@@ -1065,7 +1080,7 @@ handle_mime_key (kf_validator *kf,
return retval;
}
-/* + FIXME: is there restrictions on how a category should be named?
+/* + FIXME: are 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.
@@ -1108,7 +1123,7 @@ handle_categories_key (kf_validator *kf,
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]);
+ value, locale_key, kf->current_group, categories[i]);
continue;
}
@@ -1129,12 +1144,11 @@ handle_categories_key (kf_validator *kf,
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)) {
+ if (!g_hash_table_lookup (kf->current_keys, "OnlyShowIn")) {
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);
+ categories[i], locale_key, kf->current_group);
retval = FALSE;
}
continue;
@@ -1143,14 +1157,14 @@ handle_categories_key (kf_validator *kf,
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,
+ value, locale_key, kf->current_group,
categories[i]);
continue;
}
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]);
+ value, locale_key, kf->current_group, categories[i]);
retval = FALSE;
}
@@ -1184,14 +1198,14 @@ handle_actions_key (kf_validator *kf,
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);
+ value, locale_key, kf->current_group);
continue;
}
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]);
+ value, locale_key, kf->current_group, actions[i]);
continue;
}
@@ -1217,7 +1231,7 @@ handle_dev_key (kf_validator *kf,
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);
+ value, locale_key, kf->current_group);
return TRUE;
}
@@ -1236,7 +1250,7 @@ handle_mountpoint_key (kf_validator *kf,
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);
+ value, locale_key, kf->current_group);
return TRUE;
}
@@ -1255,7 +1269,7 @@ handle_encoding_key (kf_validator *kf,
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);
+ value, locale_key, kf->current_group);
return FALSE;
}
@@ -1369,11 +1383,11 @@ static gboolean
validate_desktop_key (kf_validator *kf,
const char *locale_key,
const char *key,
- const char *locale)
+ const char *locale,
+ const char *value)
{
unsigned int i;
unsigned int j;
- char *value;
if (!strncmp (key, "X-", 2))
return TRUE;
@@ -1386,7 +1400,7 @@ validate_desktop_key (kf_validator *kf,
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);
+ locale_key, kf->current_group, key);
return FALSE;
}
@@ -1399,42 +1413,29 @@ validate_desktop_key (kf_validator *kf,
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);
+ locale_key, kf->current_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);
+ locale_key, kf->current_group);
- /* this is not supposed to happen since we got the key from the list of
- * existing keys */
- g_assert (value != NULL);
-
- if (!validate_for_type[j].validate (kf, key, locale, value)) {
- g_free (value);
+ if (!validate_for_type[j].validate (kf, key, locale, value))
return FALSE;
- }
if (registered_desktop_keys[i].handle_and_validate != NULL) {
if (!registered_desktop_keys[i].handle_and_validate (kf, locale_key,
- value)) {
- g_free (value);
+ value))
return FALSE;
- }
}
- g_free (value);
-
break;
}
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);
+ "\"X-\"\n", key, kf->current_group);
return FALSE;
}
@@ -1445,66 +1446,100 @@ validate_desktop_key (kf_validator *kf,
* Checked.
*/
static gboolean
-validate_keys_for_group (kf_validator *kf,
- const char *group)
+validate_keys_for_current_group (kf_validator *kf)
{
gboolean desktop_group;
gboolean retval;
- int i;
char *key;
char *locale;
- char **keys;
- GHashTable *hashtable;
+ GSList *keys;
+ GSList *sl;
retval = TRUE;
- desktop_group = (!strcmp (group, GROUP_DESKTOP_ENTRY) ||
- !strcmp (group, GROUP_KDE_DESKTOP_ENTRY));
+ desktop_group = (!strcmp (kf->current_group, GROUP_DESKTOP_ENTRY) ||
+ !strcmp (kf->current_group, GROUP_KDE_DESKTOP_ENTRY));
- keys = g_key_file_get_keys (kf->keyfile, group, NULL, NULL);
+ keys = g_slist_copy (g_hash_table_lookup (kf->groups, kf->current_group));
+ /* keys were prepended, so reverse the list (that's why we use a
+ * g_slist_copy() */
+ keys = g_slist_reverse (keys);
- g_assert (keys != NULL);
+ kf->current_keys = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, NULL);
- hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+ /* we need two passes: some checks are looking if another key exists in the
+ * group */
+ for (sl = keys; sl != NULL; sl = sl->next) {
+ kf_keyvalue *keyvalue;
- for (i = 0; keys[i] != NULL; i++) {
- if (!key_extract_locale (keys[i], &key, &locale)) {
+ keyvalue = (kf_keyvalue *) sl->data;
+ g_hash_table_insert (kf->current_keys, keyvalue->key, GINT_TO_POINTER (1));
+ }
+
+ for (sl = keys; sl != NULL; sl = sl->next) {
+ kf_keyvalue *keyvalue;
+ gboolean skip_desktop_check;
+ gpointer hashvalue;
+
+ keyvalue = (kf_keyvalue *) sl->data;
+
+ skip_desktop_check = FALSE;
+
+ if (!key_extract_locale (keyvalue->key, &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);
+ keyvalue->key, kf->current_group);
retval = FALSE;
+ skip_desktop_check = TRUE;
- key = g_strdup (keys[i]);
+ key = g_strdup (keyvalue->key);
}
g_assert (key != NULL);
- if (g_hash_table_lookup (hashtable, keys[i])) {
+ hashvalue = g_hash_table_lookup (kf->current_keys, keyvalue->key);
+ if (GPOINTER_TO_INT (hashvalue) != 1) {
print_fatal (kf, "file contains multiple keys named \"%s\" in "
- "group \"%s\"\n", keys[i], group);
+ "group \"%s\"\n", keyvalue->key, kf->current_group);
retval = FALSE;
+ } else {
+ g_hash_table_replace (kf->current_keys, keyvalue->key,
+ GINT_TO_POINTER (2));
}
- if (desktop_group) {
- if (!validate_desktop_key (kf, keys[i], key, locale))
+ if (desktop_group && !skip_desktop_check) {
+ if (!validate_desktop_key (kf, keyvalue->key,
+ key, locale, keyvalue->value))
retval = FALSE;
}
- 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);
+ g_hash_table_destroy (kf->current_keys);
+ kf->current_keys = NULL;
return retval;
}
+/* + Using [KDE Desktop Entry] instead of [Desktop Entry] as header is
+ * deprecated.
+ * Checked.
+ * + Group names may contain all ASCII characters except for [ and ] and
+ * control characters.
+ * Checked.
+ * + 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_group_name (kf_validator *kf,
const char *group)
@@ -1514,7 +1549,7 @@ validate_group_name (kf_validator *kf,
for (i = 0; group[i] != '\0'; i++) {
c = group[i];
- if (!g_ascii_isprint (c) || c == '[' || c == ']') {
+ if (g_ascii_iscntrl (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);
@@ -1571,70 +1606,31 @@ validate_group_name (kf_validator *kf,
return FALSE;
}
-/* + 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)
+validate_required_keys (kf_validator *kf)
{
- gboolean retval;
- int i;
- char *group;
- char **groups;
+ gboolean retval;
+ unsigned int i;
+ GSList *sl;
+ GSList *keys;
+ GHashTable *hashtable;
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];
+ hashtable = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+ keys = g_hash_table_lookup (kf->groups, kf->main_group);
- if (!validate_group_name (kf, group))
- retval = FALSE;
+ for (sl = keys; sl != NULL; sl = sl->next) {
+ kf_keyvalue *keyvalue;
- if (!validate_keys_for_group (kf, group))
- retval = FALSE;
+ keyvalue = (kf_keyvalue *) sl->data;
+ g_hash_table_insert (hashtable, keyvalue->key, keyvalue->key);
}
- g_strfreev (groups);
- return retval;
-}
-
-static gboolean
-validate_required_keys (kf_validator *kf)
-{
- gboolean retval;
- unsigned int i;
-
- retval = TRUE;
-
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)) {
+ if (!g_hash_table_lookup (hashtable,
+ registered_desktop_keys[i].name)) {
print_fatal (kf, "required key \"%s\" in group \"%s\" is not "
"present\n",
registered_desktop_keys[i].name, kf->main_group);
@@ -1643,6 +1639,8 @@ validate_required_keys (kf_validator *kf)
}
}
+ g_hash_table_destroy (hashtable);
+
return retval;
}
@@ -1821,34 +1819,339 @@ validate_filename (kf_validator *kf)
return FALSE;
}
+/* + Lines beginning with a # and blank lines are considered comments.
+ * Checked.
+ */
+static gboolean
+validate_line_is_comment (kf_validator *kf,
+ const char *line)
+{
+ return (*line == '#' || *line == '\0');
+}
+
+/* + A group header with name groupname is a line in the format: [groupname]
+ * Checked.
+ * + Group names may contain all ASCII characters except for [ and ] and
+ * control characters.
+ * This is done in validate_group_name().
+ */
+static gboolean
+validate_line_looks_like_group (kf_validator *kf,
+ const char *line,
+ char **group)
+{
+ char *chomped;
+ gboolean result;
+
+ chomped = g_strdup (line);
+ g_strchomp (chomped);
+
+ result = (*chomped == '[' && chomped[strlen (chomped) - 1] == ']');
+
+ if (result && strcmp (chomped, line))
+ print_fatal (kf, "line \"%s\" ends with a space, but looks like a group. "
+ "The validation will continue, with the trailing spaces "
+ "ignored.\n", line);
+
+ *group = g_strndup (chomped + 1, strlen (chomped) - 2);
+
+ g_free (chomped);
+
+ return result;
+}
+
+/* + Space before and after the equals sign should be ignored; the = sign is
+ * the actual delimiter.
+ * Checked.
+ */
+static gboolean
+validate_line_looks_like_entry (kf_validator *kf,
+ const char *line,
+ char **key,
+ char **value)
+{
+ char *p;
+
+ p = g_utf8_strchr (line, -1, '=');
+
+ if (!p)
+ return FALSE;
+
+ /* key must be non-empty */
+ if (*p == line[0])
+ return FALSE;
+
+ if (key) {
+ *key = g_strndup (line, p - line);
+ g_strchomp (*key);
+ }
+ if (value) {
+ *value = g_strdup (p + 1);
+ g_strchug (*value);
+ }
+
+ return TRUE;
+}
+
+/* + Only comments are accepted before the first group.
+ * Checked.
+ * + The first group should be "Desktop Entry".
+ * Checked.
+ * + Multiple groups may not have the same name.
+ * Checked.
+ */
+static void
+validate_parse_line (kf_validator *kf)
+{
+ char *line;
+ int len;
+ char *group;
+ char *key;
+ char *value;
+
+ line = kf->parse_buffer->str;
+ len = kf->parse_buffer->len;
+
+ if (!kf->utf8_warning && !g_utf8_validate (line, len, NULL)) {
+ print_warning (kf, "file contains lines that are not UTF-8 encoded. There "
+ "is no guarantee the validator will correctly work.\n");
+ kf->utf8_warning = TRUE;
+ }
+
+ if (g_ascii_isspace (*line)) {
+ print_fatal (kf, "line \"%s\" starts with a space. Comment, group and "
+ "key-value lines should not start with a space. The "
+ "validation will continue, with the leading spaces "
+ "ignored.\n", line);
+ while (g_ascii_isspace (*line))
+ line++;
+ }
+
+ if (validate_line_is_comment (kf, line))
+ return;
+
+ if (validate_line_looks_like_group (kf, line, &group)) {
+ if (!kf->current_group &&
+ (strcmp (group, GROUP_DESKTOP_ENTRY) &&
+ strcmp (group, GROUP_KDE_DESKTOP_ENTRY)))
+ print_fatal (kf, "first group is not \"" GROUP_DESKTOP_ENTRY "\"\n");
+
+ if (kf->current_group && strcmp (kf->current_group, group))
+ validate_keys_for_current_group (kf);
+
+ if (g_hash_table_lookup_extended (kf->groups, group, NULL, NULL)) {
+ print_fatal (kf, "file contains multiple groups named \"%s\", but "
+ "multiple groups may not have the same name\n", group);
+ } else {
+ validate_group_name (kf, group);
+ g_hash_table_insert (kf->groups, g_strdup (group), NULL);
+ }
+
+ if (kf->current_group)
+ g_free (kf->current_group);
+ kf->current_group = group;
+
+ return;
+ }
+
+ if (validate_line_looks_like_entry (kf, line, &key, &value)) {
+ if (kf->current_group) {
+ GSList *keys;
+ kf_keyvalue *keyvalue;
+
+ keyvalue = g_slice_new (kf_keyvalue);
+ keyvalue->key = key;
+ keyvalue->value = value;
+
+ keys = g_hash_table_lookup (kf->groups, kf->current_group);
+ keys = g_slist_prepend (keys, keyvalue);
+ g_hash_table_replace (kf->groups, g_strdup (kf->current_group), keys);
+ } else {
+ print_fatal (kf, "file contains entry \"%s\" before the first group, "
+ "but only comments are accepted before the first "
+ "group\n", line);
+ }
+
+ return;
+ }
+
+ print_fatal (kf, "file contains line \"%s\", which is not a comment, "
+ "a group or an entry\n", line);
+}
+
+/* + Desktop entry files are encoded as lines of 8-bit characters separated by
+ * LF characters.
+ * Checked.
+ */
+static void
+validate_parse_data (kf_validator *kf,
+ char *data,
+ int length)
+{
+ int i;
+
+ for (i = 0; i < length; i++) {
+ if (data[i] == '\n') {
+ if (i > 0 && data[i - 1] == '\r') {
+ g_string_erase (kf->parse_buffer, kf->parse_buffer->len - 1, 1);
+
+ if (!kf->cr_error) {
+ print_fatal (kf, "file contains at least one line ending with a "
+ "carriage return before the line feed, while lines "
+ "should only be separated by a line feed "
+ "character. First such line is: \"%s\"\n",
+ kf->parse_buffer->str);
+ kf->cr_error = TRUE;
+ }
+ }
+
+ if (kf->parse_buffer->len > 0) {
+ validate_parse_line (kf);
+ g_string_erase (kf->parse_buffer, 0, -1);
+ }
+
+ } else if (data[i] == '\r') {
+ if (!kf->cr_error) {
+ print_fatal (kf, "file contains at least one line ending with a "
+ "carriage return, while lines should only be "
+ "separated by a line feed character. First such "
+ "line is: \"%s\"\n", kf->parse_buffer->str);
+ kf->cr_error = TRUE;
+ }
+
+ data[i] = '\n';
+ i--;
+ } else
+ g_string_append_c (kf->parse_buffer, data[i]);
+ }
+}
+
+static void
+validate_flush_parse_buffer (kf_validator *kf)
+{
+ if (kf->parse_buffer->len > 0) {
+ validate_parse_line (kf);
+ g_string_erase (kf->parse_buffer, 0, -1);
+ }
+
+ if (kf->current_group)
+ validate_keys_for_current_group (kf);
+}
+
+#define VALIDATE_READ_SIZE 4096
+static gboolean
+validate_parse_from_fd (kf_validator *kf,
+ int fd)
+{
+ int bytes_read;
+ struct stat stat_buf;
+ char read_buf[VALIDATE_READ_SIZE];
+
+ if (fstat (fd, &stat_buf) < 0) {
+ print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
+ return FALSE;
+ }
+
+ if (!S_ISREG (stat_buf.st_mode)) {
+ print_fatal (kf, "file is not a regular file\n");
+ return FALSE;
+ }
+
+ if (stat_buf.st_size == 0) {
+ print_fatal (kf, "file is empty\n");
+ return FALSE;
+ }
+
+ bytes_read = 0;
+ while (1) {
+ bytes_read = read (fd, read_buf, VALIDATE_READ_SIZE);
+
+ if (bytes_read == 0) /* End of File */
+ break;
+
+ if (bytes_read < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+
+ /* let's validate what we already have */
+ validate_flush_parse_buffer (kf);
+
+ print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
+ return FALSE;
+ }
+
+ validate_parse_data (kf, read_buf, bytes_read);
+ }
+
+ validate_flush_parse_buffer (kf);
+
+ return TRUE;
+}
+
+static gboolean
+validate_load_and_parse (kf_validator *kf)
+{
+ int fd;
+ gboolean ret;
+
+ fd = g_open (kf->filename, O_RDONLY, 0);
+
+ if (fd < 0) {
+ print_fatal (kf, "while reading the file: %s\n", g_strerror (errno));
+ return FALSE;
+ }
+
+ ret = validate_parse_from_fd (kf, fd);
+
+ close (fd);
+
+ return ret;
+}
+
+static gboolean
+groups_hashtable_free (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ GSList *list;
+ GSList *sl;
+
+ list = (GSList *) value;
+ for (sl = list; sl != NULL; sl = sl->next) {
+ kf_keyvalue *keyvalue;
+
+ keyvalue = (kf_keyvalue *) sl->data;
+ g_free (keyvalue->key);
+ g_free (keyvalue->value);
+ g_slice_free (kf_keyvalue, keyvalue);
+ }
+
+ g_slist_free (list);
+
+ return TRUE;
+}
+
gboolean
desktop_file_validate (const char *filename,
gboolean warn_kde,
gboolean no_warn_deprecated)
{
- GError *error;
- kf_validator kf;
+ 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.parse_buffer = g_string_new ("");
+ kf.utf8_warning = FALSE;
+ kf.cr_error = FALSE;
+ kf.current_group = NULL;
+ kf.groups = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+ kf.current_keys = NULL;
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;
- }
-
kf.main_group = NULL;
kf.type = INVALID_TYPE;
kf.type_string = NULL;
@@ -1863,7 +2166,7 @@ desktop_file_validate (const char *filename,
NULL, g_free);
kf.fatal_error = FALSE;
- validate_groups_and_keys (&kf);
+ validate_load_and_parse (&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) {
@@ -1885,23 +2188,30 @@ desktop_file_validate (const char *filename,
g_hash_table_destroy (kf.action_values);
g_hash_table_destroy (kf.action_groups);
- g_key_file_free (kf.keyfile);
+ g_assert (kf.current_keys == NULL);
+ /* we can't add an automatic destroy handler for the value because we replace
+ * it when adding keys, and this means we'd have to copy the value each time
+ * we replace it */
+ g_hash_table_foreach_remove (kf.groups, groups_hashtable_free, NULL);
+ g_hash_table_destroy (kf.groups);
+ g_free (kf.current_group);
+ g_string_free (kf.parse_buffer, TRUE);
return (!kf.fatal_error);
}
/* return FALSE if we were unable to fix the file */
gboolean
-desktop_file_fixup (GKeyFile *kf,
+desktop_file_fixup (GKeyFile *keyfile,
const char *filename)
{
char *value;
unsigned int i;
- if (g_key_file_has_group (kf, GROUP_KDE_DESKTOP_ENTRY)) {
+ if (g_key_file_has_group (keyfile, 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,
+ dfu_key_file_rename_group (keyfile,
GROUP_KDE_DESKTOP_ENTRY, GROUP_DESKTOP_ENTRY);
}
@@ -1911,7 +2221,7 @@ desktop_file_fixup (GKeyFile *kf,
registered_desktop_keys[i].type != DESKTOP_REGEXP_LIST_TYPE)
continue;
- value = g_key_file_get_value (kf, GROUP_DESKTOP_ENTRY,
+ value = g_key_file_get_value (keyfile, GROUP_DESKTOP_ENTRY,
registered_desktop_keys[i].name, NULL);
if (value) {
int len;
@@ -1928,7 +2238,7 @@ desktop_file_fixup (GKeyFile *kf,
filename, registered_desktop_keys[i].name);
str = g_strconcat (value, ";", NULL);
- g_key_file_set_value (kf, GROUP_DESKTOP_ENTRY,
+ g_key_file_set_value (keyfile, GROUP_DESKTOP_ENTRY,
registered_desktop_keys[i].name, str);
g_free (str);
}
diff --git a/src/validator.c b/src/validator.c
index ec24c98..2ab5215 100644
--- a/src/validator.c
+++ b/src/validator.c
@@ -59,6 +59,8 @@ main (int argc, char *argv[])
return 1;
}
+ g_option_context_free (context);
+
/* only accept one desktop file argument */
if (filename == NULL || filename[0] == NULL || filename[1] != NULL) {
g_printerr ("See \"%s --help\" for correct usage.\n", g_get_prgname ());