Bump version to 2.21.90 remove UTF-8 control characters from the report to
[bug-buddy.git] / src / bugzilla.c
blobaed213f8dee7f132c0d132a9f72af9d0f04b7eb1
1 /* bug-buddy bug submitting program
3 * Copyright (C) 2001 Jacob Berkman
4 * Copyright 2001 Ximian, Inc.
6 * Author: jacob berkman <jacob@bug-buddy.org>
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of version 2 of the GNU General Public
10 * License as published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
22 #include <config.h>
24 #include "bug-buddy.h"
25 #include "distribution.h"
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <fcntl.h>
30 #include <utime.h>
31 #include <errno.h>
32 #include <string.h>
33 #include <ctype.h>
35 #include <glib/gi18n.h>
36 #include <gnome.h>
38 #include <gmenu-tree.h>
40 #include <bonobo/bonobo-exception.h>
41 #include <bonobo-activation/bonobo-activation.h>
43 #include <dirent.h>
45 #include <libxml/tree.h>
46 #include <libxml/parser.h>
47 #include <libxml/xmlmemory.h>
49 #include <libsoup/soup.h>
52 #define APPLET_REQUIREMENTS \
53 "has_all (repo_ids, ['IDL:Bonobo/Control:1.0'," \
54 " 'IDL:GNOME/Vertigo/PanelAppletShell:1.0']) && " \
55 "defined (panel:icon)"
57 #define DESKTOP_ENTRY "Desktop Entry"
58 #define DESKTOP_NAME "Name"
59 #define DESKTOP_COMMENT "Comment"
60 #define DESKTOP_ICON "Icon"
61 #define DESKTOP_EXEC "Exec"
63 #define BUGZILLA_BUGZILLA "X-GNOME-Bugzilla-Bugzilla"
64 #define BUGZILLA_PRODUCT "X-GNOME-Bugzilla-Product"
65 #define BUGZILLA_COMPONENT "X-GNOME-Bugzilla-Component"
66 #define BUGZILLA_EMAIL "X-GNOME-Bugzilla-Email"
67 #define BUGZILLA_VERSION "X-GNOME-Bugzilla-Version"
68 #define BUGZILLA_OTHER_BINARIES "X-GNOME-Bugzilla-OtherBinaries"
69 #define BUGZILLA_EXTRA_INFO_SCRIPT "X-GNOME-Bugzilla-ExtraInfoScript"
71 #define BA_BUGZILLA_BUGZILLA "bugzilla:bugzilla"
72 #define BA_BUGZILLA_PRODUCT "bugzilla:product"
73 #define BA_BUGZILLA_COMPONENT "bugzilla:component"
74 #define BA_BUGZILLA_VERSION "bugzilla:version"
75 #define BA_BUGZILLA_OTHER_BINARIES "bugzilla:other_binaries"
76 #define BA_BUGZILLA_EXTRA_INFO_SCRIPT "bugzilla:extra_info_script"
78 static void
79 add_bugzilla_application (GHashTable *hash,
80 const char *name,
81 const char *cname,
82 const char *comment,
83 const char *bugzilla,
84 const char *product,
85 const char *component,
86 const char *version,
87 const char *icon,
88 const char *program,
89 const char *other_programs,
90 const char *extra_info_script)
92 BugzillaApplication *app;
93 char **programv;
94 int i;
96 app = g_new0 (BugzillaApplication, 1);
98 app->name = g_strdup (name);
99 app->cname = g_strdup (cname);
100 app->comment = g_strdup (comment);
101 app->icon = g_strdup (icon);
102 app->bugzilla = g_strdup (bugzilla);
103 app->product = g_strdup (product);
104 app->component = g_strdup (component);
105 app->version = g_strdup (version);
106 app->extra_info_script = g_strdup (extra_info_script);
108 if (program) {
109 g_shell_parse_argv (program, &i, &programv, NULL);
110 if (programv[0]) {
111 char *s;
112 s = strrchr (programv[0], G_DIR_SEPARATOR);
113 s = s ? s+1 : programv[0];
114 app->ref_count += 1;
115 g_hash_table_insert (hash, g_strdup (s), app);
116 } else {
117 g_free (app);
118 return;
120 if (programv)
121 g_strfreev (programv);
124 if (other_programs) {
125 programv = g_strsplit (other_programs, ";", -1);
126 for (i=0; programv[i]; i++) {
127 app->ref_count += 1;
128 g_hash_table_insert (hash, g_strdup (programv[i]), app);
130 g_strfreev (programv);
134 static void
135 application_free (BugzillaApplication *app)
137 app->ref_count -= 1;
138 if (app->ref_count > 0)
139 return;
141 g_free (app->name);
142 g_free (app->cname);
143 g_free (app->comment);
144 g_free (app->icon);
145 g_free (app->bugzilla);
146 g_free (app->product);
147 g_free (app->component);
148 g_free (app->version);
149 g_free (app->extra_info_script);
150 g_free (app);
156 static const GSList *
157 get_i18n_slist (void)
159 const char * const *langs;
160 guint i;
161 static GSList *langs_gslist = NULL;
163 if (langs_gslist)
164 return langs_gslist;
166 langs = g_get_language_names ();
167 for (i = 0; langs[i] != 0; ++i) {
168 langs_gslist = g_slist_append (langs_gslist, (gpointer) langs[i]);
171 return langs_gslist;
174 static void
175 load_applets (GHashTable *hash)
177 Bonobo_ServerInfoList *info_list;
178 Bonobo_ServerInfo *info;
179 CORBA_Environment ev;
180 GSList *langs;
181 int i;
182 gchar *name;
184 CORBA_exception_init (&ev);
185 info_list = bonobo_activation_query (APPLET_REQUIREMENTS, NULL, &ev);
186 if (BONOBO_EX (&ev)) {
187 g_warning ("Applet list query failed: %s", BONOBO_EX_REPOID (&ev));
188 CORBA_exception_free (&ev);
189 return;
191 CORBA_exception_free (&ev);
193 langs = (GSList *)get_i18n_slist ();
195 for (i = 0; i < info_list->_length; i++) {
196 info = info_list->_buffer + i;
197 if (!bonobo_server_info_prop_lookup (info,
198 BA_BUGZILLA_BUGZILLA,
199 NULL)) {
200 continue;
203 name = g_strdup (bonobo_server_info_prop_lookup (info, "name", langs));
204 /*FIXME:
205 for (l = applications; l; l = l->next) {
206 BugzillaApplication *app = l->data;
208 if (strcmp (app->name, name) == 0) {
209 g_free (name);
210 name = g_strdup_printf (_("%s (Panel Applet)"), bonobo_server_info_prop_lookup (info, "name", langs));
212 break;
216 add_bugzilla_application (hash,
217 name,
218 bonobo_server_info_prop_lookup (info, "name", NULL),
219 bonobo_server_info_prop_lookup (info, "description", langs),
220 bonobo_server_info_prop_lookup (info, BA_BUGZILLA_BUGZILLA, NULL),
221 bonobo_server_info_prop_lookup (info, BA_BUGZILLA_PRODUCT, NULL),
222 bonobo_server_info_prop_lookup (info, BA_BUGZILLA_COMPONENT, NULL),
223 bonobo_server_info_prop_lookup (info, BA_BUGZILLA_VERSION, NULL),
224 bonobo_server_info_prop_lookup (info, "panel:icon", NULL),
225 NULL,
226 bonobo_server_info_prop_lookup (info, BA_BUGZILLA_OTHER_BINARIES, NULL),
227 bonobo_server_info_prop_lookup (info, BA_BUGZILLA_EXTRA_INFO_SCRIPT, NULL));
229 g_free (name);
232 CORBA_free (info_list);
235 static int
236 compare_applications (GMenuTreeEntry *a,
237 GMenuTreeEntry *b)
239 return g_utf8_collate (gmenu_tree_entry_get_name (a),
240 gmenu_tree_entry_get_name (b));
243 static GSList *get_all_applications_from_dir (GMenuTreeDirectory *directory,
244 GSList *list);
246 static GSList *
247 get_all_applications_from_alias (GMenuTreeAlias *alias,
248 GSList *list)
250 GMenuTreeItem *aliased_item;
252 aliased_item = gmenu_tree_alias_get_item (alias);
254 switch (gmenu_tree_item_get_type (aliased_item)) {
255 case GMENU_TREE_ITEM_DIRECTORY:
256 list = get_all_applications_from_dir (GMENU_TREE_DIRECTORY (aliased_item), list);
257 break;
259 case GMENU_TREE_ITEM_ENTRY:
260 list = g_slist_append (list, gmenu_tree_item_ref (aliased_item));
261 break;
263 default:
264 break;
267 gmenu_tree_item_unref (aliased_item);
269 return list;
272 static GSList *
273 get_all_applications_from_dir (GMenuTreeDirectory *directory,
274 GSList *list)
276 GSList *items;
277 GSList *l;
279 if (g_main_context_pending (NULL)) {
280 g_main_context_iteration (NULL, FALSE);
283 items = gmenu_tree_directory_get_contents (directory);
284 for (l = items; l; l = l->next) {
285 GMenuTreeItem *item = l->data;
287 switch (gmenu_tree_item_get_type (item)) {
288 case GMENU_TREE_ITEM_DIRECTORY:
289 list = get_all_applications_from_dir (GMENU_TREE_DIRECTORY (item), list);
290 break;
292 case GMENU_TREE_ITEM_ENTRY:
293 list = g_slist_append (list, gmenu_tree_item_ref (item));
294 break;
296 case GMENU_TREE_ITEM_ALIAS:
297 list = get_all_applications_from_alias (GMENU_TREE_ALIAS (item), list);
298 break;
300 default:
301 break;
304 gmenu_tree_item_unref (item);
307 g_slist_free (items);
309 return list;
312 static GSList *
313 get_all_applications (void)
315 GMenuTree *tree;
316 GMenuTreeDirectory *root;
317 GSList *retval;
318 const char *menufile = BUDDY_DATADIR "/bug-buddy.menu";
320 if (g_file_test (menufile, G_FILE_TEST_IS_REGULAR)) {
321 /* use a custom menu file to scan desktop entry files so we aren't limited
322 * to reporting bugs only present in the applications menu*/
323 tree = gmenu_tree_lookup (menufile, GMENU_TREE_FLAGS_INCLUDE_NODISPLAY);
324 } else {
325 /* fallback to using the applications menu */
326 tree = gmenu_tree_lookup ("applications.menu", GMENU_TREE_FLAGS_INCLUDE_NODISPLAY);
329 root = gmenu_tree_get_root_directory (tree);
331 retval = get_all_applications_from_dir (root, NULL);
333 gmenu_tree_item_unref (root);
334 gmenu_tree_unref (tree);
336 retval = g_slist_sort (retval, (GCompareFunc) compare_applications);
338 return retval;
341 GQuark
342 bugzilla_error_quark (void)
344 return g_quark_from_static_string ("bugzilla_error");
347 GHashTable *
348 load_applications (void)
350 GSList *all_applications;
351 GSList *l;
352 char *prev_name = NULL;
353 GError *error = NULL;
355 GHashTable *program_to_application = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) application_free);
357 all_applications = get_all_applications ();
358 for (l = all_applications; l; l = l->next) {
359 GKeyFile *key_file;
360 char *name;
361 char *cname;
362 char *comment;
363 char *bugzilla;
364 char *product;
365 char *component;
366 char *version;
367 char *icon;
368 char *exec;
369 char *other_binaries;
370 char *extra_info_script;
371 GMenuTreeEntry *entry = l->data;
373 if (g_main_context_pending (NULL)) {
374 g_main_context_iteration (NULL, FALSE);
377 if (prev_name && strcmp (gmenu_tree_entry_get_name (entry), prev_name) == 0) {
378 gmenu_tree_item_unref (entry);
379 continue;
381 key_file = g_key_file_new ();
382 g_key_file_load_from_file (key_file, gmenu_tree_entry_get_desktop_file_path (entry),
383 G_KEY_FILE_NONE, &error);
384 if (error) {
385 g_warning ("Couldn't load %s: %s", gmenu_tree_entry_get_desktop_file_path (entry),
386 error->message);
387 g_error_free (error);
388 error = NULL;
389 gmenu_tree_item_unref (entry);
390 continue;
393 if (!g_key_file_has_group (key_file, DESKTOP_ENTRY) || !g_key_file_has_key (key_file, DESKTOP_ENTRY, BUGZILLA_BUGZILLA, &error)) {
394 g_key_file_free (key_file);
395 gmenu_tree_item_unref (entry);
396 if (error)
397 g_error_free (error);
398 continue;
401 name = g_key_file_get_locale_string (key_file, DESKTOP_ENTRY, DESKTOP_NAME, NULL, NULL);
402 cname = g_key_file_get_string (key_file, DESKTOP_ENTRY, DESKTOP_NAME, NULL);
403 comment = g_key_file_get_locale_string (key_file, DESKTOP_ENTRY, DESKTOP_COMMENT, NULL, NULL);
404 bugzilla = g_key_file_get_string (key_file, DESKTOP_ENTRY, BUGZILLA_BUGZILLA, NULL);
405 product = g_key_file_get_string (key_file, DESKTOP_ENTRY, BUGZILLA_PRODUCT, NULL);
406 component = g_key_file_get_string (key_file, DESKTOP_ENTRY, BUGZILLA_COMPONENT, NULL);
407 version = g_key_file_get_string (key_file, DESKTOP_ENTRY, BUGZILLA_VERSION, NULL);
408 icon = g_key_file_get_string (key_file, DESKTOP_ENTRY, DESKTOP_ICON, NULL);
409 exec = g_key_file_get_string (key_file, DESKTOP_ENTRY, DESKTOP_EXEC, NULL);
410 other_binaries = g_key_file_get_string (key_file, DESKTOP_ENTRY, BUGZILLA_OTHER_BINARIES, NULL);
411 extra_info_script = g_key_file_get_string (key_file, DESKTOP_ENTRY, BUGZILLA_EXTRA_INFO_SCRIPT, NULL);
413 add_bugzilla_application (program_to_application,
414 name,
415 cname,
416 comment,
417 bugzilla,
418 product,
419 component,
420 version,
421 icon,
422 exec,
423 other_binaries,
424 extra_info_script);
425 g_free (name);
426 g_free (cname);
427 g_free (comment);
428 g_free (bugzilla);
429 g_free (product);
430 g_free (component);
431 g_free (version);
432 g_free (icon);
433 g_free (exec);
434 g_free (other_binaries);
435 g_free (extra_info_script);
436 g_free (prev_name);
437 prev_name = g_strdup (gmenu_tree_entry_get_name (entry));
438 g_key_file_free (key_file);
439 gmenu_tree_item_unref (entry);
441 g_slist_free (all_applications);
443 load_applets (program_to_application);
445 return program_to_application;
448 gboolean
449 bugzilla_search_for_package (gpointer key, gpointer value, const char *package)
451 BugzillaApplication *app = (BugzillaApplication*) value;
453 if (!strcmp (app->product, package))
454 return TRUE;
456 return FALSE;
460 char *
461 bugzilla_parse_response (SoupMessage *msg, GError **err)
463 GValue value;
464 int bugid = 0;
465 char *url;
467 g_return_val_if_fail ((err == NULL || *err == NULL), NULL);
469 if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
470 g_set_error (err, BUGZILLA_ERROR, BUGZILLA_ERROR_RECV_BAD_STATUS,
471 _("HTTP Response returned bad status code %d"), msg->status_code);
472 return NULL;
475 if (!soup_xmlrpc_parse_method_response (msg->response_body->data,
476 msg->response_body->length,
477 &value, err))
478 return NULL;
480 if (G_VALUE_HOLDS_INT (&value))
481 bugid = g_value_get_int (&value);
482 else if (G_VALUE_HOLDS_STRING (&value))
483 url = g_value_dup_string (&value);
484 else {
485 g_value_unset (&value);
486 g_set_error (err, BUGZILLA_ERROR, BUGZILLA_ERROR_RECV_PARSE_FAILED,
487 _("Unable to parse XML-RPC Response\n\n%s"),
488 msg->response_body->data);
489 return NULL;
491 g_value_unset (&value);
493 return bugid ? g_strdup_printf ("%d", bugid) : url;
496 SoupMessage*
497 bugzilla_create_report (BugzillaApplication *app, int type, GnomeVersionInfo *gnome_version,
498 const char *username, const char *title, const char *text,
499 GtkBuilder *ui, const char *minidump_file, GError **err)
501 SoupMessage *message;
502 const char *uri;
503 GHashTable *report;
504 char *user_agent;
505 char *os_version;
506 gchar *crt;
507 GString *rv;
509 g_return_val_if_fail (app != NULL, NULL);
510 g_return_val_if_fail (gnome_version != NULL, NULL);
511 g_return_val_if_fail (username != NULL, NULL);
512 g_return_val_if_fail (text != NULL, NULL);
513 g_return_val_if_fail (ui != NULL, NULL);
514 g_return_val_if_fail (err == NULL || *err == NULL, NULL);
516 /* FIXME: Hardcoded right now */
517 if (app->bugzilla == NULL || strcmp (app->bugzilla, "GNOME") != 0) {
518 g_set_error (err, BUGZILLA_ERROR, BUGZILLA_ERROR_SEND_NOTSUPPORTED_APP,
519 _("Application does not track its bugs in the GNOME Bugzilla."));
520 return NULL;
523 if (!app->product || !app->component) {
524 g_set_error (err, BUGZILLA_ERROR, BUGZILLA_ERROR_SEND_NOTSUPPORTED_APP,
525 _("Product or component not specified."));
526 return NULL;
529 report = soup_value_hash_new ();
531 soup_value_hash_insert (report, "version", G_TYPE_STRING,
532 app->version ? app->version : "unspecified");
533 soup_value_hash_insert (report, "product", G_TYPE_STRING, app->product);
534 soup_value_hash_insert (report, "component", G_TYPE_STRING, app->component);
535 soup_value_hash_insert (report, "gnome_version", G_TYPE_STRING,
536 gnome_version->gnome_platform);
537 soup_value_hash_insert (report, "reporter", G_TYPE_STRING, username);
539 os_version = get_distro_name ();
540 soup_value_hash_insert (report, "os_version", G_TYPE_STRING, os_version);
541 g_free (os_version);
543 if (type == BUG_TYPE_CRASH) {
544 soup_value_hash_insert (report, "priority", G_TYPE_STRING, "High");
545 soup_value_hash_insert (report, "bug_severity", G_TYPE_STRING, "critical");
548 soup_value_hash_insert (report, "short_desc", G_TYPE_STRING, "title");
550 /* Skip UTF-8 control chars that are not valid in XML.*/
551 rv = g_string_new (NULL);
552 crt = text;
553 while (*crt) {
554 gchar *next = g_utf8_next_char (crt);
555 gunichar uni = g_utf8_get_char (crt);
557 if (!g_unichar_iscntrl (uni) || (uni == '\n') || (uni == '\t'))
558 g_string_append_len (rv, crt, next - crt);
560 crt = next;
563 soup_value_hash_insert (report, "comment", G_TYPE_STRING, rv->str);
564 g_string_free (rv, TRUE);
566 if (minidump_file) {
567 gchar *minidumpbuf;
568 gsize length;
569 GError *error;
570 gchar *base64 = NULL;
572 if (g_file_get_contents (minidump_file, &minidumpbuf, &length, &error)) {
573 base64 = g_base64_encode ((guchar*)minidumpbuf, length);
574 g_free (minidumpbuf);
575 } else {
576 g_error_free (error);
579 if (base64) {
580 gchar *basename;
581 gchar *id;
582 GByteArray *ba;
584 basename = g_path_get_basename (minidump_file);
585 id = g_strndup (basename, strlen (basename) - strlen (".dmp"));
587 soup_value_hash_insert (report, "minidump-id",
588 G_TYPE_STRING, id);
590 /* FIXME: This is broken; for some reason
591 * bug-buddy is base64-encoding the minidump
592 * *twice*. (Once itself, and once by asking
593 * libsoup to do it.) Fix this after verifying
594 * that the server side can deal with the
595 * fix...
597 ba = g_byte_array_sized_new (strlen (base64));
598 g_byte_array_append (ba, (guchar *)base64, strlen (base64));
599 soup_value_hash_insert (report, "minidump",
600 SOUP_TYPE_BYTE_ARRAY, ba);
601 g_byte_array_free (ba, TRUE);
602 g_free (base64);
603 g_free (basename);
604 g_free (id);
608 if (minidump_file)
609 //uri = "http://localhost/breakpad/xmlrpc.py";
610 uri = "http://socorro.gnome.org/collect.py";
611 else
612 uri = "http://bugzilla.gnome.org/bugbuddy.cgi";
614 message = soup_xmlrpc_request_new (uri, "BugBuddy.createBug",
615 G_TYPE_HASH_TABLE, report,
616 G_TYPE_INVALID);
617 g_hash_table_destroy (report);
618 if (message == NULL) {
619 g_set_error (err, BUGZILLA_ERROR, BUGZILLA_ERROR_SEND_ERROR,
620 _("Unable to create XML-RPC message."));
621 return NULL;
624 /* FIXME: wrong User-Agent syntax. Should be "Bug-Buddy (VERSION)" */
625 user_agent = g_strdup_printf ("Bug-Buddy: %s", VERSION);
626 soup_message_headers_append (message->request_headers,
627 "User-Agent", user_agent);
628 g_free (user_agent);
630 return message;