1 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
2 /* Balsa E-Mail Client
3  * Copyright (C) 1997-2013 Stuart Parmenter and others,
4  *                         See the file AUTHORS for a list.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2, or (at your option)
9  * any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19  * 02111-1307, USA.
20  */
21 
22 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
23 # include "config.h"
24 #endif                          /* HAVE_CONFIG_H */
25 #include "balsa-mime-widget-message.h"
26 
27 #include <string.h>
28 #include <gtk/gtk.h>
29 
30 #include "balsa-app.h"
31 #include "balsa-icons.h"
32 #include "send.h"
33 #include "rfc3156.h"
34 #include <glib/gi18n.h>
35 #include "balsa-mime-widget.h"
36 #include "balsa-mime-widget-callbacks.h"
37 #include "sendmsg-window.h"
38 
39 typedef enum _rfc_extbody_t {
40     RFC2046_EXTBODY_FTP,
41     RFC2046_EXTBODY_ANONFTP,
42     RFC2046_EXTBODY_TFTP,
43     RFC2046_EXTBODY_LOCALFILE,
44     RFC2046_EXTBODY_MAILSERVER,
45     RFC2017_EXTBODY_URL,
46     RFC2046_EXTBODY_UNKNOWN
47 } rfc_extbody_t;
48 
49 
50 typedef struct _rfc_extbody_id {
51     gchar *id_string;
52     rfc_extbody_t action;
53 } rfc_extbody_id;
54 
55 
56 static rfc_extbody_id rfc_extbodys[] = {
57     {"ftp", RFC2046_EXTBODY_FTP},
58     {"anon-ftp", RFC2046_EXTBODY_ANONFTP},
59     {"tftp", RFC2046_EXTBODY_TFTP},
60     {"local-file", RFC2046_EXTBODY_LOCALFILE},
61     {"mail-server", RFC2046_EXTBODY_MAILSERVER},
62     {"URL", RFC2017_EXTBODY_URL},
63     {NULL, RFC2046_EXTBODY_UNKNOWN}
64 };
65 
66 
67 /* message/external-body related stuff */
68 static BalsaMimeWidget *bmw_message_extbody_url(LibBalsaMessageBody *
69 						mime_body,
70 						rfc_extbody_t url_type);
71 static BalsaMimeWidget *bmw_message_extbody_mail(LibBalsaMessageBody *
72 						 mime_body);
73 static void extbody_call_url(GtkWidget * button, gpointer data);
74 static void extbody_send_mail(GtkWidget * button,
75 			      LibBalsaMessageBody * mime_body);
76 
77 /* message/rfc822 related stuff */
78 static GtkWidget *bm_header_widget_new(BalsaMessage * bm,
79 				       GtkWidget * const * buttons);
80 #ifdef HAVE_GPGME
81 static void add_header_sigstate(GtkGrid * grid,
82 				GMimeGpgmeSigstat * siginfo);
83 #endif
84 
85 BalsaMimeWidget *
balsa_mime_widget_new_message(BalsaMessage * bm,LibBalsaMessageBody * mime_body,const gchar * content_type,gpointer data)86 balsa_mime_widget_new_message(BalsaMessage * bm,
87 			      LibBalsaMessageBody * mime_body,
88 			      const gchar * content_type, gpointer data)
89 {
90     BalsaMimeWidget *mw = NULL;
91 
92     g_return_val_if_fail(mime_body != NULL, NULL);
93     g_return_val_if_fail(content_type != NULL, NULL);
94 
95     if (!g_ascii_strcasecmp("message/external-body", content_type)) {
96 	gchar *access_type;
97 	rfc_extbody_id *extbody_type = rfc_extbodys;
98 
99 	access_type =
100 	    libbalsa_message_body_get_parameter(mime_body, "access-type");
101 	while (extbody_type->id_string &&
102 	       g_ascii_strcasecmp(extbody_type->id_string, access_type))
103 	    extbody_type++;
104 	switch (extbody_type->action) {
105 	case RFC2046_EXTBODY_FTP:
106 	case RFC2046_EXTBODY_ANONFTP:
107 	case RFC2046_EXTBODY_TFTP:
108 	case RFC2046_EXTBODY_LOCALFILE:
109 	case RFC2017_EXTBODY_URL:
110 	    mw = bmw_message_extbody_url(mime_body, extbody_type->action);
111 	    break;
112 	case RFC2046_EXTBODY_MAILSERVER:
113 	    mw = bmw_message_extbody_mail(mime_body);
114 	    break;
115 	case RFC2046_EXTBODY_UNKNOWN:
116 	    break;
117 	default:
118 	    g_error("Undefined external body action %d!", extbody_type->action);
119 	    break;
120 	}
121 	g_free(access_type);
122     } else if (!g_ascii_strcasecmp("message/rfc822", content_type)) {
123 	GtkWidget *emb_hdrs;
124 
125 	mw = g_object_new(BALSA_TYPE_MIME_WIDGET, NULL);
126 
127 	mw->widget = gtk_frame_new(NULL);
128 
129 	mw->container = gtk_box_new(GTK_ORIENTATION_VERTICAL, BMW_MESSAGE_PADDING);
130 	gtk_container_set_border_width(GTK_CONTAINER(mw->container),
131 				       BMW_MESSAGE_PADDING);
132 	gtk_container_add(GTK_CONTAINER(mw->widget), mw->container);
133 
134         mw->header_widget = emb_hdrs = bm_header_widget_new(bm, NULL);
135 	gtk_box_pack_start(GTK_BOX(mw->container), emb_hdrs, FALSE, FALSE, 0);
136 
137 	balsa_mime_widget_message_set_headers(bm, mw, mime_body);
138     }
139 
140     /* return the created widget (may be NULL) */
141     return mw;
142 }
143 
144 
145 /* ----- message/external-body related stuff ----- */
146 static BalsaMimeWidget *
bmw_message_extbody_url(LibBalsaMessageBody * mime_body,rfc_extbody_t url_type)147 bmw_message_extbody_url(LibBalsaMessageBody * mime_body,
148 			rfc_extbody_t url_type)
149 {
150     GtkWidget *button;
151     GString *msg = NULL;
152     gchar *url;
153     BalsaMimeWidget *mw;
154 
155     if (url_type == RFC2046_EXTBODY_LOCALFILE) {
156 	url = libbalsa_message_body_get_parameter(mime_body, "name");
157 
158 	if (!url)
159 	    return NULL;
160 
161 	msg = g_string_new(_("Content Type: external-body\n"));
162 	g_string_append_printf(msg, _("Access type: local-file\n"));
163 	g_string_append_printf(msg, _("File name: %s"), url);
164     } else if (url_type == RFC2017_EXTBODY_URL) {
165 	gchar *local_name;
166 
167 	local_name = libbalsa_message_body_get_parameter(mime_body, "URL");
168 
169 	if (!local_name)
170 	    return NULL;
171 
172 	url = g_strdup(local_name);
173 	msg = g_string_new(_("Content Type: external-body\n"));
174 	g_string_append_printf(msg, _("Access type: URL\n"));
175 	g_string_append_printf(msg, _("URL: %s"), url);
176 	g_free(local_name);
177     } else {			/* *FTP* */
178 	gchar *ftp_dir, *ftp_name, *ftp_site;
179 
180 	ftp_dir =
181 	    libbalsa_message_body_get_parameter(mime_body, "directory");
182 	ftp_name = libbalsa_message_body_get_parameter(mime_body, "name");
183 	ftp_site = libbalsa_message_body_get_parameter(mime_body, "site");
184 
185 	if (!ftp_name || !ftp_site) {
186 	    g_free(ftp_dir);
187 	    g_free(ftp_name);
188 	    g_free(ftp_site);
189 	    return NULL;
190 	}
191 
192 	if (ftp_dir)
193 	    url = g_strdup_printf("%s://%s/%s/%s",
194 				  url_type == RFC2046_EXTBODY_TFTP
195 				  ? "tftp" : "ftp",
196 				  ftp_site, ftp_dir, ftp_name);
197 	else
198 	    url = g_strdup_printf("%s://%s/%s",
199 				  url_type == RFC2046_EXTBODY_TFTP
200 				  ? "tftp" : "ftp", ftp_site, ftp_name);
201 	msg = g_string_new(_("Content Type: external-body\n"));
202 	g_string_append_printf(msg, _("Access type: %s\n"),
203 			       url_type == RFC2046_EXTBODY_TFTP ? "tftp" :
204 			       url_type ==
205 			       RFC2046_EXTBODY_FTP ? "ftp" : "anon-ftp");
206 	g_string_append_printf(msg, _("FTP site: %s\n"), ftp_site);
207 	if (ftp_dir)
208 	    g_string_append_printf(msg, _("Directory: %s\n"), ftp_dir);
209 	g_string_append_printf(msg, _("File name: %s"), ftp_name);
210 	g_free(ftp_dir);
211 	g_free(ftp_name);
212 	g_free(ftp_site);
213     }
214 
215     /* now create & return the widget... */
216     mw = g_object_new(BALSA_TYPE_MIME_WIDGET, NULL);
217 
218     mw->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, BMW_VBOX_SPACE);
219     gtk_container_set_border_width(GTK_CONTAINER(mw->widget),
220 				   BMW_CONTAINER_BORDER);
221 
222     gtk_box_pack_start(GTK_BOX(mw->widget), gtk_label_new(msg->str), FALSE,
223 		       FALSE, 0);
224     g_string_free(msg, TRUE);
225 
226     button = gtk_button_new_with_label(url);
227     gtk_box_pack_start(GTK_BOX(mw->widget), button, FALSE, FALSE,
228 		       BMW_BUTTON_PACK_SPACE);
229     g_object_set_data_full(G_OBJECT(button), "call_url", url,
230 			   (GDestroyNotify) g_free);
231     g_signal_connect(G_OBJECT(button), "clicked",
232 		     G_CALLBACK(extbody_call_url), NULL);
233 
234     return mw;
235 }
236 
237 
238 static BalsaMimeWidget *
bmw_message_extbody_mail(LibBalsaMessageBody * mime_body)239 bmw_message_extbody_mail(LibBalsaMessageBody * mime_body)
240 {
241     GtkWidget *button;
242     GString *msg = NULL;
243     gchar *mail_subject, *mail_site;
244     BalsaMimeWidget *mw;
245 
246     mail_site = libbalsa_message_body_get_parameter(mime_body, "server");
247 
248     if (!mail_site)
249 	return NULL;
250 
251     mail_subject =
252 	libbalsa_message_body_get_parameter(mime_body, "subject");
253 
254     msg = g_string_new(_("Content Type: external-body\n"));
255     g_string_append(msg, _("Access type: mail-server\n"));
256     g_string_append_printf(msg, _("Mail server: %s\n"), mail_site);
257     if (mail_subject)
258 	g_string_append_printf(msg, _("Subject: %s\n"), mail_subject);
259     g_free(mail_subject);
260     g_free(mail_site);
261 
262     /* now create & return the widget... */
263     mw = g_object_new(BALSA_TYPE_MIME_WIDGET, NULL);
264 
265     mw->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, BMW_VBOX_SPACE);
266     gtk_container_set_border_width(GTK_CONTAINER(mw->widget),
267 				   BMW_CONTAINER_BORDER);
268 
269     gtk_box_pack_start(GTK_BOX(mw->widget), gtk_label_new(msg->str), FALSE,
270 		       FALSE, 0);
271     g_string_free(msg, TRUE);
272 
273     button =
274 	gtk_button_new_with_mnemonic(_
275 				     ("Se_nd message to obtain this part"));
276     gtk_box_pack_start(GTK_BOX(mw->widget), button, FALSE, FALSE,
277 		       BMW_BUTTON_PACK_SPACE);
278     g_signal_connect(G_OBJECT(button), "clicked",
279 		     G_CALLBACK(extbody_send_mail), (gpointer) mime_body);
280 
281 
282     return mw;
283 }
284 
285 
286 static void
extbody_call_url(GtkWidget * button,gpointer data)287 extbody_call_url(GtkWidget * button, gpointer data)
288 {
289     gchar *url = g_object_get_data(G_OBJECT(button), "call_url");
290     GdkScreen *screen;
291     GError *err = NULL;
292 
293     g_return_if_fail(url);
294     screen = gtk_widget_get_screen(button);
295     gtk_show_uri(screen, url, gtk_get_current_event_time(), &err);
296     if (err) {
297 	balsa_information(LIBBALSA_INFORMATION_WARNING,
298 			  _("Error showing %s: %s\n"), url, err->message);
299 	g_error_free(err);
300     }
301 }
302 
303 static void
extbody_send_mail(GtkWidget * button,LibBalsaMessageBody * mime_body)304 extbody_send_mail(GtkWidget * button, LibBalsaMessageBody * mime_body)
305 {
306     LibBalsaMessage *message;
307     LibBalsaMessageBody *body;
308     gchar *data;
309     GError *err = NULL;
310     LibBalsaMsgCreateResult result;
311 
312     /* create a message */
313     message = libbalsa_message_new();
314     message->headers->from = internet_address_list_new();
315     internet_address_list_add(message->headers->from,
316                               balsa_app.current_ident->ia);
317 
318     data = libbalsa_message_body_get_parameter(mime_body, "subject");
319     if (data) {
320 	libbalsa_message_set_subject(message, data);
321         g_free(data);
322     }
323 
324     data = libbalsa_message_body_get_parameter(mime_body, "server");
325     message->headers->to_list = internet_address_list_parse_string(data);
326     g_free(data);
327 
328     /* the original body my have some data to be returned as commands... */
329     body = libbalsa_message_body_new(message);
330 
331     if(libbalsa_message_body_get_content(mime_body, &data, &err)<0) {
332         balsa_information(LIBBALSA_INFORMATION_ERROR,
333                           _("Could not get a part: %s"),
334                           err ? err->message : "Unknown error");
335         g_clear_error(&err);
336     }
337 
338     if (data) {
339 	gchar *p;
340 
341 	/* ignore everything before the first two newlines */
342 	if ((p = strstr(data, "\n\n")))
343 	    body->buffer = g_strdup(p + 2);
344 	else
345 	    body->buffer = g_strdup(data);
346 	g_free(data);
347     }
348     if (mime_body->charset)
349 	body->charset = g_strdup(mime_body->charset);
350     else
351 	body->charset = g_strdup("US-ASCII");
352     libbalsa_message_append_part(message, body);
353 #if ENABLE_ESMTP
354     result = libbalsa_message_send(message, balsa_app.outbox, NULL,
355 				   balsa_find_sentbox_by_url,
356 				   balsa_app.current_ident->smtp_server,
357 				   FALSE, balsa_app.debug, &err);
358 #else
359     result = libbalsa_message_send(message, balsa_app.outbox, NULL,
360 				   balsa_find_sentbox_by_url,
361 				   FALSE, balsa_app.debug, &err);
362 #endif
363     if (result != LIBBALSA_MESSAGE_CREATE_OK)
364 	libbalsa_information(LIBBALSA_INFORMATION_ERROR,
365 			     _("Sending the external body request failed: %s"),
366 			     err ? err->message : "?");
367     g_error_free(err);
368     g_object_unref(G_OBJECT(message));
369 }
370 
371 
372 /* ----- message/rfc822 related stuff ----- */
373 
374 BalsaMimeWidget *
balsa_mime_widget_new_message_tl(BalsaMessage * bm,GtkWidget * const * tl_buttons)375 balsa_mime_widget_new_message_tl(BalsaMessage * bm,
376                                  GtkWidget * const * tl_buttons)
377 {
378     GtkWidget *headers;
379     BalsaMimeWidget *mw;
380 
381     mw = g_object_new(BALSA_TYPE_MIME_WIDGET, NULL);
382 
383     mw->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, BMW_MESSAGE_PADDING);
384     gtk_container_set_border_width(GTK_CONTAINER(mw->widget), BMW_MESSAGE_PADDING);
385 
386     mw->header_widget = headers = bm_header_widget_new(bm, tl_buttons);
387     gtk_box_pack_start(GTK_BOX(mw->widget), headers, FALSE, FALSE, 0);
388 
389     mw->container = gtk_box_new(GTK_ORIENTATION_VERTICAL, BMW_MESSAGE_PADDING);
390     gtk_box_pack_start(GTK_BOX(mw->widget), mw->container, TRUE, TRUE,
391 		       BMW_CONTAINER_BORDER - BMW_MESSAGE_PADDING);
392 
393     return mw;
394 }
395 
396 
397 /* Callback for the "realized" signal; set header frame and text base
398  * color when first realized. */
399 #define BALSA_MESSAGE_GRID "balsa-message-grid"
400 #define bm_header_widget_get_grid(header_widget) \
401     g_object_get_data(G_OBJECT(header_widget), BALSA_MESSAGE_GRID)
402 
403 static void
bm_header_ctx_menu_reply(GtkWidget * menu_item,LibBalsaMessageBody * part)404 bm_header_ctx_menu_reply(GtkWidget * menu_item,
405                          LibBalsaMessageBody *part)
406 {
407     sendmsg_window_reply_embedded(part, SEND_REPLY);
408 }
409 
410 static void
bm_header_extend_popup(GtkWidget * widget,GtkMenu * menu,gpointer arg)411 bm_header_extend_popup(GtkWidget * widget, GtkMenu * menu, gpointer arg)
412 {
413     GtkWidget *menu_item, *submenu;
414     GtkWidget *separator = gtk_separator_menu_item_new();
415 
416     gtk_menu_shell_append(GTK_MENU_SHELL(menu), separator);
417     gtk_widget_show(separator);
418     menu_item = gtk_menu_item_new_with_label(_("Reply..."));
419     g_signal_connect(G_OBJECT(menu_item), "activate",
420                      G_CALLBACK(bm_header_ctx_menu_reply),
421                      arg);
422     gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
423     gtk_widget_show(menu_item);
424 
425 
426     menu_item = gtk_menu_item_new_with_mnemonic(_("_Copy to folder..."));
427     gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
428     gtk_widget_show(menu_item);
429 
430     submenu =
431         balsa_mblist_mru_menu(GTK_WINDOW
432                               (gtk_widget_get_toplevel(widget)),
433                               &balsa_app.folder_mru,
434                               G_CALLBACK(balsa_message_copy_part), arg);
435     gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item),
436                               submenu);
437     gtk_widget_show_all(submenu);
438 }
439 
440 static GtkWidget *
bm_header_widget_new(BalsaMessage * bm,GtkWidget * const * buttons)441 bm_header_widget_new(BalsaMessage * bm, GtkWidget * const * buttons)
442 {
443     GtkWidget *grid;
444     GtkWidget *info_bar_widget;
445     GtkInfoBar *info_bar;
446     GtkWidget *content_area;
447     GtkWidget *action_area;
448     GtkWidget *widget;
449 
450     grid = gtk_grid_new();
451     gtk_grid_set_column_spacing(GTK_GRID(grid), 12);
452     gtk_widget_show(grid);
453 
454     g_signal_connect(grid, "focus_in_event",
455 		     G_CALLBACK(balsa_mime_widget_limit_focus), bm);
456     g_signal_connect(grid, "focus_out_event",
457 		     G_CALLBACK(balsa_mime_widget_unlimit_focus), bm);
458     g_signal_connect(grid, "key_press_event",
459 		     G_CALLBACK(balsa_mime_widget_key_press_event), bm);
460 
461     info_bar_widget = gtk_info_bar_new();
462     info_bar = GTK_INFO_BAR(info_bar_widget);
463     content_area = gtk_info_bar_get_content_area(info_bar);
464     gtk_container_add(GTK_CONTAINER(content_area), grid);
465 
466     action_area = gtk_info_bar_get_action_area(info_bar);
467     if (!bm->face_box) {
468         bm->face_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
469         gtk_container_add(GTK_CONTAINER(action_area), bm->face_box);
470         gtk_button_box_set_child_non_homogeneous(GTK_BUTTON_BOX(action_area),
471                                                  bm->face_box, TRUE);
472     }
473 
474     if (buttons) {
475         while (*buttons) {
476             gtk_container_add(GTK_CONTAINER(action_area), *buttons++);
477         }
478     }
479 
480     widget = gtk_frame_new(NULL);
481     gtk_frame_set_shadow_type(GTK_FRAME(widget), GTK_SHADOW_IN);
482     gtk_container_add(GTK_CONTAINER(widget), info_bar_widget);
483 
484     g_object_set_data(G_OBJECT(widget), BALSA_MESSAGE_GRID, grid);
485 
486     return widget;
487 }
488 
489 static gboolean
label_size_allocate_cb(GtkLabel * label,GdkRectangle * rectangle,GtkWidget * expander)490 label_size_allocate_cb(GtkLabel * label, GdkRectangle * rectangle,
491                        GtkWidget * expander)
492 {
493     PangoLayout *layout;
494 
495     layout = gtk_label_get_layout(label);
496 
497     if (pango_layout_is_wrapped(layout)
498         || pango_layout_is_ellipsized(layout))
499         gtk_widget_show(expander);
500     else
501         gtk_widget_hide(expander);
502 
503     return FALSE;
504 }
505 
506 static void
expanded_cb(GtkExpander * expander,GParamSpec * arg1,GtkLabel * label)507 expanded_cb(GtkExpander * expander, GParamSpec * arg1, GtkLabel * label)
508 {
509     if (gtk_expander_get_expanded(expander)) {
510         gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
511         gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_NONE);
512     } else {
513         gtk_label_set_line_wrap(GTK_LABEL(label), FALSE);
514         gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END);
515     }
516 }
517 
518 static void
add_header_gchar(BalsaMessage * bm,GtkGrid * grid,const gchar * header,const gchar * label,const gchar * value)519 add_header_gchar(BalsaMessage * bm, GtkGrid * grid,
520 		 const gchar * header, const gchar * label,
521 		 const gchar * value)
522 {
523     gint row;
524     GtkWidget *lab;
525     PangoAttrList *attr_list;
526     PangoFontDescription *font_desc;
527     PangoAttribute *attr;
528 
529     if (!(bm->shown_headers == HEADERS_ALL ||
530 	  libbalsa_find_word(header, balsa_app.selected_headers)))
531 	return;
532 
533     row = 0;
534     while (gtk_grid_get_child_at(grid, 0, row))
535         row++;
536 
537     attr_list = pango_attr_list_new();
538     font_desc =
539         pango_font_description_from_string(strcmp(header, "subject") ?
540                                            balsa_app.message_font :
541                                            balsa_app.subject_font);
542     attr = pango_attr_font_desc_new(font_desc);
543     pango_font_description_free(font_desc);
544     pango_attr_list_insert(attr_list, attr);
545 
546     lab = gtk_label_new(label);
547     gtk_label_set_attributes(GTK_LABEL(lab), attr_list);
548     gtk_grid_attach(grid, lab, 0, row, 1, 1);
549     gtk_label_set_selectable(GTK_LABEL(lab), TRUE);
550     gtk_misc_set_alignment(GTK_MISC(lab), 0, 0);
551     gtk_widget_show(lab);
552 
553     if (value && *value != '\0') {
554         gchar *sanitized;
555         GtkWidget *expander;
556         GtkWidget *hbox;
557 
558         sanitized = g_strdup(value);
559         libbalsa_utf8_sanitize(&sanitized,
560                                balsa_app.convert_unknown_8bit, NULL);
561         lab = gtk_label_new(sanitized);
562         g_free(sanitized);
563 
564         gtk_label_set_attributes(GTK_LABEL(lab), attr_list);
565         gtk_label_set_line_wrap_mode(GTK_LABEL(lab), PANGO_WRAP_WORD_CHAR);
566         gtk_label_set_selectable(GTK_LABEL(lab), TRUE);
567         gtk_misc_set_alignment(GTK_MISC(lab), 0, 0);
568         gtk_widget_set_hexpand(lab, TRUE);
569 
570         expander = gtk_expander_new(NULL);
571         g_signal_connect(expander, "notify::expanded",
572                          G_CALLBACK(expanded_cb), lab);
573 
574         if(bm->shown_headers == HEADERS_ALL) {
575             gtk_label_set_line_wrap(GTK_LABEL(lab), TRUE);
576             gtk_expander_set_expanded(GTK_EXPANDER(expander), TRUE);
577         } else {
578             gtk_label_set_ellipsize(GTK_LABEL(lab), PANGO_ELLIPSIZE_END);
579             gtk_expander_set_expanded(GTK_EXPANDER(expander), FALSE);
580         }
581         g_signal_connect(lab, "size-allocate",
582                          G_CALLBACK(label_size_allocate_cb), expander);
583 
584         hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
585         gtk_container_add(GTK_CONTAINER(hbox), lab);
586         gtk_container_add(GTK_CONTAINER(hbox), expander);
587         gtk_widget_show_all(hbox);
588         gtk_grid_attach(grid, hbox, 1, row, 1, 1);
589     }
590 
591     pango_attr_list_unref(attr_list);
592 }
593 
594 static void
add_header_address_list(BalsaMessage * bm,GtkGrid * grid,gchar * header,gchar * label,InternetAddressList * list)595 add_header_address_list(BalsaMessage * bm, GtkGrid * grid,
596 			gchar * header, gchar * label,
597 			InternetAddressList * list)
598 {
599     gchar *value;
600 
601     if (list == NULL || internet_address_list_length(list) == 0)
602 	return;
603 
604     if (!(bm->shown_headers == HEADERS_ALL ||
605 	  libbalsa_find_word(header, balsa_app.selected_headers)))
606 	return;
607 
608     value = internet_address_list_to_string(list, FALSE);
609 
610     add_header_gchar(bm, grid, header, label, value);
611 
612     g_free(value);
613 }
614 
615 static void
foreach_label(GtkWidget * widget,LibBalsaMessageBody * part)616 foreach_label(GtkWidget * widget, LibBalsaMessageBody * part)
617 {
618     if (GTK_IS_CONTAINER(widget))
619         gtk_container_foreach(GTK_CONTAINER(widget),
620                               (GtkCallback) foreach_label, part);
621     else if (g_signal_lookup("populate-popup",
622                              G_TYPE_FROM_INSTANCE(widget)))
623         g_signal_connect(widget, "populate-popup",
624                          G_CALLBACK(bm_header_extend_popup), part);
625 }
626 
627 void
balsa_mime_widget_message_set_headers(BalsaMessage * bm,BalsaMimeWidget * mw,LibBalsaMessageBody * part)628 balsa_mime_widget_message_set_headers(BalsaMessage * bm, BalsaMimeWidget *mw,
629 				      LibBalsaMessageBody * part)
630 {
631     GtkWidget *widget;
632     GtkGrid *grid;
633 
634     balsa_mime_widget_message_set_headers_d(bm, mw, part->embhdrs, part->parts,
635                                             part->embhdrs
636                                             ? part->embhdrs->subject
637                                             : NULL );
638     if (!(widget = mw->header_widget))
639 	return;
640     grid = bm_header_widget_get_grid(widget);
641     gtk_container_foreach(GTK_CONTAINER(grid), (GtkCallback) foreach_label,
642                           part);
643 }
644 
645 void
balsa_mime_widget_message_set_headers_d(BalsaMessage * bm,BalsaMimeWidget * mw,LibBalsaMessageHeaders * headers,LibBalsaMessageBody * part,const gchar * subject)646 balsa_mime_widget_message_set_headers_d(BalsaMessage * bm,
647                                         BalsaMimeWidget *mw,
648                                         LibBalsaMessageHeaders *headers,
649                                         LibBalsaMessageBody *part,
650                                         const gchar *subject)
651 {
652     GtkGrid *grid;
653     GList *p;
654     gchar *date;
655     GtkWidget * widget;
656 
657     if (!(widget = mw->header_widget))
658 	return;
659 
660     grid = bm_header_widget_get_grid(widget);
661     gtk_container_foreach(GTK_CONTAINER(grid),
662                           (GtkCallback) gtk_widget_destroy, NULL);
663 
664     if (!headers) {
665         /* Gmail sometimes fails to do that. */
666         add_header_gchar(bm, grid, "subject", _("Error:"),
667                          _("IMAP server did not report message structure"));
668         return;
669     }
670 
671     if (bm->shown_headers == HEADERS_NONE) {
672         g_signal_connect(G_OBJECT(widget), "realize",
673                          G_CALLBACK(gtk_widget_hide), NULL);
674 	return;
675     }
676 
677     bm->tab_position = 0;
678 
679     add_header_gchar(bm, grid, "subject", _("Subject:"), subject);
680 
681     date = libbalsa_message_headers_date_to_utf8(headers,
682 						 balsa_app.date_string);
683     add_header_gchar(bm, grid, "date", _("Date:"), date);
684     g_free(date);
685 
686     if (headers->from) {
687 	gchar *from =
688 	    internet_address_list_to_string(headers->from, FALSE);
689 	add_header_gchar(bm, grid, "from", _("From:"), from);
690 	g_free(from);
691     }
692 
693     if (headers->reply_to) {
694 	gchar *reply_to =
695 	    internet_address_list_to_string(headers->reply_to, FALSE);
696 	add_header_gchar(bm, grid, "reply-to", _("Reply-To:"), reply_to);
697 	g_free(reply_to);
698     }
699     add_header_address_list(bm, grid, "to", _("To:"), headers->to_list);
700     add_header_address_list(bm, grid, "cc", _("Cc:"), headers->cc_list);
701     add_header_address_list(bm, grid, "bcc", _("Bcc:"), headers->bcc_list);
702 
703 #if BALSA_SHOW_FCC_AS_WELL_AS_X_BALSA_FCC
704     if (headers->fcc_url)
705 	add_header_gchar(bm, grid, "fcc", _("Fcc:"), headers->fcc_url);
706 #endif
707 
708     if (headers->dispnotify_to) {
709 	gchar *mdn_to =
710 	    internet_address_list_to_string(headers->dispnotify_to, FALSE);
711 	add_header_gchar(bm, grid, "disposition-notification-to",
712 			 _("Disposition-Notification-To:"), mdn_to);
713 	g_free(mdn_to);
714     }
715 
716     /* remaining headers */
717     for (p = g_list_first(headers->user_hdrs); p; p = g_list_next(p)) {
718 	gchar **pair = p->data;
719 	gchar *hdr;
720 
721 	hdr = g_strconcat(pair[0], ":", NULL);
722 	add_header_gchar(bm, grid, pair[0], hdr, pair[1]);
723 	g_free(hdr);
724     }
725 
726 #ifdef HAVE_GPGME
727     if (part) {
728 	if (part->parts
729 	    && part->parts->next
730 	    && part->parts->next->sig_info
731 	    && part->parts->next->sig_info->status !=
732 	    GPG_ERR_NOT_SIGNED)
733 	    /* top-level part is RFC 3156 or RFC 2633 signed */
734 	    add_header_sigstate(grid, part->parts->next->sig_info);
735 	else if (part->sig_info
736 		 && part->sig_info->status != GPG_ERR_NOT_SIGNED)
737 	    /* top-level is OpenPGP (RFC 2440) signed */
738 	    add_header_sigstate(grid, part->sig_info);
739     }
740 #endif
741 }
742 
743 
744 #ifdef HAVE_GPGME
745 /*
746  * Add the short status of a signature info siginfo to the message headers in
747  * view
748  */
749 static void
add_header_sigstate(GtkGrid * grid,GMimeGpgmeSigstat * siginfo)750 add_header_sigstate(GtkGrid * grid, GMimeGpgmeSigstat * siginfo)
751 {
752     gchar *format;
753     gchar *msg;
754     GtkWidget *label;
755     gint row;
756 
757     format = siginfo->status ==
758         GPG_ERR_NO_ERROR ? "<i>%s%s</i>" : "<b><i>%s%s</i><b>";
759     msg = g_markup_printf_escaped
760         (format,
761          libbalsa_gpgme_sig_protocol_name(siginfo->protocol),
762          libbalsa_gpgme_sig_stat_to_gchar(siginfo->status));
763 
764     label = gtk_label_new(NULL);
765     gtk_label_set_markup(GTK_LABEL(label), msg);
766     g_free(msg);
767     gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
768     gtk_widget_show(label);
769 
770     row = 0;
771     while (gtk_grid_get_child_at(grid, 0, row))
772         row++;
773     gtk_grid_attach(grid, label, 0, row, 2, 1);
774 }
775 #endif
776