Exemplo n.º 1
0
/**
 * as_validator_validate_file:
 * @validator: An instance of #AsValidator.
 * @metadata_file: An AppStream XML file.
 *
 * Validate an AppStream XML file
 **/
gboolean
as_validator_validate_file (AsValidator *validator, GFile *metadata_file)
{
	g_autoptr(GFileInfo) info = NULL;
	g_autoptr(GInputStream) file_stream = NULL;
	g_autoptr(GInputStream) stream_data = NULL;
	g_autoptr(GConverter) conv = NULL;
	g_autoptr(GString) asxmldata = NULL;
	g_autofree gchar *fname = NULL;
	gssize len;
	const gsize buffer_size = 1024 * 32;
	g_autofree gchar *buffer = NULL;
	const gchar *content_type = NULL;
	g_autoptr(GError) tmp_error = NULL;
	gboolean ret;

	info = g_file_query_info (metadata_file,
				G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
				G_FILE_QUERY_INFO_NONE,
				NULL, NULL);
	if (info != NULL)
		content_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);

	fname = g_file_get_basename (metadata_file);
	as_validator_set_current_fname (validator, fname);

	file_stream = G_INPUT_STREAM (g_file_read (metadata_file, NULL, &tmp_error));
	if (tmp_error != NULL) {
		as_validator_add_issue (validator, NULL,
					AS_ISSUE_IMPORTANCE_ERROR,
					AS_ISSUE_KIND_READ_ERROR,
					"Unable to read file: %s", tmp_error->message);
		return FALSE;
	}
	if (file_stream == NULL)
		return FALSE;

	if ((g_strcmp0 (content_type, "application/gzip") == 0) || (g_strcmp0 (content_type, "application/x-gzip") == 0)) {
		/* decompress the GZip stream */
		conv = G_CONVERTER (g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP));
		stream_data = g_converter_input_stream_new (file_stream, conv);
	} else {
		stream_data = g_object_ref (file_stream);
	}

	asxmldata = g_string_new ("");
	buffer = g_malloc (buffer_size);
	while ((len = g_input_stream_read (stream_data, buffer, buffer_size, NULL, &tmp_error)) > 0) {
		g_string_append_len (asxmldata, buffer, len);
	}
	if (tmp_error != NULL) {
		as_validator_add_issue (validator, NULL,
					AS_ISSUE_IMPORTANCE_ERROR,
					AS_ISSUE_KIND_READ_ERROR,
					"Unable to read file: %s", tmp_error->message);
		return FALSE;
	}
	/* check if there was an error */
	if (len < 0)
		return FALSE;

	ret = as_validator_validate_data (validator, asxmldata->str);
	as_validator_clear_current_fname (validator);

	return ret;
}
Exemplo n.º 2
0
/**
 * as_validator_analyze_component_metainfo_relation_cb:
 *
 * Helper function for GHashTable foreach iteration.
 */
static void
as_validator_analyze_component_metainfo_relation_cb (const gchar *fname, AsComponent *cpt, struct MInfoCheckData *data)
{
	g_autofree gchar *cid_base = NULL;

	/* if we have no component-id, we can't check anything */
	if (as_component_get_id (cpt) == NULL)
		return;

	as_validator_set_current_cpt (data->validator, cpt);
	as_validator_set_current_fname (data->validator, fname);

	/* check if the fname and the component-id match */
	if (g_str_has_suffix (as_component_get_id (cpt), ".desktop")) {
		cid_base = g_strndup (as_component_get_id (cpt),
					g_strrstr (as_component_get_id (cpt), ".") - as_component_get_id (cpt));
	} else {
		cid_base = g_strdup (as_component_get_id (cpt));
	}
	if (!as_matches_metainfo (fname, cid_base)) {
		/* the name-without-type didn't match - check for the full id in the component name */
		if (!as_matches_metainfo (fname, as_component_get_id (cpt))) {
			as_validator_add_issue (data->validator, NULL,
					AS_ISSUE_IMPORTANCE_WARNING,
					AS_ISSUE_KIND_WRONG_NAME,
					"The metainfo filename does not match the component ID.");
		}
	}

	/* check if the referenced .desktop file exists */
	if (as_component_get_kind (cpt) == AS_COMPONENT_KIND_DESKTOP_APP) {
		if (g_hash_table_contains (data->desktop_fnames, as_component_get_desktop_id (cpt))) {
			g_autofree gchar *desktop_fname_full = NULL;
			g_autoptr(GKeyFile) dfile = NULL;
			GError *tmp_error = NULL;

			desktop_fname_full = g_build_filename (data->apps_dir, as_component_get_desktop_id (cpt), NULL);
			dfile = g_key_file_new ();

			g_key_file_load_from_file (dfile, desktop_fname_full, G_KEY_FILE_NONE, &tmp_error);
			if (tmp_error != NULL) {
				as_validator_add_issue (data->validator, NULL,
						AS_ISSUE_IMPORTANCE_WARNING,
						AS_ISSUE_KIND_READ_ERROR,
						"Unable to read associated .desktop file: %s", tmp_error->message);
				g_error_free (tmp_error);
				tmp_error = NULL;
			} else {
				/* we successfully opened the .desktop file, now perform some checks */

				/* name */
				if (as_str_empty (as_component_get_name (cpt)) &&
				    (!g_key_file_has_key (dfile, G_KEY_FILE_DESKTOP_GROUP,
								 G_KEY_FILE_DESKTOP_KEY_NAME, NULL))) {
					/* we don't have a summary, and there is also none in the .desktop file - this is bad. */
					as_validator_add_issue (data->validator, NULL,
							AS_ISSUE_IMPORTANCE_ERROR,
							AS_ISSUE_KIND_VALUE_MISSING,
							"The component is missing a name (none found in its metainfo or .desktop file)");
				}

				/* summary */
				if (as_str_empty (as_component_get_summary (cpt)) &&
				    (!g_key_file_has_key (dfile, G_KEY_FILE_DESKTOP_GROUP,
								 G_KEY_FILE_DESKTOP_KEY_COMMENT, NULL))) {
					/* we don't have a summary, and there is also none in the .desktop file - this is bad. */
					as_validator_add_issue (data->validator, NULL,
							AS_ISSUE_IMPORTANCE_ERROR,
							AS_ISSUE_KIND_VALUE_MISSING,
							"The component is missing a summary (none found in its metainfo or .desktop file)");
				}

				/* categories */
				if (g_key_file_has_key (dfile, G_KEY_FILE_DESKTOP_GROUP,
								G_KEY_FILE_DESKTOP_KEY_CATEGORIES, NULL)) {
					g_autofree gchar *cats_str = NULL;
					g_auto(GStrv) cats = NULL;
					guint i;

					cats_str = g_key_file_get_string (dfile, G_KEY_FILE_DESKTOP_GROUP,
										 G_KEY_FILE_DESKTOP_KEY_CATEGORIES, NULL);
					cats = g_strsplit (cats_str, ";", -1);
					for (i = 0; cats[i] != NULL; i++) {
						if (as_str_empty (cats[i]))
							continue;
						if (!as_utils_is_category_name (cats[i])) {
							as_validator_add_issue (data->validator, NULL,
										AS_ISSUE_IMPORTANCE_ERROR,
										AS_ISSUE_KIND_VALUE_WRONG,
										"The category '%s' defined in the .desktop file does not exist.", cats[i]);
						}
					}
				}

			}
		} else {
			as_validator_add_issue (data->validator, NULL,
					AS_ISSUE_IMPORTANCE_ERROR,
					AS_ISSUE_KIND_FILE_MISSING,
					"Component metadata refers to a non-existing .desktop file.");
		}
	}

	as_validator_clear_current_cpt (data->validator);
	as_validator_clear_current_fname (data->validator);
}
Exemplo n.º 3
0
/**
 * as_validator_validate_tree:
 * @validator: An instance of #AsValidator.
 * @root_dir: The root directory of the filesystem tree that should be validated.
 *
 * Validate a full directory tree for issues in AppStream metadata.
 **/
gboolean
as_validator_validate_tree (AsValidator *validator, const gchar *root_dir)
{
	g_autofree gchar *metainfo_dir = NULL;
	g_autofree gchar *legacy_metainfo_dir = NULL;
	g_autofree gchar *apps_dir = NULL;
	g_autoptr(GPtrArray) mfiles = NULL;
	g_autoptr(GPtrArray) mfiles_legacy = NULL;
	g_autoptr(GPtrArray) dfiles = NULL;
	GHashTable *dfilenames = NULL;
	GHashTable *validated_cpts = NULL;
	guint i;
	gboolean ret = TRUE;
	g_autoptr(AsXMLData) xdt = NULL;
	struct MInfoCheckData ht_helper;

	/* cleanup */
	as_validator_clear_issues (validator);

	metainfo_dir = g_build_filename (root_dir, "usr", "share", "metainfo", NULL);
	legacy_metainfo_dir = g_build_filename (root_dir, "usr", "share", "appdata", NULL);
	apps_dir = g_build_filename (root_dir, "usr", "share", "applications", NULL);

	/* check if we actually have a directory which could hold metadata */
	if ((!g_file_test (metainfo_dir, G_FILE_TEST_IS_DIR)) &&
	    (!g_file_test (legacy_metainfo_dir, G_FILE_TEST_IS_DIR))) {
		as_validator_add_issue (validator, NULL,
					AS_ISSUE_IMPORTANCE_INFO,
					AS_ISSUE_KIND_FILE_MISSING,
					"No AppStream metadata was found.");
		goto out;
	}

	/* check if we actually have a directory which could hold application information */
	if (!g_file_test (apps_dir, G_FILE_TEST_IS_DIR)) {
		as_validator_add_issue (validator, NULL,
					AS_ISSUE_IMPORTANCE_PEDANTIC, /* pedantic because not everything which has metadata is an application */
					AS_ISSUE_KIND_FILE_MISSING,
					"No XDG applications directory found.");
	}

	/* holds a filename -> component mapping */
	validated_cpts = g_hash_table_new_full (g_str_hash,
						g_str_equal,
						g_free,
						g_object_unref);

	/* set up XML parser */
	xdt = as_xmldata_new ();
	as_xmldata_initialize (xdt, AS_CURRENT_FORMAT_VERSION,
			       "C",
				NULL,
				NULL,
				NULL,
				0);
	as_xmldata_set_format_style (xdt, AS_FORMAT_STYLE_METAINFO);

	/* validate all metainfo files */
	mfiles = as_utils_find_files_matching (metainfo_dir, "*.xml", FALSE, NULL);
	mfiles_legacy = as_utils_find_files_matching (legacy_metainfo_dir, "*.xml", FALSE, NULL);

	/* in case we only have legacy files */
	if (mfiles == NULL)
		mfiles = g_ptr_array_new_with_free_func (g_free);

	if (mfiles_legacy != NULL) {
		for (i = 0; i < mfiles_legacy->len; i++) {
			const gchar *fname;
			g_autofree gchar *fname_basename = NULL;

			/* process metainfo files in legacy paths */
			fname = (const gchar*) g_ptr_array_index (mfiles_legacy, i);
			fname_basename = g_path_get_basename (fname);
			as_validator_set_current_fname (validator, fname_basename);

			as_validator_add_issue (validator, NULL,
						AS_ISSUE_IMPORTANCE_INFO,
						AS_ISSUE_KIND_LEGACY,
						"The metainfo file is stored in a legacy path. Please place it in '/usr/share/metainfo'.");

			g_ptr_array_add (mfiles, g_strdup (fname));
		}
	}

	for (i = 0; i < mfiles->len; i++) {
		const gchar *fname;
		g_autoptr(GFile) file = NULL;
		g_autoptr(GInputStream) file_stream = NULL;
		g_autoptr(GError) tmp_error = NULL;
		g_autoptr(GString) asdata = NULL;
		gssize len;
		const gsize buffer_size = 1024 * 24;
		g_autofree gchar *buffer = NULL;
		xmlNode *root;
		xmlDoc *doc;
		g_autofree gchar *fname_basename = NULL;

		fname = (const gchar*) g_ptr_array_index (mfiles, i);
		file = g_file_new_for_path (fname);
		if (!g_file_query_exists (file, NULL)) {
			g_warning ("File '%s' suddenly vanished.", fname);
			g_object_unref (file);
			continue;
		}

		fname_basename = g_path_get_basename (fname);
		as_validator_set_current_fname (validator, fname_basename);

		/* load a plaintext file */
		file_stream = G_INPUT_STREAM (g_file_read (file, NULL, &tmp_error));
		if (tmp_error != NULL) {
			as_validator_add_issue (validator, NULL,
						AS_ISSUE_IMPORTANCE_ERROR,
						AS_ISSUE_KIND_READ_ERROR,
						"Unable to read file: %s", tmp_error->message);
			continue;
		}

		asdata = g_string_new ("");
		buffer = g_malloc (buffer_size);
		while ((len = g_input_stream_read (file_stream, buffer, buffer_size, NULL, &tmp_error)) > 0) {
			g_string_append_len (asdata, buffer, len);
		}
		/* check if there was an error */
		if (tmp_error != NULL) {
			as_validator_add_issue (validator, NULL,
						AS_ISSUE_IMPORTANCE_ERROR,
						AS_ISSUE_KIND_READ_ERROR,
						"Unable to read file: %s", tmp_error->message);
			continue;
		}

		/* now read the XML */
		doc = as_validator_open_xml_document (validator, xdt, asdata->str);
		if (doc == NULL) {
			as_validator_clear_current_fname (validator);
			continue;
		}
		root = xmlDocGetRootElement (doc);

		if (g_strcmp0 ((gchar*) root->name, "component") == 0) {
			AsComponent *cpt;
			cpt = as_validator_validate_component_node (validator,
								    xdt,
								    root);
			if (cpt != NULL)
				g_hash_table_insert (validated_cpts,
							g_strdup (fname_basename),
							cpt);
		} else if (g_strcmp0 ((gchar*) root->name, "components") == 0) {
			as_validator_add_issue (validator, root,
					AS_ISSUE_IMPORTANCE_ERROR,
					AS_ISSUE_KIND_TAG_NOT_ALLOWED,
					"The metainfo file specifies multiple components. This is not allowed.");
			ret = FALSE;
		} else if (g_str_has_prefix ((gchar*) root->name, "application")) {
			as_validator_add_issue (validator, root,
					AS_ISSUE_IMPORTANCE_ERROR,
					AS_ISSUE_KIND_LEGACY,
					"The metainfo file uses an ancient version of the AppStream specification, which can not be validated. Please migrate it to version 0.6 (or higher).");
			ret = FALSE;
		}

		as_validator_clear_current_fname (validator);
		xmlFreeDoc (doc);
	}

	/* check if we have matching .desktop files */
	dfilenames = g_hash_table_new_full (g_str_hash,
						g_str_equal,
						g_free,
						NULL);
	dfiles = as_utils_find_files_matching (apps_dir, "*.desktop", FALSE, NULL);
	if (dfiles != NULL) {
		for (i = 0; i < dfiles->len; i++) {
			const gchar *fname;
			fname = (const gchar*) g_ptr_array_index (dfiles, i);
			g_hash_table_add (dfilenames,
						g_path_get_basename (fname));
		}
	}

	/* validate the component-id <-> filename relations and availability of other metadata */
	ht_helper.validator = validator;
	ht_helper.desktop_fnames = dfilenames;
	ht_helper.apps_dir = apps_dir;
	g_hash_table_foreach (validated_cpts,
				(GHFunc) as_validator_analyze_component_metainfo_relation_cb,
				&ht_helper);

out:
	if (dfilenames != NULL)
		g_hash_table_unref (dfilenames);
	if (validated_cpts != NULL)
		g_hash_table_unref (validated_cpts);

	return ret;
}
Exemplo n.º 4
0
/**
 * as_validator_analyze_component_metainfo_relation_cb:
 *
 * Helper function for GHashTable foreach iteration.
 */
static void
as_validator_analyze_component_metainfo_relation_cb (const gchar *fname, AsComponent *cpt, struct MInfoCheckData *data)
{
	gchar *tmp;

	/* if we have no component-id, we can't check anything */
	if (as_component_get_id (cpt) == NULL)
		return;

	as_validator_set_current_cpt (data->validator, cpt);
	as_validator_set_current_fname (data->validator, fname);

	/* check if the fname and the component-id match */
	tmp = g_strndup (as_component_get_id (cpt),
				g_strrstr (as_component_get_id (cpt), ".") - as_component_get_id (cpt));
	if (!as_matches_metainfo (fname, tmp)) {
		/* the name-without-type didn't match - check for the full id in the component name */
		if (!as_matches_metainfo (fname, as_component_get_id (cpt))) {
			as_validator_add_issue (data->validator,
					AS_ISSUE_IMPORTANCE_WARNING,
					AS_ISSUE_KIND_WRONG_NAME,
					"The metainfo filename does not match the component ID.");
		}
	}
	g_free (tmp);

	/* check if the referenced .desktop file exists */
	if (as_component_get_kind (cpt) == AS_COMPONENT_KIND_DESKTOP_APP) {
		if (g_hash_table_contains (data->desktop_fnames, as_component_get_id (cpt))) {
			g_autofree gchar *desktop_fname_full = NULL;
			g_autoptr(GKeyFile) dfile = NULL;
			GError *tmp_error = NULL;

			desktop_fname_full = g_build_filename (data->apps_dir, as_component_get_id (cpt), NULL);
			dfile = g_key_file_new ();

			g_key_file_load_from_file (dfile, desktop_fname_full, G_KEY_FILE_NONE, &tmp_error);
			if (tmp_error != NULL) {
				as_validator_add_issue (data->validator,
						AS_ISSUE_IMPORTANCE_WARNING,
						AS_ISSUE_KIND_READ_ERROR,
						"Unable to read associated .desktop file: %s", tmp_error->message);
				g_error_free (tmp_error);
				tmp_error = NULL;
			} else {
				/* we successfully opened the .desktop file, now perform some checks */

				/* name */
				if ((g_strcmp0 (as_component_get_name (cpt), "") == 0) &&
				    (!g_key_file_has_key (dfile, "Desktop Entry", "Name", NULL))) {
					/* we don't have a summary, and there is also none in the .desktop file - this is bad. */
					as_validator_add_issue (data->validator,
							AS_ISSUE_IMPORTANCE_ERROR,
							AS_ISSUE_KIND_VALUE_MISSING,
							"The component is missing a name (none found in its metainfo or .desktop file)");
				}

				/* summary */
				if ((g_strcmp0 (as_component_get_summary (cpt), "") == 0) &&
				    (!g_key_file_has_key (dfile, "Desktop Entry", "Comment", NULL))) {
					/* we don't have a summary, and there is also none in the .desktop file - this is bad. */
					as_validator_add_issue (data->validator,
							AS_ISSUE_IMPORTANCE_ERROR,
							AS_ISSUE_KIND_VALUE_MISSING,
							"The component is missing a summary (none found in its metainfo or .desktop file)");
				}
			}
		} else {
			as_validator_add_issue (data->validator,
					AS_ISSUE_IMPORTANCE_ERROR,
					AS_ISSUE_KIND_FILE_MISSING,
					"Component metadata refers to a non-existing .desktop file.");
		}
	}

	as_validator_clear_current_cpt (data->validator);
	as_validator_clear_current_fname (data->validator);
}