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-message.h"
26 
27 #include <errno.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <ctype.h>
31 #include <iconv.h>
32 #include <sys/utsname.h>
33 
34 #include "balsa-app.h"
35 #include "balsa-icons.h"
36 #include "mime.h"
37 #include "misc.h"
38 #include "html.h"
39 #include <glib/gi18n.h>
40 #include "balsa-mime-widget.h"
41 #include "balsa-mime-widget-callbacks.h"
42 #include "balsa-mime-widget-message.h"
43 #include "balsa-mime-widget-image.h"
44 #include "balsa-mime-widget-crypto.h"
45 
46 #include <gdk/gdkkeysyms.h>
47 #include <gdk-pixbuf/gdk-pixbuf.h>
48 
49 #include "send.h"
50 #include "quote-color.h"
51 #include "sendmsg-window.h"
52 #include "libbalsa-vfs.h"
53 
54 #if HAVE_MACOSX_DESKTOP
55 #  include "macosx-helpers.h"
56 #endif
57 
58 #ifdef HAVE_GPGME
59 #  include "gmime-part-rfc2440.h"
60 #endif
61 
62 #  define FORWARD_SEARCH(iter, text, match_begin, match_end)            \
63     gtk_text_iter_forward_search((iter), (text),                        \
64     GTK_TEXT_SEARCH_CASE_INSENSITIVE, (match_begin), (match_end), NULL)
65 #  define BACKWARD_SEARCH(iter, text, match_begin, match_end)           \
66     gtk_text_iter_backward_search((iter), (text),                       \
67     GTK_TEXT_SEARCH_CASE_INSENSITIVE, (match_begin), (match_end), NULL)
68 
69 enum {
70     SELECT_PART,
71     LAST_SIGNAL,
72 };
73 
74 enum {
75     PART_INFO_COLUMN = 0,
76     PART_NUM_COLUMN,
77     MIME_ICON_COLUMN,
78     MIME_TYPE_COLUMN,
79     NUM_COLUMNS
80 };
81 
82 typedef struct _BalsaPartInfoClass BalsaPartInfoClass;
83 
84 struct _BalsaPartInfo {
85     GObject parent_object;
86 
87     LibBalsaMessageBody *body;
88 
89     /* MIME widget */
90     BalsaMimeWidget *mime_widget;
91 
92     /* The contect menu; referenced */
93     GtkWidget *popup_menu;
94 
95     /* the path in the tree view */
96     GtkTreePath *path;
97 };
98 
99 struct _BalsaPartInfoClass {
100     GObjectClass parent_class;
101 };
102 
103 static GType balsa_part_info_get_type();
104 
105 #define TYPE_BALSA_PART_INFO          \
106         (balsa_part_info_get_type ())
107 #define BALSA_PART_INFO(obj)          \
108         (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_BALSA_PART_INFO, BalsaPartInfo))
109 #define IS_BALSA_PART_INFO(obj)       \
110         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_BALSA_PART_INFO))
111 
112 static gint balsa_message_signals[LAST_SIGNAL];
113 
114 /* widget */
115 static void balsa_message_class_init(BalsaMessageClass * klass);
116 static void balsa_message_init(BalsaMessage * bm);
117 
118 static void balsa_message_destroy(GObject * object);
119 
120 static void display_headers(BalsaMessage * bm);
121 static void display_content(BalsaMessage * bm);
122 
123 static LibBalsaMessageBody *add_part(BalsaMessage *bm, BalsaPartInfo *info,
124                                      GtkWidget * container);
125 static LibBalsaMessageBody *add_multipart(BalsaMessage * bm,
126                                           LibBalsaMessageBody * parent,
127                                           GtkWidget * container);
128 static void select_part(BalsaMessage * bm, BalsaPartInfo *info);
129 static void tree_activate_row_cb(GtkTreeView *treeview, GtkTreePath *arg1,
130                                  GtkTreeViewColumn *arg2, gpointer user_data);
131 static gboolean tree_menu_popup_key_cb(GtkWidget *widget, gpointer user_data);
132 static gboolean tree_button_press_cb(GtkWidget * widget, GdkEventButton * event,
133                                      gpointer data);
134 
135 static void part_info_init(BalsaMessage * bm, BalsaPartInfo * info);
136 static void part_context_save_all_cb(GtkWidget * menu_item, GList * info_list);
137 static void part_context_dump_all_cb(GtkWidget * menu_item, GList * info_list);
138 static void part_create_menu (BalsaPartInfo* info);
139 
140 static GtkNotebookClass *parent_class = NULL;
141 
142 /* stuff needed for sending Message Disposition Notifications */
143 static void handle_mdn_request(GtkWindow *parent, LibBalsaMessage *message);
144 static LibBalsaMessage *create_mdn_reply (const LibBalsaIdentity *mdn_ident,
145                                           LibBalsaMessage *for_msg,
146                                           gboolean manual);
147 static GtkWidget* create_mdn_dialog (GtkWindow *parent, gchar *sender,
148 				     gchar *mdn_to_address,
149                                      LibBalsaMessage *send_msg,
150                                      LibBalsaIdentity *mdn_ident);
151 static void mdn_dialog_response(GtkWidget * dialog, gint response,
152                                 gpointer user_data);
153 
154 static void balsa_part_info_init(GObject *object, gpointer data);
155 static BalsaPartInfo* balsa_part_info_new(LibBalsaMessageBody* body);
156 static void balsa_part_info_free(GObject * object);
157 
158 
159 #ifdef HAVE_GPGME
160 static LibBalsaMsgProtectState balsa_message_scan_signatures(LibBalsaMessageBody *body,
161 							     LibBalsaMessage * message);
162 static GdkPixbuf * get_crypto_content_icon(LibBalsaMessageBody * body,
163 					   const gchar * content_type,
164 					   gchar ** icon_title);
165 static void message_recheck_crypto_cb(GtkWidget * button, BalsaMessage * bm);
166 #endif /* HAVE_GPGME */
167 
168 
169 static void
balsa_part_info_class_init(BalsaPartInfoClass * klass)170 balsa_part_info_class_init(BalsaPartInfoClass *klass)
171 {
172     GObjectClass *object_class = G_OBJECT_CLASS(klass);
173 
174     object_class->finalize = balsa_part_info_free;
175 }
176 
177 static GType
balsa_part_info_get_type()178 balsa_part_info_get_type()
179 {
180     static GType balsa_part_info_type = 0 ;
181 
182     if (!balsa_part_info_type) {
183         static const GTypeInfo balsa_part_info_info =
184             {
185                 sizeof (BalsaPartInfoClass),
186                 (GBaseInitFunc) NULL,
187                 (GBaseFinalizeFunc) NULL,
188                 (GClassInitFunc) balsa_part_info_class_init,
189                 (GClassFinalizeFunc) NULL,
190                 NULL,
191                 sizeof(BalsaPartInfo),
192                 0,
193                 (GInstanceInitFunc) balsa_part_info_init
194             };
195         balsa_part_info_type =
196            g_type_register_static (G_TYPE_OBJECT, "BalsaPartInfo",
197                                    &balsa_part_info_info, 0);
198     }
199     return balsa_part_info_type;
200 }
201 
202 GType
balsa_message_get_type()203 balsa_message_get_type()
204 {
205     static GType balsa_message_type = 0;
206 
207     if (!balsa_message_type) {
208         static const GTypeInfo balsa_message_info = {
209             sizeof(BalsaMessageClass),
210             NULL,               /* base_init */
211             NULL,               /* base_finalize */
212             (GClassInitFunc) balsa_message_class_init,
213             NULL,               /* class_finalize */
214             NULL,               /* class_data */
215             sizeof(BalsaMessage),
216             0,                  /* n_preallocs */
217             (GInstanceInitFunc) balsa_message_init
218         };
219 
220         balsa_message_type =
221             g_type_register_static(GTK_TYPE_NOTEBOOK, "BalsaMessage",
222                                    &balsa_message_info, 0);
223     }
224 
225     return balsa_message_type;
226 }
227 
228 static void
balsa_message_class_init(BalsaMessageClass * klass)229 balsa_message_class_init(BalsaMessageClass * klass)
230 {
231     GObjectClass *object_class;
232 
233     object_class = G_OBJECT_CLASS(klass);
234 
235     balsa_message_signals[SELECT_PART] =
236         g_signal_new("select-part",
237                      G_TYPE_FROM_CLASS(object_class),
238                      G_SIGNAL_RUN_FIRST,
239                      G_STRUCT_OFFSET(BalsaMessageClass, select_part),
240                      NULL, NULL,
241                      g_cclosure_marshal_VOID__VOID,
242                      G_TYPE_NONE, 0);
243 
244     object_class->dispose = balsa_message_destroy;
245 
246     parent_class = g_type_class_peek_parent(klass);
247 
248     klass->select_part = NULL;
249 
250 }
251 
252 /* Helpers for balsa_message_init. */
253 #define BALSA_MESSAGE_ATTACH_BTN "balsa-message-attach-btn"
254 #define bm_header_widget_att_button(balsa_message) \
255     g_object_get_data(G_OBJECT(balsa_message), BALSA_MESSAGE_ATTACH_BTN)
256 
257 static void
balsa_headers_attachments_popup(GtkButton * button,BalsaMessage * bm)258 balsa_headers_attachments_popup(GtkButton * button, BalsaMessage * bm)
259 {
260     if (bm->parts_popup)
261 	gtk_menu_popup(GTK_MENU(bm->parts_popup), NULL, NULL, NULL, NULL, 0,
262 		       gtk_get_current_event_time());
263 }
264 
265 
266 static GtkWidget **
bm_header_tl_buttons(BalsaMessage * bm)267 bm_header_tl_buttons(BalsaMessage * bm)
268 {
269     GPtrArray *array;
270     GtkWidget *button;
271 
272     array = g_ptr_array_new();
273 
274 #ifdef HAVE_GPGME
275     button = gtk_button_new();
276     gtk_widget_set_tooltip_text(button,
277 			        _("Check cryptographic signature"));
278     g_signal_connect(G_OBJECT(button), "focus_in_event",
279 		     G_CALLBACK(balsa_mime_widget_limit_focus),
280 		     (gpointer) bm);
281     g_signal_connect(G_OBJECT(button), "focus_out_event",
282 		     G_CALLBACK(balsa_mime_widget_unlimit_focus),
283 		     (gpointer) bm);
284     gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
285     gtk_container_add(GTK_CONTAINER(button),
286 		      gtk_image_new_from_stock(BALSA_PIXMAP_GPG_RECHECK,
287 					       GTK_ICON_SIZE_LARGE_TOOLBAR));
288     g_signal_connect(button, "clicked",
289 		     G_CALLBACK(message_recheck_crypto_cb), bm);
290     g_ptr_array_add(array, button);
291 #endif
292 
293     button = gtk_button_new();
294     gtk_widget_set_tooltip_text(button,
295 			        _("Select message part to display"));
296     g_signal_connect(G_OBJECT(button), "focus_in_event",
297 		     G_CALLBACK(balsa_mime_widget_limit_focus),
298 		     (gpointer) bm);
299     g_signal_connect(G_OBJECT(button), "focus_out_event",
300 		     G_CALLBACK(balsa_mime_widget_unlimit_focus),
301 		     (gpointer) bm);
302     gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
303     gtk_container_add(GTK_CONTAINER(button),
304 		      gtk_image_new_from_stock(BALSA_PIXMAP_ATTACHMENT,
305 					       GTK_ICON_SIZE_LARGE_TOOLBAR));
306     g_signal_connect(button, "clicked",
307 		     G_CALLBACK(balsa_headers_attachments_popup), bm);
308     g_signal_connect(button, "key_press_event",
309 		     G_CALLBACK(balsa_mime_widget_key_press_event), bm);
310     g_object_set_data(G_OBJECT(bm), BALSA_MESSAGE_ATTACH_BTN, button);
311     g_ptr_array_add(array, button);
312 
313     g_ptr_array_add(array, NULL);
314 
315     return (GtkWidget **) g_ptr_array_free(array, FALSE);
316 }
317 
318 
319 /* Callback for the "style-updated" signal; set the message background to
320  * match the base color of the content in the tree-view. */
321 static void
bm_on_set_style(GtkWidget * widget,BalsaMessage * bm)322 bm_on_set_style(GtkWidget * widget,
323 	        BalsaMessage * bm)
324 {
325     GtkStyleContext *context;
326     GdkRGBA rgba;
327 
328     context = gtk_widget_get_style_context(bm->treeview);
329     gtk_style_context_get_background_color(context, GTK_STATE_FLAG_NORMAL,
330                                            &rgba);
331     gtk_widget_override_background_color(bm->scroll,
332                                          GTK_STATE_FLAG_NORMAL, &rgba);
333 }
334 
335 static void
on_content_size_alloc(GtkWidget * widget,GtkAllocation * allocation,gpointer user_data)336 on_content_size_alloc(GtkWidget * widget, GtkAllocation * allocation,
337 		      gpointer user_data)
338 {
339     gtk_container_foreach (GTK_CONTAINER(widget), balsa_mime_widget_image_resize_all, NULL);
340 }
341 
342 /*
343  * Callbacks and helpers for the find bar.
344  */
345 
346 typedef enum {
347     BM_FIND_STATUS_INIT,
348     BM_FIND_STATUS_FOUND,
349     BM_FIND_STATUS_WRAPPED,
350     BM_FIND_STATUS_NOT_FOUND
351 } BalsaMessageFindStatus;
352 
353 static void
bm_find_set_status(BalsaMessage * bm,BalsaMessageFindStatus status)354 bm_find_set_status(BalsaMessage * bm, BalsaMessageFindStatus status)
355 {
356     const gchar *text = "";
357     gboolean sensitive = FALSE;
358 
359     switch (status) {
360         default:
361         case BM_FIND_STATUS_INIT:
362             break;
363         case BM_FIND_STATUS_FOUND:
364             /* The widget returned "found"; if it really found a string,
365              * we sensitize the "next" and "previous" buttons, but if
366              * the find-entry was empty, we desensitize them. */
367             if (gtk_entry_get_text(GTK_ENTRY(bm->find_entry))[0])
368                 sensitive = TRUE;
369             break;
370         case BM_FIND_STATUS_WRAPPED:
371             text = _("Wrapped");
372             sensitive = TRUE;
373             break;
374         case BM_FIND_STATUS_NOT_FOUND:
375             text = _("Not found");
376             break;
377     }
378 
379     gtk_label_set_text(GTK_LABEL(bm->find_label), text);
380     gtk_separator_tool_item_set_draw(GTK_SEPARATOR_TOOL_ITEM
381                                      (bm->find_sep), text[0] != '\0');
382     gtk_widget_set_sensitive(bm->find_prev, sensitive);
383     gtk_widget_set_sensitive(bm->find_next, sensitive);
384 }
385 
386 static void
bm_find_scroll_to_rectangle(BalsaMessage * bm,GtkWidget * widget,GdkRectangle * rectangle)387 bm_find_scroll_to_rectangle(BalsaMessage * bm,
388                             GtkWidget    * widget,
389                             GdkRectangle * rectangle)
390 {
391     gint x, y;
392     GtkAdjustment *hadj, *vadj;
393 
394     gtk_widget_translate_coordinates(widget, bm->bm_widget->widget,
395                                      rectangle->x, rectangle->y,
396                                      &x, &y);
397 
398     g_object_get(G_OBJECT(bm->scroll), "hadjustment", &hadj,
399                                        "vadjustment", &vadj, NULL);
400     gtk_adjustment_clamp_page(hadj, x, x + rectangle->width);
401     gtk_adjustment_clamp_page(vadj, y, y + rectangle->height);
402 }
403 
404 static void
bm_find_scroll_to_selection(BalsaMessage * bm,GtkTextView * text_view,GtkTextIter * begin_iter,GtkTextIter * end_iter)405 bm_find_scroll_to_selection(BalsaMessage * bm,
406                             GtkTextView  * text_view,
407                             GtkTextIter  * begin_iter,
408                             GtkTextIter  * end_iter)
409 {
410     GdkRectangle begin_location, end_location;
411 
412     gtk_text_view_get_iter_location(text_view, begin_iter,
413                                     &begin_location);
414     gtk_text_view_get_iter_location(text_view, end_iter,
415                                     &end_location);
416     end_location.width = 0;
417     gdk_rectangle_union(&begin_location, &end_location, &begin_location);
418     gtk_text_view_buffer_to_window_coords(text_view,
419                                           GTK_TEXT_WINDOW_WIDGET,
420                                           begin_location.x,
421                                           begin_location.y,
422                                           &begin_location.x,
423                                           &begin_location.y);
424 
425     bm_find_scroll_to_rectangle(bm, GTK_WIDGET(text_view), &begin_location);
426 }
427 
428 #ifdef HAVE_HTML_WIDGET
429 typedef struct {
430     BalsaMessage *bm;
431     GtkWidget    *widget;
432     gboolean      continuing;
433     gboolean      wrapping;
434 } BalsaMessageFindInfo;
435 #define BALSA_MESSAGE_FIND_INFO "BalsaMessageFindInfo"
436 
437 static void
bm_find_cb(const gchar * text,gboolean found,gpointer data)438 bm_find_cb(const gchar * text, gboolean found, gpointer data)
439 {
440     BalsaMessageFindInfo *info = data;
441 
442     if (!found && info->continuing) {
443         info->wrapping = TRUE;
444         libbalsa_html_search(info->widget, text, info->bm->find_forward,
445                              TRUE, bm_find_cb, info);
446         return;
447     }
448 
449     if (found && *text) {
450         GdkRectangle selection_bounds;
451         if (libbalsa_html_get_selection_bounds(info->widget,
452                                                &selection_bounds))
453             bm_find_scroll_to_rectangle(info->bm, info->widget,
454                                         &selection_bounds);
455     }
456 
457     if (info->wrapping) {
458         info->wrapping = FALSE;
459         bm_find_set_status(info->bm, BM_FIND_STATUS_WRAPPED);
460     } else
461         bm_find_set_status(info->bm, found ? BM_FIND_STATUS_FOUND :
462                                              BM_FIND_STATUS_NOT_FOUND);
463 }
464 #endif                          /* HAVE_HTML_WIDGET */
465 
466 static void
bm_find_entry_changed_cb(GtkEditable * editable,gpointer data)467 bm_find_entry_changed_cb(GtkEditable * editable, gpointer data)
468 {
469     const gchar *text = gtk_entry_get_text(GTK_ENTRY(editable));
470     BalsaMessage *bm = data;
471     GtkWidget *widget = bm->current_part->mime_widget->widget;
472     gboolean found = FALSE;
473 
474     if (GTK_IS_TEXT_VIEW(widget)) {
475         GtkTextView *text_view = GTK_TEXT_VIEW(widget);
476         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text_view);
477         GtkTextIter match_begin, match_end;
478 
479         if (bm->find_forward) {
480             found = FORWARD_SEARCH(&bm->find_iter, text,
481                                    &match_begin, &match_end);
482             if (!found) {
483                 /* Silently wrap to the top. */
484                 gtk_text_buffer_get_start_iter(buffer, &bm->find_iter);
485                 found = FORWARD_SEARCH(&bm->find_iter, text,
486                                        &match_begin, &match_end);
487             }
488         } else {
489             found = BACKWARD_SEARCH(&bm->find_iter, text,
490                                     &match_begin, &match_end);
491             if (!found) {
492                 /* Silently wrap to the bottom. */
493                 gtk_text_buffer_get_end_iter(buffer, &bm->find_iter);
494                 found = BACKWARD_SEARCH(&bm->find_iter, text,
495                                         &match_begin, &match_end);
496             }
497         }
498 
499         if (found) {
500             gtk_text_buffer_select_range(buffer, &match_begin, &match_end);
501             bm_find_scroll_to_selection(bm, text_view,
502                                         &match_begin, &match_end);
503             bm->find_iter = match_begin;
504         }
505 
506         bm_find_set_status(bm, found ? BM_FIND_STATUS_FOUND :
507                                        BM_FIND_STATUS_NOT_FOUND);
508 #ifdef HAVE_HTML_WIDGET
509     } else if (libbalsa_html_can_search(widget)) {
510         BalsaMessageFindInfo *info;
511 
512         if (!(info = bm->html_find_info)) {
513             bm->html_find_info = info = g_new(BalsaMessageFindInfo, 1);
514             info->bm = bm;
515         }
516         info->widget = widget;
517         info->continuing = FALSE;
518         info->wrapping = FALSE;
519 
520         libbalsa_html_search(widget, text, bm->find_forward, TRUE,
521                              bm_find_cb, info);
522 #endif                          /* HAVE_HTML_WIDGET */
523     } else
524         g_assert_not_reached();
525 }
526 
527 static void
bm_find_again(BalsaMessage * bm,gboolean find_forward)528 bm_find_again(BalsaMessage * bm, gboolean find_forward)
529 {
530     const gchar *text = gtk_entry_get_text(GTK_ENTRY(bm->find_entry));
531     GtkWidget *widget = bm->current_part->mime_widget->widget;
532     gboolean found;
533 
534     bm->find_forward = find_forward;
535 
536     if (GTK_IS_TEXT_VIEW(widget)) {
537         GtkTextView *text_view = GTK_TEXT_VIEW(widget);
538         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text_view);
539         GtkTextIter match_begin, match_end;
540 
541         if (find_forward) {
542             gtk_text_iter_forward_char(&bm->find_iter);
543             found = FORWARD_SEARCH(&bm->find_iter, text,
544                                    &match_begin, &match_end);
545             if (!found) {
546                 gtk_text_buffer_get_start_iter(buffer, &bm->find_iter);
547                 FORWARD_SEARCH(&bm->find_iter, text, &match_begin,
548                                &match_end);
549             }
550         } else {
551             gtk_text_iter_backward_char(&bm->find_iter);
552             found = BACKWARD_SEARCH(&bm->find_iter, text,
553                                     &match_begin, &match_end);
554             if (!found) {
555                 gtk_text_buffer_get_end_iter(buffer, &bm->find_iter);
556                 BACKWARD_SEARCH(&bm->find_iter, text, &match_begin,
557                                 &match_end);
558             }
559         }
560 
561         gtk_text_buffer_select_range(buffer, &match_begin, &match_end);
562         bm_find_scroll_to_selection(bm, text_view,
563                                     &match_begin, &match_end);
564         bm->find_iter = match_begin;
565 
566         bm_find_set_status(bm, found ?
567                            BM_FIND_STATUS_FOUND : BM_FIND_STATUS_WRAPPED);
568 #ifdef HAVE_HTML_WIDGET
569     } else if (libbalsa_html_can_search(widget)) {
570         BalsaMessageFindInfo *info = bm->html_find_info;
571 
572         info->continuing = TRUE;
573         libbalsa_html_search(widget, text, find_forward, FALSE,
574                              bm_find_cb, info);
575 #endif                          /* HAVE_HTML_WIDGET */
576     } else
577         g_assert_not_reached();
578 }
579 
580 static void
bm_find_prev_cb(GtkToolButton * prev_button,gpointer data)581 bm_find_prev_cb(GtkToolButton * prev_button, gpointer data)
582 {
583     bm_find_again((BalsaMessage *) data, FALSE);
584 }
585 
586 static void
bm_find_next_cb(GtkToolButton * prev_button,gpointer data)587 bm_find_next_cb(GtkToolButton * prev_button, gpointer data)
588 {
589     bm_find_again((BalsaMessage *) data, TRUE);
590 }
591 
592 static GtkWidget *
bm_find_bar_new(BalsaMessage * bm)593 bm_find_bar_new(BalsaMessage * bm)
594 {
595     GtkWidget *toolbar;
596     GtkWidget *hbox;
597     GtkToolItem *tool_item;
598 
599     toolbar = gtk_toolbar_new();
600     gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH_HORIZ);
601 
602     hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
603     gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_("Find:")),
604                        FALSE, FALSE, 0);
605     bm->find_entry = gtk_entry_new();
606     g_signal_connect(bm->find_entry, "changed",
607                      G_CALLBACK(bm_find_entry_changed_cb), bm);
608     gtk_box_pack_start(GTK_BOX(hbox), bm->find_entry, FALSE, FALSE, 0);
609 
610     tool_item = gtk_tool_item_new();
611     gtk_container_add(GTK_CONTAINER(tool_item), hbox);
612     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), tool_item, -1);
613 
614     tool_item =
615         gtk_tool_button_new(gtk_arrow_new(GTK_ARROW_LEFT, GTK_SHADOW_NONE),
616                             _("Previous"));
617     bm->find_prev = GTK_WIDGET(tool_item);
618     gtk_tool_item_set_is_important(tool_item, TRUE);
619     g_signal_connect(tool_item, "clicked", G_CALLBACK(bm_find_prev_cb), bm);
620     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), tool_item, -1);
621 
622     tool_item =
623         gtk_tool_button_new(gtk_arrow_new(GTK_ARROW_RIGHT, GTK_SHADOW_NONE),
624                             _("Next"));
625     bm->find_next = GTK_WIDGET(tool_item);
626     gtk_tool_item_set_is_important(tool_item, TRUE);
627     g_signal_connect(tool_item, "clicked", G_CALLBACK(bm_find_next_cb), bm);
628     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), tool_item, -1);
629 
630     bm->find_sep = GTK_WIDGET(gtk_separator_tool_item_new());
631     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), GTK_TOOL_ITEM(bm->find_sep), -1);
632 
633     bm->find_label = gtk_label_new("");
634     tool_item = gtk_tool_item_new();
635     gtk_container_add(GTK_CONTAINER(tool_item), bm->find_label);
636     gtk_toolbar_insert(GTK_TOOLBAR(toolbar), tool_item, -1);
637 
638     return toolbar;
639 }
640 
641 static void bm_disable_find_entry(BalsaMessage * bm);
642 
643 static gboolean
bm_find_pass_to_entry(BalsaMessage * bm,GdkEventKey * event)644 bm_find_pass_to_entry(BalsaMessage * bm, GdkEventKey * event)
645 {
646     gboolean res = TRUE;
647 
648     switch (event->keyval) {
649     case GDK_KEY_Escape:
650     case GDK_KEY_Return:
651     case GDK_KEY_KP_Enter:
652         bm_disable_find_entry(bm);
653         return res;
654     case GDK_KEY_g:
655         if ((event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) ==
656             GDK_CONTROL_MASK && gtk_widget_get_sensitive(bm->find_next)) {
657             bm_find_again(bm, bm->find_forward);
658             return res;
659         }
660     default:
661         break;
662     }
663 
664     res = FALSE;
665     if (gtk_widget_has_focus(bm->find_entry))
666         g_signal_emit_by_name(bm->find_entry, "key-press-event", event,
667                               &res, NULL);
668 
669     return res;
670 }
671 
672 static void
bm_disable_find_entry(BalsaMessage * bm)673 bm_disable_find_entry(BalsaMessage * bm)
674 {
675     g_signal_handlers_disconnect_by_func
676         (gtk_widget_get_toplevel(GTK_WIDGET(bm)),
677          G_CALLBACK(bm_find_pass_to_entry), bm);
678     gtk_widget_hide(bm->find_bar);
679 }
680 
681 /*
682  * End of callbacks and helpers for the find bar.
683  */
684 
685 static void
balsa_message_init(BalsaMessage * bm)686 balsa_message_init(BalsaMessage * bm)
687 {
688     GtkWidget *vbox;
689     GtkWidget *scroll;
690     GtkWidget *label;
691     GtkWidget **buttons;
692     GtkTreeStore *model;
693     GtkCellRenderer *renderer;
694     GtkTreeSelection *selection;
695 
696     gtk_notebook_set_show_border(GTK_NOTEBOOK(bm), FALSE);
697 
698     /* Box to hold the scrolled window and the find bar */
699     vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
700     label = gtk_label_new(_("Content"));
701     gtk_notebook_append_page(GTK_NOTEBOOK(bm), vbox, label);
702 
703     /* scrolled window for the contents */
704     bm->scroll = scroll = gtk_scrolled_window_new(NULL, NULL);
705     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
706                                    GTK_POLICY_AUTOMATIC,
707                                    GTK_POLICY_AUTOMATIC);
708     g_signal_connect(scroll, "key_press_event",
709 		     G_CALLBACK(balsa_mime_widget_key_press_event), bm);
710     gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
711     g_signal_connect_after(bm, "style-updated",
712 			   G_CALLBACK(bm_on_set_style), bm);
713     g_signal_connect(bm->scroll, "size-allocate",
714 		     G_CALLBACK(on_content_size_alloc), NULL);
715 
716     /* Widget to hold headers */
717     buttons = bm_header_tl_buttons(bm);
718     bm->bm_widget = balsa_mime_widget_new_message_tl(bm, buttons);
719     g_free(buttons);
720 
721     /* Widget to hold message */
722     g_signal_connect(G_OBJECT(bm->bm_widget->widget), "focus_in_event",
723                      G_CALLBACK(balsa_mime_widget_limit_focus),
724                      (gpointer) bm);
725     g_signal_connect(G_OBJECT(bm->bm_widget->widget), "focus_out_event",
726                      G_CALLBACK(balsa_mime_widget_unlimit_focus),
727 		     (gpointer) bm);
728 #if GTK_CHECK_VERSION(3, 8, 0)
729     gtk_container_add(GTK_CONTAINER(bm->scroll), bm->bm_widget->widget);
730 #else                           /* GTK_CHECK_VERSION(3, 8, 0 */
731     gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(bm->scroll),
732                                           bm->bm_widget->widget);
733 #endif                          /* GTK_CHECK_VERSION(3, 8, 0 */
734 
735     /* structure view */
736     model = gtk_tree_store_new (NUM_COLUMNS,
737                                 TYPE_BALSA_PART_INFO,
738 				G_TYPE_STRING,
739                                 GDK_TYPE_PIXBUF,
740                                 G_TYPE_STRING);
741     bm->treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL(model));
742     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW (bm->treeview));
743     g_signal_connect(bm->treeview, "row-activated",
744                      G_CALLBACK(tree_activate_row_cb), bm);
745     g_signal_connect(bm->treeview, "button_press_event",
746                      G_CALLBACK(tree_button_press_cb), bm);
747     g_signal_connect(bm->treeview, "popup-menu",
748                      G_CALLBACK(tree_menu_popup_key_cb), bm);
749     g_object_unref (G_OBJECT (model));
750     gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (bm->treeview), TRUE);
751     gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
752     gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (bm->treeview), FALSE);
753 
754     /* column for the part number */
755     renderer = gtk_cell_renderer_text_new ();
756     g_object_set (G_OBJECT (renderer), "xalign", 0.0, NULL);
757     gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (bm->treeview),
758                                                  -1, NULL,
759                                                  renderer, "text",
760                                                  PART_NUM_COLUMN,
761                                                  NULL);
762 
763     /* column for type icon */
764     renderer = gtk_cell_renderer_pixbuf_new ();
765     g_object_set (G_OBJECT (renderer), "xalign", 0.0, NULL);
766     gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (bm->treeview),
767                                                  -1, NULL,
768                                                  renderer, "pixbuf",
769                                                  MIME_ICON_COLUMN,
770                                                  NULL);
771 
772     /* column for mime type */
773     renderer = gtk_cell_renderer_text_new ();
774     g_object_set (G_OBJECT (renderer), "xalign", 0.0, NULL);
775     gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (bm->treeview),
776                                                  -1, NULL,
777                                                  renderer, "text",
778                                                  MIME_TYPE_COLUMN,
779                                                  NULL);
780 
781     /* scrolled window for the tree view */
782     scroll = gtk_scrolled_window_new(NULL, NULL);
783     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
784                                    GTK_POLICY_AUTOMATIC,
785                                    GTK_POLICY_AUTOMATIC);
786 
787     gtk_tree_view_set_expander_column
788 	(GTK_TREE_VIEW (bm->treeview), gtk_tree_view_get_column
789 	 (GTK_TREE_VIEW (bm->treeview), MIME_ICON_COLUMN - 1));
790 
791     label = gtk_label_new(_("Message parts"));
792     gtk_notebook_append_page(GTK_NOTEBOOK(bm), scroll, label);
793     gtk_container_add(GTK_CONTAINER(scroll), bm->treeview);
794     gtk_notebook_set_show_tabs(GTK_NOTEBOOK(bm), FALSE);
795 
796     bm->current_part = NULL;
797     bm->message = NULL;
798     bm->info_count = 0;
799     bm->save_all_list = NULL;
800     bm->save_all_popup = NULL;
801 
802     bm->wrap_text = balsa_app.browse_wrap;
803     bm->shown_headers = balsa_app.shown_headers;
804     bm->close_with_msg = FALSE;
805 
806     gtk_widget_show_all(GTK_WIDGET(bm));
807 
808     /* Find-in-message toolbar that is hidden by default. */
809     bm->find_bar = bm_find_bar_new(bm);
810     gtk_box_pack_start(GTK_BOX(vbox), bm->find_bar, FALSE, FALSE, 0);
811 }
812 
813 static void
balsa_message_destroy(GObject * object)814 balsa_message_destroy(GObject * object)
815 {
816     BalsaMessage* bm = BALSA_MESSAGE(object);
817 
818     if (bm->treeview) {
819         balsa_message_set(bm, NULL, 0);
820         gtk_widget_destroy(bm->treeview);
821         bm->treeview = NULL;
822     }
823 
824     g_list_free(bm->save_all_list);
825     bm->save_all_list = NULL;
826 
827     if (bm->save_all_popup) {
828         g_object_unref(bm->save_all_popup);
829 	bm->save_all_popup = NULL;
830     }
831 
832     if (bm->parts_popup) {
833 	g_object_unref(bm->parts_popup);
834 	bm->parts_popup = NULL;
835     }
836 
837 #ifdef HAVE_HTML_WIDGET
838     if (bm->html_find_info) {
839         g_free(bm->html_find_info);
840         bm->html_find_info = NULL;
841     }
842 #endif                          /* HAVE_HTML_WIDGET */
843 
844     if (G_OBJECT_CLASS(parent_class)->dispose)
845         (*G_OBJECT_CLASS(parent_class)->dispose) (object);
846 }
847 
848 GtkWidget *
balsa_message_new(void)849 balsa_message_new(void)
850 {
851     BalsaMessage *bm;
852 
853     bm = g_object_new(BALSA_TYPE_MESSAGE, NULL);
854 
855     return GTK_WIDGET(bm);
856 }
857 
858 void
balsa_message_set_close(BalsaMessage * bm,gboolean close_with_msg)859 balsa_message_set_close(BalsaMessage * bm, gboolean close_with_msg)
860 {
861     bm->close_with_msg = close_with_msg;
862 }
863 
864 /* Returns a BalsaPartInfo with a reference (g_object_unref when done). */
865 static BalsaPartInfo *
tree_next_valid_part_info(GtkTreeModel * model,GtkTreeIter * iter)866 tree_next_valid_part_info(GtkTreeModel * model, GtkTreeIter * iter)
867 {
868     BalsaPartInfo *info = NULL;
869 
870     do {
871         GtkTreeIter child;
872 
873         /* check if there is a valid info */
874         gtk_tree_model_get(model, iter, PART_INFO_COLUMN, &info, -1);
875         if (info)
876             return info;
877 
878         /* if there are children, check the childs */
879         if (gtk_tree_model_iter_children (model, &child, iter))
880             if ((info = tree_next_valid_part_info(model, &child)))
881                 return info;
882 
883         /* switch to the next iter on the same level */
884         if (!gtk_tree_model_iter_next(model, iter))
885             return NULL;
886     } while (1);
887     /* never reached */
888     return NULL;
889 }
890 
891 static void
tree_activate_row_cb(GtkTreeView * treeview,GtkTreePath * arg1,GtkTreeViewColumn * arg2,gpointer user_data)892 tree_activate_row_cb(GtkTreeView *treeview, GtkTreePath *arg1,
893                      GtkTreeViewColumn *arg2, gpointer user_data)
894 {
895     BalsaMessage * bm = (BalsaMessage *)user_data;
896     GtkTreeModel * model = gtk_tree_view_get_model(treeview);
897     GtkTreeIter sel_iter;
898     BalsaPartInfo *info = NULL;
899 
900     g_return_if_fail(bm);
901 
902     /* get the info of the activated part */
903     if (!gtk_tree_model_get_iter(model, &sel_iter, arg1))
904         return;
905     gtk_tree_model_get(model, &sel_iter, PART_INFO_COLUMN, &info, -1);
906 
907     /* if it's not displayable (== no info), get the next one... */
908     if (!info) {
909         info = tree_next_valid_part_info(model, &sel_iter);
910 
911         if (!info) {
912             gtk_tree_model_get_iter_first(model, &sel_iter);
913             gtk_tree_model_get(model, &sel_iter, PART_INFO_COLUMN, &info, -1);
914             if (!info)
915                 info = tree_next_valid_part_info(model, &sel_iter);
916         }
917     }
918 
919     gtk_notebook_set_current_page(GTK_NOTEBOOK(bm), 0);
920     select_part(bm, info);
921     if (info)
922         g_object_unref(info);
923 }
924 
925 static void
collect_selected_info(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)926 collect_selected_info(GtkTreeModel * model, GtkTreePath * path,
927                       GtkTreeIter * iter, gpointer data)
928 {
929     GList **info_list = (GList **)data;
930     BalsaPartInfo *info;
931 
932     gtk_tree_model_get(model, iter, PART_INFO_COLUMN, &info, -1);
933     if (info) {
934         g_object_unref(info);
935         *info_list = g_list_append(*info_list, info);
936     }
937 }
938 
939 static void
tree_mult_selection_popup(BalsaMessage * bm,GdkEventButton * event,GtkTreeSelection * selection)940 tree_mult_selection_popup(BalsaMessage * bm, GdkEventButton * event,
941                           GtkTreeSelection * selection)
942 {
943     gint selected;
944 
945     /* destroy left-over select list and popup... */
946     g_list_free(bm->save_all_list);
947     bm->save_all_list = NULL;
948     if (bm->save_all_popup) {
949         g_object_unref(bm->save_all_popup);
950         bm->save_all_popup = NULL;
951     }
952 
953     /* collect all selected info blocks */
954     gtk_tree_selection_selected_foreach(selection,
955                                         collect_selected_info,
956                                         &bm->save_all_list);
957 
958     /* For a single part, display it's popup, for multiple parts a "save all"
959      * popup. If nothing with an info block is selected, do nothing */
960     selected = g_list_length(bm->save_all_list);
961     if (selected == 1) {
962         BalsaPartInfo *info = BALSA_PART_INFO(bm->save_all_list->data);
963         if (info->popup_menu) {
964             if (event)
965                 gtk_menu_popup(GTK_MENU(info->popup_menu), NULL, NULL, NULL,
966                                NULL, event->button, event->time);
967             else
968                 gtk_menu_popup(GTK_MENU(info->popup_menu), NULL, NULL, NULL,
969                                NULL, 0, gtk_get_current_event_time());
970         }
971         g_list_free(bm->save_all_list);
972         bm->save_all_list = NULL;
973     } else if (selected > 1) {
974         GtkWidget *menu_item;
975 
976         bm->save_all_popup = gtk_menu_new ();
977         g_object_ref_sink(bm->save_all_popup);
978         menu_item =
979             gtk_menu_item_new_with_label (_("Save selected as..."));
980         gtk_widget_show(menu_item);
981         g_signal_connect (G_OBJECT (menu_item), "activate",
982                           G_CALLBACK (part_context_save_all_cb),
983                           (gpointer) bm->save_all_list);
984         gtk_menu_shell_append (GTK_MENU_SHELL (bm->save_all_popup), menu_item);
985         menu_item =
986             gtk_menu_item_new_with_label (_("Save selected to folder..."));
987         gtk_widget_show(menu_item);
988         g_signal_connect (G_OBJECT (menu_item), "activate",
989                           G_CALLBACK (part_context_dump_all_cb),
990                           (gpointer) bm->save_all_list);
991         gtk_menu_shell_append (GTK_MENU_SHELL (bm->save_all_popup), menu_item);
992         if (event)
993             gtk_menu_popup(GTK_MENU(bm->save_all_popup), NULL, NULL, NULL,
994                            NULL, event->button, event->time);
995         else
996             gtk_menu_popup(GTK_MENU(bm->save_all_popup), NULL, NULL, NULL,
997                            NULL, 0, gtk_get_current_event_time());
998     }
999 }
1000 
1001 static gboolean
tree_menu_popup_key_cb(GtkWidget * widget,gpointer user_data)1002 tree_menu_popup_key_cb(GtkWidget *widget, gpointer user_data)
1003 {
1004     BalsaMessage * bm = (BalsaMessage *)user_data;
1005 
1006     g_return_val_if_fail(bm, FALSE);
1007     tree_mult_selection_popup(bm, NULL,
1008                               gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)));
1009     return TRUE;
1010 }
1011 
1012 static gboolean
tree_button_press_cb(GtkWidget * widget,GdkEventButton * event,gpointer data)1013 tree_button_press_cb(GtkWidget * widget, GdkEventButton * event,
1014                      gpointer data)
1015 {
1016     BalsaMessage * bm = (BalsaMessage *)data;
1017     GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
1018     GtkTreePath *path;
1019 
1020     g_return_val_if_fail(bm, FALSE);
1021     g_return_val_if_fail(event, FALSE);
1022     if (event->type != GDK_BUTTON_PRESS || event->button != 3
1023         || event->window != gtk_tree_view_get_bin_window(tree_view))
1024         return FALSE;
1025 
1026     /* If the part which received the click is already selected, don't change
1027      * the selection and check if more than on part is selected. Pop up the
1028      * "save all" menu in this case and the "normal" popup otherwise.
1029      * If the receiving part is not selected, select (only) this part and pop
1030      * up its menu.
1031      */
1032     if (gtk_tree_view_get_path_at_pos(tree_view, event->x, event->y,
1033                                       &path, NULL, NULL, NULL)) {
1034         GtkTreeIter iter;
1035         GtkTreeSelection * selection =
1036             gtk_tree_view_get_selection(tree_view);
1037         GtkTreeModel * model = gtk_tree_view_get_model(tree_view);
1038 
1039         if (!gtk_tree_selection_path_is_selected(selection, path)) {
1040             BalsaPartInfo *info = NULL;
1041 
1042             gtk_tree_selection_unselect_all(selection);
1043             gtk_tree_selection_select_path(selection, path);
1044             gtk_tree_view_set_cursor(GTK_TREE_VIEW(tree_view), path, NULL,
1045                                      FALSE);
1046             if (gtk_tree_model_get_iter (model, &iter, path)) {
1047                 gtk_tree_model_get(model, &iter, PART_INFO_COLUMN, &info, -1);
1048                 if (info) {
1049                     if (info->popup_menu)
1050                         gtk_menu_popup(GTK_MENU(info->popup_menu), NULL, NULL,
1051                                        NULL, NULL, event->button, event->time);
1052                     g_object_unref(info);
1053                 }
1054             }
1055         } else
1056             tree_mult_selection_popup(bm, event, selection);
1057         gtk_tree_path_free(path);
1058     }
1059 
1060     return TRUE;
1061 }
1062 
1063 /* balsa_message_set:
1064    returns TRUE on success, FALSE on failure (message content could not be
1065    accessed).
1066 
1067    if msgno == 0, clears the display and returns TRUE
1068 */
1069 
1070 /* Helpers:
1071  */
1072 
1073 gchar *
balsa_message_sender_to_gchar(InternetAddressList * list,gint which)1074 balsa_message_sender_to_gchar(InternetAddressList * list, gint which)
1075 {
1076     InternetAddress *ia;
1077 
1078     if (!list)
1079 	return g_strdup(_("(No sender)"));
1080     if (which < 0)
1081 	return internet_address_list_to_string(list, FALSE);
1082     ia = internet_address_list_get_address (list, which);
1083     return internet_address_to_string(ia, FALSE);
1084 }
1085 
1086 static void
balsa_message_clear_tree(BalsaMessage * bm)1087 balsa_message_clear_tree(BalsaMessage * bm)
1088 {
1089     GtkTreeModel *model;
1090 
1091     g_return_if_fail(bm != NULL);
1092 
1093     model = gtk_tree_view_get_model(GTK_TREE_VIEW(bm->treeview));
1094     gtk_tree_store_clear(GTK_TREE_STORE(model));
1095     bm->info_count = 0;
1096 }
1097 
1098 gboolean
balsa_message_set(BalsaMessage * bm,LibBalsaMailbox * mailbox,guint msgno)1099 balsa_message_set(BalsaMessage * bm, LibBalsaMailbox * mailbox, guint msgno)
1100 {
1101     gboolean is_new;
1102     GtkTreeIter iter;
1103     BalsaPartInfo *info;
1104     gboolean has_focus = bm->focus_state != BALSA_MESSAGE_FOCUS_STATE_NO;
1105     LibBalsaMessage *message;
1106 
1107     g_return_val_if_fail(bm != NULL, FALSE);
1108 
1109     bm_disable_find_entry(bm);
1110     balsa_message_clear_tree(bm);
1111     select_part(bm, NULL);
1112     if (bm->message != NULL) {
1113         libbalsa_message_body_unref(bm->message);
1114         g_object_unref(bm->message);
1115         bm->message = NULL;
1116     }
1117 
1118     if (mailbox == NULL || msgno == 0) {
1119         gtk_notebook_set_show_tabs(GTK_NOTEBOOK(bm), FALSE);
1120         gtk_notebook_set_current_page(GTK_NOTEBOOK(bm), 0);
1121         return TRUE;
1122     }
1123 
1124     bm->message = message = libbalsa_mailbox_get_message(mailbox, msgno);
1125     /* We must not use msgno from now on: an asynchronous expunge may
1126        arrive (in particular between the body_ref() and set_flags()
1127        actions) and change the message numbering. Asynchronous
1128        expunges will update the LibBalsaMailbox::message data but no
1129        message numbers stored in random integer variables. */
1130     if (!message) {
1131 	balsa_information(LIBBALSA_INFORMATION_WARNING,
1132                           _("Could not access message %u "
1133                             "in mailbox \"%s\"."),
1134 			  msgno, mailbox->name);
1135         return FALSE;
1136     }
1137 
1138     is_new = LIBBALSA_MESSAGE_IS_UNREAD(message);
1139     if(!libbalsa_message_body_ref(message, TRUE, TRUE)) {
1140 	libbalsa_mailbox_check(mailbox);
1141         g_object_unref(bm->message);
1142         bm->message = NULL;
1143 	balsa_information(LIBBALSA_INFORMATION_WARNING,
1144                           _("Could not access message %u "
1145                             "in mailbox \"%s\"."),
1146 			  (unsigned int) message->msgno, mailbox->name);
1147         return FALSE;
1148     }
1149 
1150 #ifdef HAVE_GPGME
1151     balsa_message_perform_crypto(message,
1152 				 libbalsa_mailbox_get_crypto_mode(mailbox),
1153 				 FALSE, 1);
1154     /* calculate the signature summary state if not set earlier */
1155     if(message->prot_state == LIBBALSA_MSG_PROTECT_NONE) {
1156         LibBalsaMsgProtectState prot_state =
1157             balsa_message_scan_signatures(message->body_list, message);
1158         /* update the icon if necessary */
1159         if (message->prot_state != prot_state)
1160             message->prot_state = prot_state;
1161     }
1162 #endif
1163 
1164     /* may update the icon */
1165     libbalsa_mailbox_msgno_update_attach(mailbox, message->msgno, message);
1166 
1167     display_headers(bm);
1168     display_content(bm);
1169     gtk_widget_show(GTK_WIDGET(bm));
1170 
1171 #if defined(ENABLE_TOUCH_UI)
1172     /* hide tabs so that they do not confuse keyboard navigation.
1173      * This could probably be a configuration option. */
1174     gtk_notebook_set_show_tabs(GTK_NOTEBOOK(bm), FALSE);
1175 #else
1176     gtk_notebook_set_show_tabs(GTK_NOTEBOOK(bm), bm->info_count > 1);
1177 #endif /* ENABLE_TOUCH_UI */
1178     gtk_notebook_set_current_page(GTK_NOTEBOOK(bm), 0);
1179 
1180     /*
1181      * At this point we check if (a) a message was new (its not new
1182      * any longer) and (b) a Disposition-Notification-To header line is
1183      * present.
1184      *
1185      */
1186     if (is_new && message->headers->dispnotify_to)
1187         handle_mdn_request (balsa_get_parent_window(GTK_WIDGET(bm)), message);
1188 
1189     if (!gtk_tree_model_get_iter_first (gtk_tree_view_get_model(GTK_TREE_VIEW(bm->treeview)),
1190                                         &iter))
1191         /* Not possible? */
1192         return TRUE;
1193 
1194     info =
1195         tree_next_valid_part_info(gtk_tree_view_get_model(GTK_TREE_VIEW(bm->treeview)),
1196                                   &iter);
1197     select_part(bm, info);
1198     if (info)
1199         g_object_unref(info);
1200     /*
1201      * emit read message
1202      */
1203     if (is_new && !mailbox->readonly)
1204         libbalsa_mailbox_msgno_change_flags(mailbox, message->msgno, 0,
1205                                             LIBBALSA_MESSAGE_FLAG_NEW);
1206 
1207     /* restore keyboard focus to the content, if it was there before */
1208     if (has_focus)
1209         balsa_message_grab_focus(bm);
1210 
1211     return TRUE;
1212 }
1213 
1214 void
balsa_message_save_current_part(BalsaMessage * bm)1215 balsa_message_save_current_part(BalsaMessage * bm)
1216 {
1217     g_return_if_fail(bm != NULL);
1218 
1219     if (bm->current_part)
1220 	balsa_mime_widget_ctx_menu_save(GTK_WIDGET(bm), bm->current_part->body);
1221 }
1222 
1223 static gboolean
balsa_message_set_embedded_hdr(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)1224 balsa_message_set_embedded_hdr(GtkTreeModel * model, GtkTreePath * path,
1225                                GtkTreeIter *iter, gpointer data)
1226 {
1227     BalsaPartInfo *info = NULL;
1228     BalsaMessage * bm = BALSA_MESSAGE(data);
1229 
1230     gtk_tree_model_get(model, iter, PART_INFO_COLUMN, &info, -1);
1231     if (info) {
1232  	if (info->body && info->body->embhdrs && info->mime_widget)
1233  	    balsa_mime_widget_message_set_headers_d(bm, info->mime_widget,
1234                                                     info->body->embhdrs,
1235                                                     info->body->parts,
1236                                                     info->body->embhdrs->subject);
1237 	g_object_unref(G_OBJECT(info));
1238     }
1239 
1240     return FALSE;
1241 }
1242 
1243 void
balsa_message_set_displayed_headers(BalsaMessage * bmessage,ShownHeaders sh)1244 balsa_message_set_displayed_headers(BalsaMessage * bmessage,
1245                                     ShownHeaders sh)
1246 {
1247     g_return_if_fail(bmessage != NULL);
1248     g_return_if_fail(sh >= HEADERS_NONE && sh <= HEADERS_ALL);
1249 
1250     if (bmessage->shown_headers == sh)
1251         return;
1252 
1253     bmessage->shown_headers = sh;
1254 
1255     if (bmessage->message) {
1256         if(sh == HEADERS_ALL)
1257             libbalsa_mailbox_set_msg_headers(bmessage->message->mailbox,
1258                                              bmessage->message);
1259         display_headers(bmessage);
1260         gtk_tree_model_foreach
1261             (gtk_tree_view_get_model(GTK_TREE_VIEW(bmessage->treeview)),
1262              balsa_message_set_embedded_hdr, bmessage);
1263 	if (bm_header_widget_att_button(bmessage)) {
1264 	    if (bmessage->info_count > 1)
1265 		gtk_widget_show_all
1266 		    (GTK_WIDGET(bm_header_widget_att_button(bmessage)));
1267 	    else
1268 		gtk_widget_hide
1269 		    (GTK_WIDGET(bm_header_widget_att_button(bmessage)));
1270 	}
1271     }
1272 }
1273 
1274 void
balsa_message_set_wrap(BalsaMessage * bm,gboolean wrap)1275 balsa_message_set_wrap(BalsaMessage * bm, gboolean wrap)
1276 {
1277     g_return_if_fail(bm != NULL);
1278 
1279     bm->wrap_text = wrap;
1280 
1281     /* This is easier than reformating all the widgets... */
1282     if (bm->message) {
1283         LibBalsaMessage *msg = bm->message;
1284         balsa_message_set(bm, msg->mailbox, msg->msgno);
1285     }
1286 }
1287 
1288 
1289 static void
display_headers(BalsaMessage * bm)1290 display_headers(BalsaMessage * bm)
1291 {
1292     balsa_mime_widget_message_set_headers_d(bm, bm->bm_widget,
1293                                             bm->message->headers,
1294                                             bm->message->body_list,
1295                                             LIBBALSA_MESSAGE_GET_SUBJECT(bm->message));
1296 }
1297 
1298 
1299 static void
part_info_init(BalsaMessage * bm,BalsaPartInfo * info)1300 part_info_init(BalsaMessage * bm, BalsaPartInfo * info)
1301 {
1302     g_return_if_fail(bm != NULL);
1303     g_return_if_fail(info != NULL);
1304     g_return_if_fail(info->body != NULL);
1305 
1306     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(bm->scroll),
1307                                    GTK_POLICY_AUTOMATIC,
1308                                    GTK_POLICY_AUTOMATIC);
1309 
1310     info->mime_widget = balsa_mime_widget_new(bm, info->body, info->popup_menu);
1311 }
1312 
1313 
1314 static inline gchar *
mpart_content_name(const gchar * content_type)1315 mpart_content_name(const gchar *content_type)
1316 {
1317     if (g_ascii_strcasecmp(content_type, "multipart/mixed") == 0)
1318         return g_strdup(_("mixed parts"));
1319     else if (g_ascii_strcasecmp(content_type, "multipart/alternative") == 0)
1320         return g_strdup(_("alternative parts"));
1321     else if (g_ascii_strcasecmp(content_type, "multipart/signed") == 0)
1322         return g_strdup(_("signed parts"));
1323     else if (g_ascii_strcasecmp(content_type, "multipart/encrypted") == 0)
1324         return g_strdup(_("encrypted parts"));
1325     else if (g_ascii_strcasecmp(content_type, "message/rfc822") == 0)
1326         return g_strdup(_("rfc822 message"));
1327     else
1328         return g_strdup_printf(_("\"%s\" parts"),
1329                                strchr(content_type, '/') + 1);
1330 }
1331 
1332 static void
atattchments_menu_cb(GtkWidget * widget,BalsaPartInfo * info)1333 atattchments_menu_cb(GtkWidget * widget, BalsaPartInfo *info)
1334 {
1335     BalsaMessage * bm = g_object_get_data(G_OBJECT(widget), "balsa-message");
1336 
1337     g_return_if_fail(bm);
1338     g_return_if_fail(info);
1339 
1340     gtk_notebook_set_current_page(GTK_NOTEBOOK(bm), 0);
1341     select_part(bm, info);
1342 }
1343 
1344 static void
add_to_attachments_popup(GtkMenuShell * menu,const gchar * item,BalsaMessage * bm,BalsaPartInfo * info)1345 add_to_attachments_popup(GtkMenuShell * menu, const gchar * item,
1346 			 BalsaMessage * bm, BalsaPartInfo *info)
1347 {
1348     GtkWidget * menuitem = gtk_menu_item_new_with_label (item);
1349 
1350     g_object_set_data(G_OBJECT(menuitem), "balsa-message", bm);
1351     g_signal_connect(G_OBJECT (menuitem), "activate",
1352 		     G_CALLBACK (atattchments_menu_cb),
1353 		     (gpointer) info);
1354     gtk_menu_shell_append(menu, menuitem);
1355 }
1356 
1357 static void
toggle_all_inline_cb(GtkCheckMenuItem * item,BalsaPartInfo * info)1358 toggle_all_inline_cb(GtkCheckMenuItem * item, BalsaPartInfo *info)
1359 {
1360     BalsaMessage * bm = g_object_get_data(G_OBJECT(item), "balsa-message");
1361 
1362     g_return_if_fail(bm);
1363     g_return_if_fail(info);
1364 
1365     bm->force_inline = gtk_check_menu_item_get_active(item);
1366 
1367     gtk_notebook_set_current_page(GTK_NOTEBOOK(bm), 0);
1368     select_part(bm, info);
1369 }
1370 
1371 static void
add_toggle_inline_menu_item(GtkMenuShell * menu,BalsaMessage * bm,BalsaPartInfo * info)1372 add_toggle_inline_menu_item(GtkMenuShell * menu, BalsaMessage * bm,
1373 			    BalsaPartInfo *info)
1374 {
1375     GtkWidget * menuitem =
1376 	gtk_check_menu_item_new_with_label (_("force inline for all parts"));
1377 
1378     g_object_set_data(G_OBJECT(menuitem), "balsa-message", bm);
1379     g_signal_connect(G_OBJECT (menuitem), "activate",
1380 		     G_CALLBACK (toggle_all_inline_cb),
1381 		     (gpointer) info);
1382     gtk_menu_shell_append(menu, menuitem);
1383 
1384     /* Clear force-inline to be consistent with initial FALSE state of
1385      * check-menu-item. */
1386     bm->force_inline = FALSE;
1387 }
1388 
1389 static void
display_part(BalsaMessage * bm,LibBalsaMessageBody * body,GtkTreeModel * model,GtkTreeIter * iter,gchar * part_id)1390 display_part(BalsaMessage * bm, LibBalsaMessageBody * body,
1391              GtkTreeModel * model, GtkTreeIter * iter, gchar * part_id)
1392 {
1393     BalsaPartInfo *info = NULL;
1394     gchar *content_type = libbalsa_message_body_get_mime_type(body);
1395     gchar *icon_title = NULL;
1396     gboolean is_multipart=libbalsa_message_body_is_multipart(body);
1397     GdkPixbuf *content_icon;
1398     gchar *content_desc;
1399 
1400     content_desc = libbalsa_vfs_content_description(content_type);
1401 
1402     if(!is_multipart ||
1403        g_ascii_strcasecmp(content_type, "message/rfc822")==0 ||
1404        g_ascii_strcasecmp(content_type, "multipart/signed")==0 ||
1405        g_ascii_strcasecmp(content_type, "multipart/encrypted")==0 ||
1406        g_ascii_strcasecmp(content_type, "multipart/mixed")==0 ||
1407        g_ascii_strcasecmp(content_type, "multipart/alternative")==0) {
1408 
1409         info = balsa_part_info_new(body);
1410         bm->info_count++;
1411 
1412         if (g_ascii_strcasecmp(content_type, "message/rfc822") == 0 &&
1413             body->embhdrs) {
1414             gchar *from = balsa_message_sender_to_gchar(body->embhdrs->from, 0);
1415             gchar *subj = g_strdup(body->embhdrs->subject);
1416             libbalsa_utf8_sanitize(&from, balsa_app.convert_unknown_8bit, NULL);
1417             libbalsa_utf8_sanitize(&subj, balsa_app.convert_unknown_8bit, NULL);
1418             icon_title =
1419                 g_strdup_printf(_("rfc822 message (from %s, subject \"%s\")"),
1420                                 from, subj);
1421             g_free(from);
1422             g_free(subj);
1423         } else if (is_multipart) {
1424             icon_title = mpart_content_name(content_type);
1425 	    if (!strcmp(part_id, "1")) {
1426 		add_toggle_inline_menu_item(GTK_MENU_SHELL(bm->parts_popup),
1427 					    bm, info);
1428 		gtk_menu_shell_append(GTK_MENU_SHELL(bm->parts_popup),
1429 				      gtk_separator_menu_item_new ());
1430 		add_to_attachments_popup(GTK_MENU_SHELL(bm->parts_popup),
1431 					 _("complete message"),
1432 					 bm, info);
1433 		gtk_menu_shell_append(GTK_MENU_SHELL(bm->parts_popup),
1434 				      gtk_separator_menu_item_new ());
1435 	    }
1436         } else if (body->filename) {
1437             gchar * filename = g_strdup(body->filename);
1438 	    gchar * menu_label;
1439 
1440             libbalsa_utf8_sanitize(&filename, balsa_app.convert_unknown_8bit,
1441                                    NULL);
1442             icon_title =
1443                 g_strdup_printf("%s (%s)", filename, content_desc);
1444 
1445 	    /* this should neither be a message nor multipart, so add it to the
1446 	       attachments popup */
1447 	    menu_label =
1448 		g_strdup_printf(_("part %s: %s (file %s)"), part_id,
1449 				content_desc, filename);
1450 	    add_to_attachments_popup(GTK_MENU_SHELL(bm->parts_popup),
1451 				     menu_label, bm, info);
1452 	    g_free(menu_label);
1453             g_free(filename);
1454         } else {
1455 	    gchar * menu_label;
1456 
1457             icon_title = g_strdup_printf("%s", content_desc);
1458 	    menu_label =
1459 		g_strdup_printf(_("part %s: %s"), part_id, content_desc);
1460 	    add_to_attachments_popup(GTK_MENU_SHELL(bm->parts_popup),
1461 				     menu_label, bm, info);
1462 	    g_free(menu_label);
1463 	}
1464 
1465         part_create_menu (info);
1466         info->path = gtk_tree_model_get_path(model, iter);
1467 
1468         /* add to the tree view */
1469 #ifdef HAVE_GPGME
1470         content_icon =
1471 	    get_crypto_content_icon(body, content_type, &icon_title);
1472 	if (info->body->was_encrypted) {
1473 	    gchar * new_title =
1474 		g_strconcat(_("encrypted: "), icon_title, NULL);
1475 	    g_free(icon_title);
1476 	    icon_title = new_title;
1477 	}
1478 #else
1479 	content_icon = NULL;
1480 #endif
1481         if (!content_icon)
1482 	    content_icon =
1483 		libbalsa_icon_finder(GTK_WIDGET(bm),
1484                                      content_type, NULL, NULL,
1485 				     GTK_ICON_SIZE_LARGE_TOOLBAR);
1486         gtk_tree_store_set (GTK_TREE_STORE(model), iter,
1487                             PART_INFO_COLUMN, info,
1488 			    PART_NUM_COLUMN, part_id,
1489                             MIME_ICON_COLUMN, content_icon,
1490                             MIME_TYPE_COLUMN, icon_title, -1);
1491 
1492         g_object_unref(info);
1493         g_free(icon_title);
1494     } else {
1495 	content_icon =
1496 	    libbalsa_icon_finder(GTK_WIDGET(bm),
1497                                  content_type, NULL, NULL,
1498 				 GTK_ICON_SIZE_LARGE_TOOLBAR);
1499         gtk_tree_store_set (GTK_TREE_STORE(model), iter,
1500                             PART_INFO_COLUMN, NULL,
1501 			    PART_NUM_COLUMN, part_id,
1502                             MIME_ICON_COLUMN, content_icon,
1503                             MIME_TYPE_COLUMN, content_desc, -1);
1504     }
1505 
1506     if (content_icon)
1507 	g_object_unref(G_OBJECT(content_icon));
1508     g_free(content_desc);
1509     g_free(content_type);
1510 }
1511 
1512 static void
display_parts(BalsaMessage * bm,LibBalsaMessageBody * body,GtkTreeIter * parent,gchar * prefix)1513 display_parts(BalsaMessage * bm, LibBalsaMessageBody * body,
1514               GtkTreeIter * parent, gchar * prefix)
1515 {
1516     GtkTreeModel *model =
1517         gtk_tree_view_get_model(GTK_TREE_VIEW(bm->treeview));
1518     GtkTreeIter iter;
1519     gint part_in_level = 1;
1520 
1521     while (body) {
1522 	gchar * part_id;
1523 
1524 	if (prefix)
1525 	    part_id = g_strdup_printf("%s.%d", prefix, part_in_level);
1526 	else
1527 	    part_id = g_strdup_printf("%d", part_in_level);
1528         gtk_tree_store_append(GTK_TREE_STORE(model), &iter, parent);
1529         display_part(bm, body, model, &iter, part_id);
1530         display_parts(bm, body->parts, &iter, part_id);
1531         body = body->next;
1532 	part_in_level++;
1533 	g_free(part_id);
1534     }
1535 }
1536 
1537 /* Display the image in a "Face:" header, if any. */
1538 static void
display_face(BalsaMessage * bm)1539 display_face(BalsaMessage * bm)
1540 {
1541     GtkWidget *face_box;
1542     const gchar *face, *x_face = NULL;
1543     GError *err = NULL;
1544     GtkWidget *image;
1545 
1546     face_box = bm->face_box;
1547     gtk_widget_hide(face_box);
1548     gtk_container_foreach(GTK_CONTAINER(face_box),
1549                           (GtkCallback) gtk_widget_destroy, NULL);
1550 
1551     if (!bm->message
1552         || !((face = libbalsa_message_get_user_header(bm->message, "Face"))
1553              || (x_face =
1554                  libbalsa_message_get_user_header(bm->message,
1555                                                   "X-Face")))) {
1556         return;
1557     }
1558 
1559     if (face)
1560         image = libbalsa_get_image_from_face_header(face, &err);
1561     else {
1562 #if HAVE_COMPFACE
1563         image = libbalsa_get_image_from_x_face_header(x_face, &err);
1564 #else                           /* HAVE_COMPFACE */
1565         return;
1566 #endif                          /* HAVE_COMPFACE */
1567     }
1568     if (err) {
1569         balsa_information(LIBBALSA_INFORMATION_WARNING,
1570                 /* Translators: please do not translate Face. */
1571                           _("Error loading Face: %s"), err->message);
1572         g_error_free(err);
1573         return;
1574     }
1575 
1576     gtk_box_pack_start(GTK_BOX(face_box), image, FALSE, FALSE, 0);
1577     gtk_widget_show_all(face_box);
1578 }
1579 
1580 static void
display_content(BalsaMessage * bm)1581 display_content(BalsaMessage * bm)
1582 {
1583     balsa_message_clear_tree(bm);
1584     if (bm->parts_popup)
1585 	g_object_unref(bm->parts_popup);
1586     bm->parts_popup = gtk_menu_new();
1587     g_object_ref_sink(bm->parts_popup);
1588     display_parts(bm, bm->message->body_list, NULL, NULL);
1589     if (bm->info_count > 1) {
1590  	gtk_widget_show_all(bm->parts_popup);
1591  	gtk_widget_show_all
1592 	    (GTK_WIDGET(bm_header_widget_att_button(bm)));
1593     } else
1594  	gtk_widget_hide
1595 	    (GTK_WIDGET(bm_header_widget_att_button(bm)));
1596     display_face(bm);
1597     gtk_tree_view_columns_autosize(GTK_TREE_VIEW(bm->treeview));
1598     gtk_tree_view_expand_all(GTK_TREE_VIEW(bm->treeview));
1599 }
1600 
1601 void
balsa_message_copy_part(const gchar * url,LibBalsaMessageBody * part)1602 balsa_message_copy_part(const gchar *url, LibBalsaMessageBody *part)
1603 {
1604     GError *err = NULL;
1605     LibBalsaMailbox *mailbox = balsa_find_mailbox_by_url(url);
1606     GMimeStream *stream;
1607 
1608     g_return_if_fail(mailbox != NULL);
1609 
1610     stream = libbalsa_message_body_get_stream(part, &err);
1611 
1612     if (!stream) {
1613 	libbalsa_information(LIBBALSA_INFORMATION_ERROR,
1614 			     _("Reading embedded message failed: %s"),
1615 			     err ? err->message : "?");
1616 	g_clear_error(&err);
1617 	return;
1618     }
1619 
1620     if (!libbalsa_mailbox_add_message(mailbox, stream, 0, &err)) {
1621 	libbalsa_information(LIBBALSA_INFORMATION_ERROR,
1622 			     _("Appending message to %s failed: %s"),
1623 			     mailbox->name,
1624 			     err ? err->message : "?");
1625 	g_clear_error(&err);
1626     }
1627     g_object_unref(stream);
1628 }
1629 
1630 static void
part_create_menu(BalsaPartInfo * info)1631 part_create_menu (BalsaPartInfo* info)
1632 /* Remarks: Will add items in the following order:
1633             1) Default application according to GnomeVFS.
1634             2) GNOME MIME/GnomeVFS key values that don't match default
1635                application or anything on the shortlist.
1636             3) GnomeVFS shortlist applications, with the default one (sometimes
1637                included on shortlist, sometimes not) excluded. */
1638 {
1639     GtkWidget* menu_item;
1640     gchar* content_type;
1641 
1642     info->popup_menu = gtk_menu_new ();
1643     g_object_ref_sink(info->popup_menu);
1644 
1645     content_type = libbalsa_message_body_get_mime_type (info->body);
1646     libbalsa_vfs_fill_menu_by_content_type(GTK_MENU(info->popup_menu),
1647 					   content_type,
1648 					   G_CALLBACK (balsa_mime_widget_ctx_menu_cb),
1649 					   (gpointer)info->body);
1650 
1651     menu_item = gtk_menu_item_new_with_mnemonic (_("_Save..."));
1652     g_signal_connect (G_OBJECT (menu_item), "activate",
1653                       G_CALLBACK (balsa_mime_widget_ctx_menu_save), (gpointer) info->body);
1654     gtk_menu_shell_append (GTK_MENU_SHELL (info->popup_menu), menu_item);
1655 
1656     if (g_ascii_strcasecmp(content_type, "message/rfc822") == 0) {
1657         GtkWidget *submenu;
1658 
1659         menu_item =
1660             gtk_menu_item_new_with_mnemonic(_("_Copy to folder..."));
1661         gtk_menu_shell_append(GTK_MENU_SHELL(info->popup_menu), menu_item);
1662 
1663         submenu =
1664             balsa_mblist_mru_menu(GTK_WINDOW(gtk_widget_get_toplevel(info->popup_menu)),
1665                                   &balsa_app.folder_mru,
1666                                   G_CALLBACK(balsa_message_copy_part),
1667                                   info->body);
1668         gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
1669     }
1670 
1671     gtk_widget_show_all (info->popup_menu);
1672     g_free (content_type);
1673 }
1674 
1675 
1676 static void
balsa_part_info_init(GObject * object,gpointer data)1677 balsa_part_info_init(GObject *object, gpointer data)
1678 {
1679     BalsaPartInfo * info = BALSA_PART_INFO(object);
1680 
1681     info->body = NULL;
1682     info->mime_widget = NULL;
1683     info->popup_menu = NULL;
1684     info->path = NULL;
1685 }
1686 
1687 static BalsaPartInfo*
balsa_part_info_new(LibBalsaMessageBody * body)1688 balsa_part_info_new(LibBalsaMessageBody* body)
1689 {
1690     BalsaPartInfo * info = g_object_new(TYPE_BALSA_PART_INFO, NULL);
1691     info->body = body;
1692     return info;
1693 }
1694 
1695 static void
balsa_part_info_free(GObject * object)1696 balsa_part_info_free(GObject * object)
1697 {
1698     BalsaPartInfo * info;
1699     GObjectClass *parent_class;
1700 
1701     g_return_if_fail(object != NULL);
1702     g_return_if_fail(IS_BALSA_PART_INFO(object));
1703     info = BALSA_PART_INFO(object);
1704 
1705     if (info->mime_widget) {
1706 	g_object_unref(G_OBJECT(info->mime_widget));
1707 	info->mime_widget = NULL;
1708     }
1709     if (info->popup_menu)
1710         g_object_unref(info->popup_menu);
1711 
1712     gtk_tree_path_free(info->path);
1713 
1714     parent_class = g_type_class_peek_parent(G_OBJECT_GET_CLASS(object));
1715     parent_class->finalize(object);
1716 }
1717 
1718 static void
part_context_save_all_cb(GtkWidget * menu_item,GList * info_list)1719 part_context_save_all_cb(GtkWidget * menu_item, GList * info_list)
1720 {
1721     while (info_list) {
1722 	balsa_mime_widget_ctx_menu_save(menu_item,
1723                                         BALSA_PART_INFO(info_list->data)->body);
1724         info_list = g_list_next(info_list);
1725     }
1726 }
1727 
1728 /*
1729  * Let the user select a folder and save all message parts form info_list in
1730  * this folder, either with their name (if defined) or as localised "content-
1731  * type message part". The function protects files from being overwritten by
1732  * appending "(1)", "(2)", ... to the name to make it unique.
1733  * Sets balsa_app::save_dir to the selected folder.
1734  */
1735 static void
part_context_dump_all_cb(GtkWidget * menu_item,GList * info_list)1736 part_context_dump_all_cb(GtkWidget * menu_item, GList * info_list)
1737 {
1738     GtkWidget *dump_dialog;
1739 
1740     g_return_if_fail(info_list);
1741 
1742     dump_dialog =
1743         gtk_file_chooser_dialog_new(_("Select folder for saving selected parts"),
1744                                     balsa_get_parent_window(menu_item),
1745                                     GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
1746                                     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1747                                     GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
1748 #if HAVE_MACOSX_DESKTOP
1749     libbalsa_macosx_menu_for_parent(dump_dialog, balsa_get_parent_window(menu_item));
1750 #endif
1751     gtk_dialog_set_default_response(GTK_DIALOG(dump_dialog),
1752                                     GTK_RESPONSE_CANCEL);
1753     gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dump_dialog),
1754                                     libbalsa_vfs_local_only());
1755     if (balsa_app.save_dir)
1756         gtk_file_chooser_set_current_folder_uri(GTK_FILE_CHOOSER(dump_dialog),
1757                                                 balsa_app.save_dir);
1758 
1759     if (gtk_dialog_run(GTK_DIALOG(dump_dialog)) == GTK_RESPONSE_OK) {
1760 	gchar *dir_name =
1761             gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dump_dialog));
1762         LibbalsaVfs * dir_uri;
1763 
1764         g_message("store to URI: %s", dir_name);
1765         if (!(dir_uri = libbalsa_vfs_new_from_uri(dir_name)))
1766             balsa_information(LIBBALSA_INFORMATION_ERROR,
1767                               _("Could not create URI for %s"),
1768                               dir_name);
1769 
1770 	/* remember the folder */
1771 	g_free(balsa_app.save_dir);
1772 	balsa_app.save_dir = dir_name;
1773 
1774 	/* save all parts without further user interaction */
1775 	info_list = g_list_first(info_list);
1776 	while (dir_uri && info_list) {
1777 	    BalsaPartInfo *info = BALSA_PART_INFO(info_list->data);
1778             LibbalsaVfs * save_uri;
1779 	    gboolean result;
1780             GError *err = NULL;
1781 
1782 	    if (info->body->filename)
1783 		save_uri =
1784 		    libbalsa_vfs_dir_append(dir_uri, info->body->filename);
1785 	    else {
1786 		gchar *cont_type =
1787 		    libbalsa_message_body_get_mime_type(info->body);
1788 		gchar *p;
1789 
1790 		/* be sure to have no '/' in the file name */
1791 		g_strdelimit(cont_type, G_DIR_SEPARATOR_S, '-');
1792 		p = g_strdup_printf(_("%s message part"), cont_type);
1793 		g_free(cont_type);
1794 		save_uri = libbalsa_vfs_dir_append(dir_uri, p);
1795 		g_free(p);
1796 	    }
1797             g_message("store to file: %s", libbalsa_vfs_get_uri_utf8(save_uri));
1798 
1799 	    /* don't overwrite existing files, append (1), (2), ... instead */
1800 	    if (libbalsa_vfs_file_exists(save_uri)) {
1801 		gint n = 1;
1802 		LibbalsaVfs * base_uri = save_uri;
1803 
1804 		save_uri = NULL;
1805 		do {
1806                     gchar * ext = g_strdup_printf(" (%d)", n++);
1807 		    if (save_uri)
1808                         g_object_unref(save_uri);
1809                     save_uri = libbalsa_vfs_append(base_uri, ext);
1810                     g_free(ext);
1811 		} while (libbalsa_vfs_file_exists(save_uri));
1812 		g_object_unref(base_uri);
1813 	    }
1814             g_message("store to file: %s", libbalsa_vfs_get_uri_utf8(save_uri));
1815 
1816 	    /* try to save the file */
1817             result =
1818                 libbalsa_message_body_save_vfs(info->body, save_uri,
1819                                                LIBBALSA_MESSAGE_BODY_UNSAFE,
1820                                                info->body->body_type ==
1821                                                LIBBALSA_MESSAGE_BODY_TYPE_TEXT,
1822                                                &err);
1823 	    if (!result)
1824 		balsa_information(LIBBALSA_INFORMATION_ERROR,
1825 				  _("Could not save %s: %s"),
1826 				  libbalsa_vfs_get_uri_utf8(save_uri),
1827                                   err && err->message ?
1828                                   err->message : "Unknown error");
1829             g_clear_error(&err);
1830 	    g_object_unref(save_uri);
1831 	    info_list = g_list_next(info_list);
1832 	}
1833 	g_object_unref(dir_uri);
1834     }
1835     gtk_widget_destroy(dump_dialog);
1836 }
1837 
1838 
1839 typedef struct _selFirst_T {
1840     GtkTreeIter sel_iter;
1841     gboolean found;
1842 } selFirst_T;
1843 
1844 static void
tree_selection_get_first(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)1845 tree_selection_get_first(GtkTreeModel * model, GtkTreePath * path,
1846                          GtkTreeIter * iter, gpointer data)
1847 {
1848     selFirst_T *sel = (selFirst_T *)data;
1849 
1850     if (!sel->found) {
1851         sel->found = TRUE;
1852         memcpy (&sel->sel_iter, iter, sizeof(GtkTreeIter));
1853     }
1854 }
1855 
1856 static BalsaPartInfo *
bm_next_part_info(BalsaMessage * bmessage)1857 bm_next_part_info(BalsaMessage * bmessage)
1858 {
1859     selFirst_T sel;
1860     GtkTreeView *gtv;
1861     GtkTreeModel *model;
1862 
1863     g_return_val_if_fail(bmessage != NULL, NULL);
1864     g_return_val_if_fail(bmessage->treeview != NULL, NULL);
1865 
1866     gtv = GTK_TREE_VIEW(bmessage->treeview);
1867     model = gtk_tree_view_get_model(gtv);
1868 
1869     /* get the info of the first selected part */
1870     sel.found = FALSE;
1871     gtk_tree_selection_selected_foreach(gtk_tree_view_get_selection(gtv),
1872                                         tree_selection_get_first, &sel);
1873     if (!sel.found) {
1874         /* return the first part if nothing is selected */
1875         if (!gtk_tree_model_get_iter_first(model, &sel.sel_iter))
1876             return NULL;
1877     } else {
1878         GtkTreeIter iter;
1879 
1880         /* If the first selected iter has a child, select it, otherwise
1881          * take next on the same or higher level.  If there is no next,
1882          * return NULL */
1883         if (!gtk_tree_model_iter_children (model, &iter, &sel.sel_iter)) {
1884             GtkTreeIter tmp_iter;
1885 
1886             tmp_iter = iter = sel.sel_iter;
1887             while (!gtk_tree_model_iter_next (model, &iter)) {
1888                 if (!gtk_tree_model_iter_parent(model, &iter, &tmp_iter))
1889                     return NULL;
1890 	        tmp_iter = iter;
1891             }
1892         }
1893         sel.sel_iter = iter;
1894     }
1895 
1896     return tree_next_valid_part_info(model, &sel.sel_iter);
1897 }
1898 
1899 void
balsa_message_next_part(BalsaMessage * bmessage)1900 balsa_message_next_part(BalsaMessage * bmessage)
1901 {
1902     BalsaPartInfo *info;
1903     GtkTreeView *gtv;
1904 
1905     if (!(info = bm_next_part_info(bmessage)))
1906 	return;
1907 
1908     gtv = GTK_TREE_VIEW(bmessage->treeview);
1909     gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(gtv));
1910     select_part(bmessage, info);
1911     g_object_unref(info);
1912 }
1913 
1914 gboolean
balsa_message_has_next_part(BalsaMessage * bmessage)1915 balsa_message_has_next_part(BalsaMessage * bmessage)
1916 {
1917     BalsaPartInfo *info;
1918 
1919     if (bmessage && bmessage->treeview
1920         && (info = bm_next_part_info(bmessage))) {
1921         g_object_unref(info);
1922         return TRUE;
1923     }
1924 
1925     return FALSE;
1926 }
1927 
1928 static BalsaPartInfo *
bm_previous_part_info(BalsaMessage * bmessage)1929 bm_previous_part_info(BalsaMessage * bmessage)
1930 {
1931     selFirst_T sel;
1932     GtkTreeView *gtv;
1933     GtkTreeModel *model;
1934     BalsaPartInfo *info;
1935 
1936     g_return_val_if_fail(bmessage != NULL, NULL);
1937     g_return_val_if_fail(bmessage->treeview != NULL, NULL);
1938 
1939     gtv = GTK_TREE_VIEW(bmessage->treeview);
1940     model = gtk_tree_view_get_model(gtv);
1941 
1942     /* get the info of the first selected part */
1943     sel.found = FALSE;
1944     gtk_tree_selection_selected_foreach(gtk_tree_view_get_selection(gtv),
1945                                         tree_selection_get_first, &sel);
1946     if (!sel.found) {
1947         /* return the first part if nothing is selected */
1948         if (!gtk_tree_model_get_iter_first(model, &sel.sel_iter))
1949             return NULL;
1950         gtk_tree_model_get(model, &sel.sel_iter, PART_INFO_COLUMN, &info, -1);
1951     } else {
1952         GtkTreePath * path = gtk_tree_model_get_path(model, &sel.sel_iter);
1953 
1954         /* find the previous element with a valid info block */
1955         do {
1956             if (!gtk_tree_path_prev (path)) {
1957                 if (gtk_tree_path_get_depth (path) <= 1) {
1958                     gtk_tree_path_free(path);
1959                     return NULL;
1960                 }
1961                 gtk_tree_path_up(path);
1962             }
1963             gtk_tree_model_get_iter(model, &sel.sel_iter, path);
1964             gtk_tree_model_get(model, &sel.sel_iter, PART_INFO_COLUMN,
1965                                &info, -1);
1966         } while (!info);
1967         gtk_tree_path_free(path);
1968     }
1969 
1970     return info;
1971 }
1972 
1973 void
balsa_message_previous_part(BalsaMessage * bmessage)1974 balsa_message_previous_part(BalsaMessage * bmessage)
1975 {
1976     BalsaPartInfo *info;
1977     GtkTreeView *gtv;
1978 
1979     if (!(info = bm_previous_part_info(bmessage)))
1980 	return;
1981 
1982     gtv = GTK_TREE_VIEW(bmessage->treeview);
1983     gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(gtv));
1984     select_part(bmessage, info);
1985     g_object_unref(info);
1986 }
1987 
1988 gboolean
balsa_message_has_previous_part(BalsaMessage * bmessage)1989 balsa_message_has_previous_part(BalsaMessage * bmessage)
1990 {
1991     BalsaPartInfo *info;
1992 
1993     if (bmessage && bmessage->treeview
1994         && (info = bm_previous_part_info(bmessage))) {
1995         g_object_unref(info);
1996         return TRUE;
1997     }
1998 
1999     return FALSE;
2000 }
2001 
2002 #ifdef HAVE_HTML_WIDGET
2003 static gboolean
libbalsa_can_display(LibBalsaMessageBody * part)2004 libbalsa_can_display(LibBalsaMessageBody *part)
2005 {
2006     gchar *content_type = libbalsa_message_body_get_mime_type(part);
2007     gboolean res = FALSE;
2008     if (!balsa_app.display_alt_plain &&
2009 	libbalsa_html_type(content_type))
2010 	res = TRUE;
2011     else if(g_ascii_strcasecmp(content_type, "multipart/related") == 0 &&
2012 	    part->parts)
2013 	res = libbalsa_can_display(part->parts);
2014     g_free(content_type);
2015     return res;
2016 }
2017 #endif                          /* HAVE_HTML_WIDGET */
2018 
2019 /** Determines whether given part can be displayed. We display plain
2020    text, parts html/rtf parts unless it has been disabled in the
2021    preferences. We make sure the process is correctly recurrsive, to
2022    display properly messages of following structure:
2023 
2024    multiplart/alternative
2025      text/plain "A"
2026      multipart/related
2027        text/plain "B"
2028        image/jpeg "C"
2029 
2030    In the case as above, B & C should be displayed.
2031 */
2032 static LibBalsaMessageBody*
preferred_part(LibBalsaMessageBody * parts)2033 preferred_part(LibBalsaMessageBody *parts)
2034 {
2035     LibBalsaMessageBody *body, *preferred = parts;
2036 
2037     for (body = parts; body; body = body->next) {
2038         gchar *content_type;
2039 
2040         content_type = libbalsa_message_body_get_mime_type(body);
2041 
2042         if (g_ascii_strcasecmp(content_type, "text/plain") == 0 ||
2043             g_ascii_strcasecmp(content_type, "text/calendar") == 0)
2044             preferred = body;
2045 #ifdef HAVE_HTML_WIDGET
2046         else if (libbalsa_can_display(body))
2047             preferred = body;
2048 #endif                          /* HAVE_HTML_WIDGET */
2049 
2050         g_free(content_type);
2051     }
2052 
2053     return preferred;
2054 }
2055 
2056 typedef struct _treeSearchT {
2057     const LibBalsaMessageBody *body;
2058     BalsaPartInfo *info;
2059 } treeSearchT;
2060 
2061 static gboolean
treeSearch_Func(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)2062 treeSearch_Func(GtkTreeModel * model, GtkTreePath *path,
2063                 GtkTreeIter * iter, gpointer data)
2064 {
2065     treeSearchT *search = (treeSearchT *)data;
2066     BalsaPartInfo *info = NULL;
2067 
2068     gtk_tree_model_get(model, iter, PART_INFO_COLUMN, &info, -1);
2069     if (info) {
2070         if (info->body == search->body) {
2071             search->info = info;
2072             return TRUE;
2073         } else
2074             g_object_unref(info);
2075     }
2076 
2077     return FALSE;
2078 }
2079 
2080 static BalsaPartInfo *
part_info_from_body(BalsaMessage * bm,const LibBalsaMessageBody * body)2081 part_info_from_body(BalsaMessage *bm, const LibBalsaMessageBody *body)
2082 {
2083     treeSearchT search;
2084 
2085     search.body = body;
2086     search.info = NULL;
2087 
2088     gtk_tree_model_foreach
2089         (gtk_tree_view_get_model(GTK_TREE_VIEW(bm->treeview)),
2090          treeSearch_Func, &search);
2091     return search.info;
2092 }
2093 
2094 
2095 static LibBalsaMessageBody *
add_body(BalsaMessage * bm,LibBalsaMessageBody * body,GtkWidget * container)2096 add_body(BalsaMessage * bm, LibBalsaMessageBody * body,
2097          GtkWidget * container)
2098 {
2099     if(body) {
2100         BalsaPartInfo *info = part_info_from_body(bm, body);
2101 
2102         if (info) {
2103 	    body = add_part(bm, info, container);
2104             g_object_unref(info);
2105         } else
2106 	    body = add_multipart(bm, body, container);
2107     }
2108 
2109     return body;
2110 }
2111 
2112 static LibBalsaMessageBody *
add_multipart_digest(BalsaMessage * bm,LibBalsaMessageBody * body,GtkWidget * container)2113 add_multipart_digest(BalsaMessage * bm, LibBalsaMessageBody * body,
2114                      GtkWidget * container)
2115 {
2116     LibBalsaMessageBody *retval = NULL;
2117     /* Add all parts */
2118     retval = add_body(bm, body, container);
2119     for (body = body->next; body; body = body->next)
2120         add_body(bm, body, container);
2121 
2122     return retval;
2123 }
2124 
2125 static LibBalsaMessageBody *
add_multipart_mixed(BalsaMessage * bm,LibBalsaMessageBody * body,GtkWidget * container)2126 add_multipart_mixed(BalsaMessage * bm, LibBalsaMessageBody * body,
2127                     GtkWidget * container)
2128 {
2129     LibBalsaMessageBody * retval = NULL;
2130     /* Add first (main) part + anything else with
2131        Content-Disposition: inline */
2132     if (body) {
2133         retval = add_body(bm, body, container);
2134         for (body = body->next; body; body = body->next) {
2135 #ifdef HAVE_GPGME
2136 	    GMimeContentType *type =
2137 		g_mime_content_type_new_from_string(body->content_type);
2138 
2139             if (libbalsa_message_body_is_inline(body) ||
2140 		bm->force_inline ||
2141                 libbalsa_message_body_is_multipart(body) ||
2142 		g_mime_content_type_is_type(type, "application", "pgp-signature") ||
2143 		(balsa_app.has_smime &&
2144 		 (g_mime_content_type_is_type(type, "application", "pkcs7-signature") ||
2145 		  g_mime_content_type_is_type(type, "application", "x-pkcs7-signature"))))
2146                 add_body(bm, body, container);
2147 	    g_object_unref(type);
2148 #else
2149             if (libbalsa_message_body_is_inline(body) ||
2150 		bm->force_inline ||
2151 		libbalsa_message_body_is_multipart(body))
2152                 add_body(bm, body, container);
2153 #endif
2154         }
2155     }
2156 
2157     return retval;
2158 }
2159 
2160 static LibBalsaMessageBody *
add_multipart(BalsaMessage * bm,LibBalsaMessageBody * body,GtkWidget * container)2161 add_multipart(BalsaMessage *bm, LibBalsaMessageBody *body,
2162               GtkWidget * container)
2163 /* This function handles multiparts as specified by RFC2046 5.1 and
2164  * message/rfc822 types. */
2165 {
2166     GMimeContentType *type;
2167 
2168     if (!body->parts)
2169 	return body;
2170 
2171     type=g_mime_content_type_new_from_string(body->content_type);
2172 
2173     if (g_mime_content_type_is_type(type, "multipart", "related")) {
2174         /* FIXME: more processing required see RFC1872 */
2175         /* Add the first part */
2176         body = add_body(bm, body->parts, container);
2177     } else if (g_mime_content_type_is_type(type, "multipart", "alternative")) {
2178             /* Add the most suitable part. */
2179         body = add_body(bm, preferred_part(body->parts), container);
2180     } else if (g_mime_content_type_is_type(type, "multipart", "digest")) {
2181 	body = add_multipart_digest(bm, body->parts, container);
2182     } else { /* default to multipart/mixed */
2183 	body = add_multipart_mixed(bm, body->parts, container);
2184     }
2185     g_object_unref(type);
2186 
2187     return body;
2188 }
2189 
2190 static LibBalsaMessageBody *
add_part(BalsaMessage * bm,BalsaPartInfo * info,GtkWidget * container)2191 add_part(BalsaMessage * bm, BalsaPartInfo * info, GtkWidget * container)
2192 {
2193     GtkTreeSelection *selection;
2194     GtkWidget *widget;
2195     LibBalsaMessageBody *body;
2196 
2197     if (!info)
2198 	return NULL;
2199 
2200     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(bm->treeview));
2201 
2202     if (info->path &&
2203 	!gtk_tree_selection_path_is_selected(selection, info->path))
2204 	gtk_tree_selection_select_path(selection, info->path);
2205 
2206     if (info->mime_widget == NULL)
2207 	part_info_init(bm, info);
2208 
2209     if ((widget = info->mime_widget->widget))
2210         gtk_box_pack_start(GTK_BOX(container), widget, TRUE, TRUE, 0);
2211 
2212     body =
2213         add_multipart(bm, info->body,
2214                       info->mime_widget->container ?
2215                       info->mime_widget->container : container);
2216 
2217     return body;
2218 }
2219 
2220 
2221 static gboolean
gtk_tree_hide_func(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)2222 gtk_tree_hide_func(GtkTreeModel * model, GtkTreePath * path,
2223                    GtkTreeIter * iter, gpointer data)
2224 {
2225     BalsaPartInfo *info;
2226 
2227     gtk_tree_model_get(model, iter, PART_INFO_COLUMN, &info, -1);
2228     if (info) {
2229         GtkWidget *widget, *parent;
2230 
2231         if (info->mime_widget && (widget = info->mime_widget->widget)
2232             && (parent = gtk_widget_get_parent(widget)))
2233             gtk_container_remove(GTK_CONTAINER(parent), widget);
2234         g_object_unref(info);
2235     }
2236 
2237     return FALSE;
2238 }
2239 
2240 static void
hide_all_parts(BalsaMessage * bm)2241 hide_all_parts(BalsaMessage * bm)
2242 {
2243     if (bm->current_part) {
2244 	gtk_tree_model_foreach(gtk_tree_view_get_model
2245 			       (GTK_TREE_VIEW(bm->treeview)),
2246 			       gtk_tree_hide_func, bm);
2247 	gtk_tree_selection_unselect_all(gtk_tree_view_get_selection
2248 					(GTK_TREE_VIEW(bm->treeview)));
2249         g_object_unref(bm->current_part);
2250 	bm->current_part = NULL;
2251     }
2252 
2253     gtk_container_foreach(GTK_CONTAINER(bm->bm_widget->container),
2254                           (GtkCallback) gtk_widget_destroy, NULL);
2255 }
2256 
2257 /*
2258  * If part == -1 then change to no part
2259  * must release selection before hiding a text widget.
2260  */
2261 static void
select_part(BalsaMessage * bm,BalsaPartInfo * info)2262 select_part(BalsaMessage * bm, BalsaPartInfo *info)
2263 {
2264     LibBalsaMessageBody *body;
2265     GtkAdjustment *hadj, *vadj;
2266 
2267     hide_all_parts(bm);
2268 
2269     if (bm->current_part)
2270         g_object_unref(bm->current_part);
2271 
2272     body = add_part(bm, info, bm->bm_widget->container);
2273     bm->current_part = part_info_from_body(bm, body);
2274 
2275     g_signal_emit(G_OBJECT(bm), balsa_message_signals[SELECT_PART], 0);
2276 
2277     g_object_get(G_OBJECT(bm->scroll), "hadjustment", &hadj,
2278                                               "vadjustment", &vadj, NULL);
2279     gtk_adjustment_set_value(hadj, 0);
2280     gtk_adjustment_set_value(vadj, 0);
2281 }
2282 
2283 GtkWidget *
balsa_message_current_part_widget(BalsaMessage * bmessage)2284 balsa_message_current_part_widget(BalsaMessage * bmessage)
2285 {
2286     if (bmessage && bmessage->current_part &&
2287 	bmessage->current_part->mime_widget)
2288 	return bmessage->current_part->mime_widget->widget;
2289     else
2290 	return NULL;
2291 }
2292 
2293 GtkWindow *
balsa_get_parent_window(GtkWidget * widget)2294 balsa_get_parent_window(GtkWidget * widget)
2295 {
2296     if (widget) {
2297         GtkWidget *toplevel = gtk_widget_get_toplevel(widget);
2298 
2299         if (gtk_widget_is_toplevel(toplevel) && GTK_IS_WINDOW(toplevel))
2300             return GTK_WINDOW(toplevel);
2301     }
2302 
2303     return GTK_WINDOW(balsa_app.main_window);
2304 }
2305 
2306 
2307 /*
2308  * This function informs the caller if the currently selected part
2309  * supports selection/copying etc.
2310  */
2311 gboolean
balsa_message_can_select(BalsaMessage * bmessage)2312 balsa_message_can_select(BalsaMessage * bmessage)
2313 {
2314     GtkWidget *w;
2315 
2316     g_return_val_if_fail(bmessage != NULL, FALSE);
2317 
2318     if (bmessage->current_part == NULL
2319         || (w = bmessage->current_part->mime_widget->widget) == NULL)
2320         return FALSE;
2321 
2322     return GTK_IS_EDITABLE(w) || GTK_IS_TEXT_VIEW(w)
2323 #ifdef    HAVE_HTML_WIDGET
2324         || libbalsa_html_can_select(w)
2325 #endif /* HAVE_HTML_WIDGET */
2326         ;
2327 }
2328 
2329 gboolean
balsa_message_grab_focus(BalsaMessage * bmessage)2330 balsa_message_grab_focus(BalsaMessage * bmessage)
2331 {
2332     GtkWidget *widget;
2333 
2334     g_return_val_if_fail(bmessage != NULL, FALSE);
2335     g_return_val_if_fail(bmessage->current_part != NULL, FALSE);
2336 
2337     widget = bmessage->current_part->mime_widget->widget;
2338     g_return_val_if_fail(widget != NULL, FALSE);
2339 
2340     gtk_widget_set_can_focus(widget, TRUE);
2341     gtk_widget_grab_focus(widget);
2342 
2343     return TRUE;
2344 }
2345 
2346 static InternetAddress *
bm_get_mailbox(InternetAddressList * list)2347 bm_get_mailbox(InternetAddressList * list)
2348 {
2349     InternetAddress *ia;
2350 
2351     if (!list)
2352 	return NULL;
2353 
2354     ia = internet_address_list_get_address (list, 0);
2355     if (!ia)
2356 	return NULL;
2357 
2358     while (ia && INTERNET_ADDRESS_IS_GROUP (ia))
2359 	ia = internet_address_list_get_address (INTERNET_ADDRESS_GROUP (ia)->members, 0);
2360     if (!ia)
2361 	return NULL;
2362 
2363     return ia;
2364 }
2365 
2366 static void
handle_mdn_request(GtkWindow * parent,LibBalsaMessage * message)2367 handle_mdn_request(GtkWindow *parent, LibBalsaMessage *message)
2368 {
2369     gboolean suspicious;
2370     InternetAddressList *use_from;
2371     InternetAddressList *list;
2372     InternetAddress *from, *dn;
2373     BalsaMDNReply action;
2374     LibBalsaMessage *mdn;
2375     LibBalsaIdentity *mdn_ident = NULL;
2376     gint i, len;
2377 
2378     /* Check if the dispnotify_to address is equal to the (in this order,
2379        if present) reply_to, from or sender address. */
2380     if (message->headers->reply_to)
2381         use_from = message->headers->reply_to;
2382     else if (message->headers->from)
2383         use_from = message->headers->from;
2384     else if (message->sender)
2385         use_from = message->sender;
2386     else
2387         use_from = NULL;
2388     /* note: neither Disposition-Notification-To: nor Reply-To:, From: or
2389        Sender: may be address groups */
2390     from = use_from ? internet_address_list_get_address (use_from, 0) : NULL;
2391     dn = internet_address_list_get_address (message->headers->dispnotify_to, 0);
2392     suspicious = !libbalsa_ia_rfc2821_equal(dn, from);
2393 
2394     /* Try to find "my" identity first in the to, then in the cc list */
2395     list = message->headers->to_list;
2396     len = list ? internet_address_list_length(list) : 0;
2397     for (i = 0; i < len && !mdn_ident; i++) {
2398         GList * id_list;
2399 
2400         for (id_list = balsa_app.identities; !mdn_ident && id_list;
2401              id_list = id_list->next) {
2402             LibBalsaIdentity *ident = LIBBALSA_IDENTITY(id_list->data);
2403 
2404             if (libbalsa_ia_rfc2821_equal(ident->ia, bm_get_mailbox(list)))
2405                 mdn_ident = ident;
2406         }
2407     }
2408 
2409     list = message->headers->cc_list;
2410     for (i = 0; i < len && !mdn_ident; i++) {
2411         GList * id_list;
2412 
2413         for (id_list = balsa_app.identities; !mdn_ident && id_list;
2414              id_list = id_list->next) {
2415             LibBalsaIdentity *ident = LIBBALSA_IDENTITY(id_list->data);
2416 
2417             if (libbalsa_ia_rfc2821_equal(ident->ia, bm_get_mailbox(list)))
2418                 mdn_ident = ident;
2419         }
2420     }
2421 
2422 
2423     /* Now we decide from the settings of balsa_app.mdn_reply_[not]clean what
2424        to do...
2425     */
2426     if (suspicious || !mdn_ident)
2427         action = balsa_app.mdn_reply_notclean;
2428     else
2429         action = balsa_app.mdn_reply_clean;
2430     if (action == BALSA_MDN_REPLY_NEVER)
2431         return;
2432 
2433     /* fall back to the current identity if the requested one is empty */
2434     if (!mdn_ident)
2435         mdn_ident = balsa_app.current_ident;
2436 
2437     /* We *may* send a reply, so let's create a message for that... */
2438     mdn = create_mdn_reply (mdn_ident, message, action == BALSA_MDN_REPLY_ASKME);
2439 
2440     /* if the user wants to be asked, display a dialog, otherwise send... */
2441     if (action == BALSA_MDN_REPLY_ASKME) {
2442         gchar *sender;
2443         gchar *reply_to;
2444         sender = from ? internet_address_to_string (from, FALSE) : NULL;
2445         reply_to =
2446             internet_address_list_to_string (message->headers->dispnotify_to,
2447 		                             FALSE);
2448         gtk_widget_show_all (create_mdn_dialog (parent, sender, reply_to, mdn,
2449                                                 mdn_ident));
2450         g_free (reply_to);
2451         g_free (sender);
2452     } else {
2453 	GError * error = NULL;
2454 	LibBalsaMsgCreateResult result;
2455 
2456 #if ENABLE_ESMTP
2457         result = libbalsa_message_send(mdn, balsa_app.outbox, NULL,
2458 				       balsa_find_sentbox_by_url,
2459 				       mdn_ident->smtp_server,
2460 				       TRUE, balsa_app.debug, &error);
2461 #else
2462         result = libbalsa_message_send(mdn, balsa_app.outbox, NULL,
2463 				       balsa_find_sentbox_by_url,
2464 				       TRUE, balsa_app.debug, &error);
2465 #endif
2466 	if (result != LIBBALSA_MESSAGE_CREATE_OK)
2467 	    libbalsa_information(LIBBALSA_INFORMATION_ERROR,
2468 				 _("Sending the disposition notification failed: %s"),
2469 				 error ? error->message : "?");
2470 	g_error_free(error);
2471         g_object_unref(G_OBJECT(mdn));
2472     }
2473 }
2474 
create_mdn_reply(const LibBalsaIdentity * mdn_ident,LibBalsaMessage * for_msg,gboolean manual)2475 static LibBalsaMessage *create_mdn_reply (const LibBalsaIdentity *mdn_ident,
2476                                           LibBalsaMessage *for_msg,
2477                                           gboolean manual)
2478 {
2479     LibBalsaMessage *message;
2480     LibBalsaMessageBody *body;
2481     gchar *date, *dummy;
2482     GString *report;
2483     gchar **params;
2484     struct utsname uts_name;
2485     const gchar *original_rcpt;
2486 
2487     /* create a message with the header set from the incoming message */
2488     message = libbalsa_message_new();
2489     message->headers->from = internet_address_list_new();
2490     internet_address_list_add(message->headers->from,
2491                               balsa_app.current_ident->ia);
2492     message->headers->date = time(NULL);
2493     libbalsa_message_set_subject(message, "Message Disposition Notification");
2494     message->headers->to_list = internet_address_list_new ();
2495     internet_address_list_append(message->headers->to_list,
2496                                  for_msg->headers->dispnotify_to);
2497 
2498     /* RFC 2298 requests this mime type... */
2499     message->subtype = g_strdup("report");
2500     params = g_new(gchar *, 3);
2501     params[0] = g_strdup("report-type");
2502     params[1] = g_strdup("disposition-notification");
2503     params[2] = NULL;
2504     message->parameters = g_list_prepend(message->parameters, params);
2505 
2506     /* the first part of the body is an informational note */
2507     body = libbalsa_message_body_new(message);
2508     date = libbalsa_message_date_to_utf8(for_msg, balsa_app.date_string);
2509     dummy = internet_address_list_to_string(for_msg->headers->to_list, FALSE);
2510     body->buffer = g_strdup_printf(
2511         "The message sent on %s to %s with subject \"%s\" has been displayed.\n"
2512         "There is no guarantee that the message has been read or understood.\n\n",
2513         date, dummy, LIBBALSA_MESSAGE_GET_SUBJECT(for_msg));
2514     g_free (date);
2515     g_free (dummy);
2516     libbalsa_utf8_sanitize(&body->buffer, balsa_app.convert_unknown_8bit, NULL);
2517     if (balsa_app.wordwrap)
2518         libbalsa_wrap_string(body->buffer, balsa_app.wraplength);
2519     dummy = (gchar *)g_mime_charset_best(body->buffer, strlen(body->buffer));
2520     body->charset = g_strdup(dummy ? dummy : "us-ascii");
2521     libbalsa_message_append_part(message, body);
2522 
2523     /* the second part is a rfc3798 compliant
2524        message/disposition-notification */
2525     body = libbalsa_message_body_new(message);
2526     report = g_string_new("");
2527     uname(&uts_name);
2528     g_string_printf(report, "Reporting-UA: %s; " PACKAGE " " VERSION "\n",
2529 		    uts_name.nodename);
2530     /* see rfc 3798, sections 2.3 and 3.2.3 */
2531     if ((original_rcpt =
2532 	 libbalsa_message_get_user_header(for_msg, "original-recipient")))
2533 	g_string_append_printf(report, "Original-Recipient: %s\n",
2534 			       original_rcpt);
2535     g_string_append_printf(report, "Final-Recipient: rfc822; %s\n",
2536                            INTERNET_ADDRESS_MAILBOX(balsa_app.
2537                                                     current_ident->ia)->
2538                            addr);
2539     if (for_msg->message_id)
2540         g_string_append_printf(report, "Original-Message-ID: <%s>\n",
2541                                for_msg->message_id);
2542     g_string_append_printf(report,
2543 			   "Disposition: %s-action/MDN-sent-%sly; displayed",
2544 			   manual ? "manual" : "automatic",
2545 			   manual ? "manual" : "automatical");
2546     body->buffer = report->str;
2547     g_string_free(report, FALSE);
2548     body->content_type = g_strdup("message/disposition-notification");
2549     body->charset = g_strdup ("US-ASCII");
2550     libbalsa_message_append_part(message, body);
2551     return message;
2552 }
2553 
2554 static GtkWidget *
create_mdn_dialog(GtkWindow * parent,gchar * sender,gchar * mdn_to_address,LibBalsaMessage * send_msg,LibBalsaIdentity * mdn_ident)2555 create_mdn_dialog(GtkWindow *parent, gchar * sender, gchar * mdn_to_address,
2556                   LibBalsaMessage * send_msg, LibBalsaIdentity *mdn_ident)
2557 {
2558     GtkWidget *mdn_dialog;
2559 
2560     mdn_dialog =
2561         gtk_message_dialog_new(parent,
2562                                GTK_DIALOG_DESTROY_WITH_PARENT,
2563                                GTK_MESSAGE_QUESTION,
2564                                GTK_BUTTONS_YES_NO,
2565                                _("The sender of this mail, %s, "
2566                                  "requested \n"
2567                                  "a Message Disposition Notification"
2568                                  "(MDN) to be returned to `%s'.\n"
2569                                  "Do you want to send "
2570                                  "this notification?"),
2571                                sender, mdn_to_address);
2572 #if HAVE_MACOSX_DESKTOP
2573     libbalsa_macosx_menu_for_parent(mdn_dialog, parent);
2574 #endif
2575     gtk_window_set_title(GTK_WINDOW(mdn_dialog), _("Reply to MDN?"));
2576     g_object_set_data(G_OBJECT(mdn_dialog), "balsa-send-msg", send_msg);
2577     g_object_set_data(G_OBJECT(mdn_dialog), "mdn-ident",
2578                       g_object_ref(mdn_ident));
2579     g_signal_connect(G_OBJECT(mdn_dialog), "response",
2580                      G_CALLBACK(mdn_dialog_response), NULL);
2581 
2582     return mdn_dialog;
2583 }
2584 
2585 static void
mdn_dialog_response(GtkWidget * dialog,gint response,gpointer user_data)2586 mdn_dialog_response(GtkWidget * dialog, gint response, gpointer user_data)
2587 {
2588     LibBalsaMessage *send_msg =
2589         LIBBALSA_MESSAGE(g_object_get_data(G_OBJECT(dialog),
2590                                            "balsa-send-msg"));
2591     LibBalsaIdentity *mdn_ident =
2592         LIBBALSA_IDENTITY(g_object_get_data(G_OBJECT(dialog), "mdn-ident"));
2593     GError * error = NULL;
2594     LibBalsaMsgCreateResult result;
2595 
2596     g_return_if_fail(send_msg != NULL);
2597     g_return_if_fail(mdn_ident != NULL);
2598 
2599     if (response == GTK_RESPONSE_YES) {
2600 #if ENABLE_ESMTP
2601         result = libbalsa_message_send(send_msg, balsa_app.outbox, NULL,
2602 				       balsa_find_sentbox_by_url,
2603 				       mdn_ident->smtp_server,
2604 				       TRUE, balsa_app.debug, &error);
2605 #else
2606         result = libbalsa_message_send(send_msg, balsa_app.outbox, NULL,
2607 				       balsa_find_sentbox_by_url,
2608 				       TRUE, balsa_app.debug, &error);
2609 #endif
2610         if (result != LIBBALSA_MESSAGE_CREATE_OK)
2611             libbalsa_information(LIBBALSA_INFORMATION_ERROR,
2612                                  _("Sending the disposition notification failed: %s"),
2613                                  error ? error->message : "?");
2614         if (error)
2615             g_error_free(error);
2616     }
2617     g_object_unref(G_OBJECT(send_msg));
2618     g_object_unref(G_OBJECT(mdn_ident));
2619     gtk_widget_destroy(dialog);
2620 }
2621 
2622 #ifdef HAVE_HTML_WIDGET
2623 /* Does the current part support zoom? */
2624 gboolean
balsa_message_can_zoom(BalsaMessage * bm)2625 balsa_message_can_zoom(BalsaMessage * bm)
2626 {
2627     return bm->current_part
2628         && libbalsa_html_can_zoom(bm->current_part->mime_widget->widget);
2629 }
2630 
2631 /* Zoom an html item. */
2632 void
balsa_message_zoom(BalsaMessage * bm,gint in_out)2633 balsa_message_zoom(BalsaMessage * bm, gint in_out)
2634 {
2635     gint zoom;
2636 
2637     if (!balsa_message_can_zoom(bm))
2638         return;
2639 
2640     zoom =
2641        GPOINTER_TO_INT(g_object_get_data
2642                        (G_OBJECT(bm->message), BALSA_MESSAGE_ZOOM_KEY));
2643      if (in_out)
2644        zoom += in_out;
2645      else
2646        zoom = 0;
2647      g_object_set_data(G_OBJECT(bm->message), BALSA_MESSAGE_ZOOM_KEY,
2648                      GINT_TO_POINTER(zoom));
2649 
2650      libbalsa_html_zoom(bm->current_part->mime_widget->widget, in_out);
2651 
2652 }
2653 #endif /* HAVE_HTML_WIDGET */
2654 
2655 
2656 #ifdef HAVE_GPGME
2657 /*
2658  * collected GPG(ME) helper stuff to make the source more readable
2659  */
2660 
2661 
2662 /*
2663  * Calculate and return a "worst case" summary of all checked signatures in a
2664  * message.
2665  */
2666 static LibBalsaMsgProtectState
balsa_message_scan_signatures(LibBalsaMessageBody * body,LibBalsaMessage * message)2667 balsa_message_scan_signatures(LibBalsaMessageBody *body,
2668                               LibBalsaMessage * message)
2669 {
2670     LibBalsaMsgProtectState result = LIBBALSA_MSG_PROTECT_NONE;
2671 
2672     g_return_val_if_fail(message->headers != NULL, result);
2673 
2674     while (body) {
2675 	LibBalsaMsgProtectState this_part_state =
2676 	    libbalsa_message_body_protect_state(body);
2677 
2678 	/* remember: greater means worse... */
2679 	if (this_part_state > result)
2680 	    result = this_part_state;
2681 
2682         /* scan embedded messages */
2683         if (body->parts) {
2684             LibBalsaMsgProtectState sub_result =
2685                 balsa_message_scan_signatures(body->parts, message);
2686 
2687             if (sub_result >= result)
2688                 result = sub_result;
2689         }
2690 
2691 	body = body->next;
2692     }
2693 
2694     return result;
2695 }
2696 
2697 
2698 /*
2699  * Check if body (of type content_type) is signed and/or encrypted and return
2700  * the proper icon in this case, and NULL otherwise. If the part is signed,
2701  * replace *icon-title by the signature status.
2702  */
2703 static GdkPixbuf *
get_crypto_content_icon(LibBalsaMessageBody * body,const gchar * content_type,gchar ** icon_title)2704 get_crypto_content_icon(LibBalsaMessageBody * body, const gchar * content_type,
2705 			gchar ** icon_title)
2706 {
2707     GdkPixbuf *icon;
2708     gchar * new_title;
2709     const gchar * icon_name;
2710 
2711     if ((libbalsa_message_body_protection(body) &
2712          (LIBBALSA_PROTECT_ENCRYPT | LIBBALSA_PROTECT_ERROR)) ==
2713         LIBBALSA_PROTECT_ENCRYPT)
2714         return NULL;
2715 
2716     icon_name = balsa_mime_widget_signature_icon_name(libbalsa_message_body_protect_state(body));
2717     if (!icon_name)
2718         return NULL;
2719     icon =
2720         gtk_widget_render_icon_pixbuf(GTK_WIDGET(balsa_app.main_window),
2721                                       icon_name,
2722                                       GTK_ICON_SIZE_LARGE_TOOLBAR);
2723     if (!icon_title)
2724         return icon;
2725 
2726     if (*icon_title &&
2727 	g_ascii_strcasecmp(content_type, "application/pgp-signature") &&
2728 	g_ascii_strcasecmp(content_type, "application/pkcs7-signature") &&
2729 	g_ascii_strcasecmp(content_type, "application/x-pkcs7-signature"))
2730 	new_title = g_strconcat(*icon_title, "; ",
2731 				libbalsa_gpgme_sig_protocol_name(body->sig_info->protocol),
2732 				libbalsa_gpgme_sig_stat_to_gchar(body->sig_info->status),
2733 				NULL);
2734     else
2735 	new_title = g_strconcat(libbalsa_gpgme_sig_protocol_name(body->sig_info->protocol),
2736 				libbalsa_gpgme_sig_stat_to_gchar(body->sig_info->status),
2737 				NULL);
2738 
2739     if (*icon_title)
2740 	g_free(*icon_title);
2741     *icon_title = new_title;
2742 
2743     return icon;
2744 }
2745 
2746 
2747 /* helper functions for libbalsa_msg_perform_crypto below */
2748 
2749 /* this is a helper structure to simplify passing a set of parameters */
2750 typedef struct {
2751     LibBalsaChkCryptoMode chk_mode;      /* current check mode */
2752     gboolean no_mp_signed;               /* don't check multipart/signed */
2753     guint max_ref;                       /* maximum allowed ref count */
2754     gchar * sender;                      /* shortcut to sender */
2755     gchar * subject;                     /* shortcut to subject */
2756 } chk_crypto_t;
2757 
2758 
2759 /*
2760  * check if body is a multipart/encrypted (RFC 3156) or a signed or encrypted
2761  * application/pkcs7-mime type, try to decrypt it and return the new body
2762  * chain.
2763  */
2764 static LibBalsaMessageBody *
libbalsa_msg_try_decrypt(LibBalsaMessage * message,LibBalsaMessageBody * body,chk_crypto_t * chk_crypto)2765 libbalsa_msg_try_decrypt(LibBalsaMessage * message, LibBalsaMessageBody * body,
2766 			 chk_crypto_t * chk_crypto)
2767 {
2768     gchar * mime_type;
2769     LibBalsaMessageBody * this_body;
2770 
2771     /* Check for multipart/encrypted or application/pkcs7-mime parts and
2772        replace this_body by it's decrypted content. Remember that there may be
2773        weird cases where such parts are nested, so do it in a loop. */
2774     this_body = body;
2775     mime_type = libbalsa_message_body_get_mime_type(this_body);
2776     while (!g_ascii_strcasecmp(mime_type, "multipart/encrypted") ||
2777 	   !g_ascii_strcasecmp(mime_type, "application/pkcs7-mime")) {
2778 	gint encrres;
2779 
2780 	/* FIXME: not checking for body_ref > 1 (or > 2 when re-checking, which
2781 	 * adds an extra ref) leads to a crash if we have both the encrypted and
2782 	 * the unencrypted version open as the body chain of the first one will be
2783 	 * unref'd. */
2784 	if (message->body_ref > chk_crypto->max_ref) {
2785 	    if (chk_crypto->chk_mode == LB_MAILBOX_CHK_CRYPT_ALWAYS) {
2786 		libbalsa_information
2787 		    (LIBBALSA_INFORMATION_ERROR,
2788 		     _("The decryption cannot be performed because this message "
2789 		       "is displayed more than once.\n"
2790 		       "Please close the other instances of this message and try "
2791 		       "again."));
2792 		/* downgrade the check mode to avoid multiple errors popping up */
2793 		chk_crypto->chk_mode = LB_MAILBOX_CHK_CRYPT_MAYBE;
2794 	    }
2795 	    return body;
2796 	}
2797 
2798 	/* check if the gmime part is present and force loading it if we are
2799 	   in "always" mode */
2800 	if (!this_body->mime_part) {
2801             GError *err = NULL;
2802 	    if (chk_crypto->chk_mode != LB_MAILBOX_CHK_CRYPT_ALWAYS)
2803 		return this_body;
2804 
2805 	    /* force loading the missing part */
2806 	    if(!libbalsa_mailbox_get_message_part(message, this_body, &err)) {
2807 		libbalsa_information
2808 		    (LIBBALSA_INFORMATION_ERROR,
2809                      _("Parsing a message part failed: %s"),
2810                      err ? err->message : _("Possible disk space problem."));
2811                 g_clear_error(&err);
2812                 /* There is nothing more we can do... */
2813                 return body;
2814             }
2815 	}
2816 
2817 	encrres = libbalsa_message_body_protection(this_body);
2818 
2819 	if (encrres & LIBBALSA_PROTECT_ENCRYPT) {
2820 	    if (encrres & LIBBALSA_PROTECT_ERROR) {
2821 		libbalsa_information
2822 		    (chk_crypto->chk_mode == LB_MAILBOX_CHK_CRYPT_ALWAYS ?
2823 		     LIBBALSA_INFORMATION_ERROR : LIBBALSA_INFORMATION_MESSAGE,
2824                      _("The message sent by %s with subject \"%s\" contains "
2825                        "an encrypted part, but it's structure is invalid."),
2826 		     chk_crypto->sender, chk_crypto->subject);
2827             } else if (encrres & LIBBALSA_PROTECT_RFC3156) {
2828                 if (!balsa_app.has_openpgp)
2829                     libbalsa_information
2830                         (chk_crypto->chk_mode == LB_MAILBOX_CHK_CRYPT_ALWAYS ?
2831 			 LIBBALSA_INFORMATION_WARNING : LIBBALSA_INFORMATION_MESSAGE,
2832                          _("The message sent by %s with subject \"%s\" "
2833                            "contains a PGP encrypted part, but this "
2834                            "crypto protocol is not available."),
2835                          chk_crypto->sender, chk_crypto->subject);
2836                 else
2837                     this_body =
2838                         libbalsa_body_decrypt(this_body,
2839                                               GPGME_PROTOCOL_OpenPGP, NULL);
2840             } else if (encrres & LIBBALSA_PROTECT_SMIMEV3) {
2841                 if (!balsa_app.has_smime)
2842                     libbalsa_information
2843                         (chk_crypto->chk_mode == LB_MAILBOX_CHK_CRYPT_ALWAYS ?
2844 			 LIBBALSA_INFORMATION_WARNING : LIBBALSA_INFORMATION_MESSAGE,
2845                          _("The message sent by %s with subject \"%s\" "
2846                            "contains a S/MIME encrypted part, but this "
2847                            "crypto protocol is not available."),
2848                          chk_crypto->sender, chk_crypto->subject);
2849                 else
2850                     this_body =
2851                         libbalsa_body_decrypt(this_body, GPGME_PROTOCOL_CMS,
2852 					      NULL);
2853             }
2854         }
2855 
2856 	/* has been decrypted? - eject if not, otherwise... */
2857 	if (!this_body->was_encrypted)
2858 	    return this_body;
2859 
2860 	/* ...check decrypted content again... */
2861 	g_free(mime_type);
2862 	mime_type = libbalsa_message_body_get_mime_type(this_body);
2863     }
2864     g_free(mime_type);
2865 
2866     return this_body;
2867 }
2868 
2869 
2870 /*
2871  * Treatment of a multipart/signed body with protocol protocol.
2872  */
2873 #define BALSA_MESSAGE_SIGNED_NOTIFIED "balsa-message-signed-notified"
2874 static void
libbalsa_msg_try_mp_signed(LibBalsaMessage * message,LibBalsaMessageBody * body,chk_crypto_t * chk_crypto)2875 libbalsa_msg_try_mp_signed(LibBalsaMessage * message, LibBalsaMessageBody *body,
2876 			   chk_crypto_t * chk_crypto)
2877 {
2878     gint signres;
2879 
2880     if (chk_crypto->no_mp_signed)
2881 	return;
2882 
2883     /* check if the gmime part is present and force loading it if we are in
2884        "always" mode */
2885     if (!body->mime_part) {
2886         GError *err = NULL;
2887 	if (chk_crypto->chk_mode != LB_MAILBOX_CHK_CRYPT_ALWAYS)
2888 	    return;
2889 
2890 	/* force loading the missing part */
2891         if(!libbalsa_mailbox_get_message_part(message, body, &err)) {
2892             libbalsa_information
2893                 (LIBBALSA_INFORMATION_ERROR,
2894                  _("Parsing a message part failed: %s"),
2895                  err ? err->message : _("Possible disk space problem."));
2896             g_clear_error(&err);
2897             /* There is nothing more we can do... */
2898             return;
2899         }
2900     }
2901 
2902     /* check which type of protection we've got */
2903     signres = libbalsa_message_body_protection(body);
2904     if (!(signres & LIBBALSA_PROTECT_SIGN))
2905 	return;
2906 
2907     /* eject if the structure is broken */
2908     if (signres & LIBBALSA_PROTECT_ERROR) {
2909 	libbalsa_information
2910 	    (chk_crypto->chk_mode == LB_MAILBOX_CHK_CRYPT_ALWAYS ?
2911 	     LIBBALSA_INFORMATION_ERROR : LIBBALSA_INFORMATION_MESSAGE,
2912 	     _("The message sent by %s with subject \"%s\" contains a signed "
2913 	       "part, but its structure is invalid. The signature, if there "
2914 	       "is any, cannot be checked."),
2915 	     chk_crypto->sender, chk_crypto->subject);
2916 	return;
2917     }
2918 
2919     /* check for an unsupported protocol */
2920     if (((signres & LIBBALSA_PROTECT_RFC3156) && !balsa_app.has_openpgp) ||
2921 	((signres & LIBBALSA_PROTECT_SMIMEV3) && !balsa_app.has_smime)) {
2922 	libbalsa_information
2923 	    (chk_crypto->chk_mode == LB_MAILBOX_CHK_CRYPT_ALWAYS ?
2924 	     LIBBALSA_INFORMATION_WARNING : LIBBALSA_INFORMATION_MESSAGE,
2925 	     _("The message sent by %s with subject \"%s\" contains a %s "
2926 	       "signed part, but this crypto protocol is not available."),
2927 	     chk_crypto->sender, chk_crypto->subject,
2928 	     signres & LIBBALSA_PROTECT_RFC3156 ? _("PGP") : _("S/MIME"));
2929 	return;
2930     }
2931 
2932     /* force creating the protection info */
2933     if (body->parts->next->sig_info) {
2934 	g_object_unref(body->parts->next->sig_info);
2935 	body->parts->next->sig_info = NULL;
2936     }
2937     if (!libbalsa_body_check_signature(body,
2938 				       signres & LIBBALSA_PROTECT_RFC3156 ?
2939 				       GPGME_PROTOCOL_OpenPGP : GPGME_PROTOCOL_CMS))
2940 	return;
2941 
2942     /* evaluate the result */
2943     if (g_object_get_data(G_OBJECT(message), BALSA_MESSAGE_SIGNED_NOTIFIED))
2944         return;
2945     g_object_set_data(G_OBJECT(message), BALSA_MESSAGE_SIGNED_NOTIFIED,
2946                       GUINT_TO_POINTER(TRUE));
2947 
2948     if (body->parts->next->sig_info) {
2949 	switch (libbalsa_message_body_protect_state(body->parts->next)) {
2950 	case LIBBALSA_MSG_PROTECT_SIGN_GOOD:
2951 	    libbalsa_information(LIBBALSA_INFORMATION_DEBUG,
2952 				 _("Detected a good signature"));
2953 	    break;
2954 	case LIBBALSA_MSG_PROTECT_SIGN_NOTRUST:
2955 	    if (body->parts->next->sig_info->protocol == GPGME_PROTOCOL_CMS)
2956 		libbalsa_information
2957 		    (LIBBALSA_INFORMATION_MESSAGE,
2958 		     _("Detected a good signature with insufficient "
2959 		       "validity"));
2960 	    else
2961 		libbalsa_information
2962 		    (LIBBALSA_INFORMATION_MESSAGE,
2963 		     _("Detected a good signature with insufficient "
2964 		       "validity/trust"));
2965 	    break;
2966 	case LIBBALSA_MSG_PROTECT_SIGN_BAD:
2967             libbalsa_information
2968 		(chk_crypto->chk_mode == LB_MAILBOX_CHK_CRYPT_ALWAYS ?
2969 		 LIBBALSA_INFORMATION_ERROR : LIBBALSA_INFORMATION_MESSAGE,
2970 		 _("Checking the signature of the message sent by %s with "
2971 		   "subject \"%s\" returned:\n%s"),
2972 		 chk_crypto->sender, chk_crypto->subject,
2973 		 libbalsa_gpgme_sig_stat_to_gchar(body->parts->next->sig_info->status));
2974 	    break;
2975 	default:
2976 	    break;
2977         }
2978     } else
2979 	libbalsa_information
2980 	    (chk_crypto->chk_mode == LB_MAILBOX_CHK_CRYPT_ALWAYS ?
2981 	     LIBBALSA_INFORMATION_ERROR : LIBBALSA_INFORMATION_MESSAGE,
2982 	     _("Checking the signature of the message sent by %s with subject "
2983 	       "\"%s\" failed with an error!"),
2984 	     chk_crypto->sender, chk_crypto->subject);
2985 }
2986 
2987 
2988 /*
2989  * Check if body is has some RFC 2440 protection and try to decrypt and/or
2990  * verify it.
2991  */
2992 static void
libbalsa_msg_part_2440(LibBalsaMessage * message,LibBalsaMessageBody * body,chk_crypto_t * chk_crypto)2993 libbalsa_msg_part_2440(LibBalsaMessage * message, LibBalsaMessageBody * body,
2994 		       chk_crypto_t * chk_crypto)
2995 {
2996     gpgme_error_t sig_res;
2997     GMimePartRfc2440Mode rfc2440mode;
2998 
2999     /* multiparts or complete messages can not be RFC 2440 protected */
3000     if (body->body_type == LIBBALSA_MESSAGE_BODY_TYPE_MESSAGE ||
3001 	body->body_type == LIBBALSA_MESSAGE_BODY_TYPE_MULTIPART)
3002 	return;
3003 
3004     if (!body->mime_part) {
3005         GError *err = NULL;
3006 	/* if the user requested to always check everything or if the part is
3007 	   text (and therefore likely to be displayed anyway) force loading
3008 	   the missing part */
3009 	if (chk_crypto->chk_mode == LB_MAILBOX_CHK_CRYPT_MAYBE &&
3010 	    body->body_type != LIBBALSA_MESSAGE_BODY_TYPE_TEXT)
3011 	    return;
3012 
3013         if(!libbalsa_mailbox_get_message_part(message, body, &err)) {
3014             libbalsa_information
3015                 (LIBBALSA_INFORMATION_ERROR,
3016                  _("Parsing a message part failed: %s"),
3017                  err ? err->message : _("Possible disk space problem."));
3018             g_clear_error(&err);
3019             return;
3020         }
3021     }
3022 
3023     /* check if this is a RFC2440 part */
3024     if (!GMIME_IS_PART(body->mime_part))
3025 	return;
3026     libbalsa_mailbox_lock_store(body->message->mailbox);
3027     rfc2440mode = g_mime_part_check_rfc2440(GMIME_PART(body->mime_part));
3028     libbalsa_mailbox_unlock_store(body->message->mailbox);
3029 
3030     /* if not, or if we have more than one instance of this message open, eject
3031        (see remark for libbalsa_msg_try_decrypt above) - remember that
3032        libbalsa_rfc2440_verify would also replace the stream by the "decrypted"
3033        (i.e. RFC2440 stuff removed) one! */
3034     if (rfc2440mode == GMIME_PART_RFC2440_NONE)
3035 	return;
3036     if (message->body_ref > chk_crypto->max_ref) {
3037 	if (chk_crypto->chk_mode == LB_MAILBOX_CHK_CRYPT_ALWAYS) {
3038             libbalsa_information
3039                 (LIBBALSA_INFORMATION_ERROR, "%s\n%s",
3040 		 rfc2440mode == GMIME_PART_RFC2440_ENCRYPTED ?
3041                  _("The decryption cannot be performed because "
3042 		   "this message is displayed more than once.") :
3043                  _("The signature check and removal of the OpenPGP armor "
3044 		   "cannot be performed because "
3045 		   "this message is displayed more than once."),
3046                  _("Please close the other instances of this message "
3047 		   "and try again."));
3048 	    /* downgrade the check mode to avoid multiple errors popping up */
3049 	    chk_crypto->chk_mode = LB_MAILBOX_CHK_CRYPT_MAYBE;
3050 	}
3051         return;
3052     }
3053 
3054     /* do the rfc2440 stuff */
3055     libbalsa_mailbox_lock_store(body->message->mailbox);
3056     if (rfc2440mode == GMIME_PART_RFC2440_SIGNED)
3057         sig_res =
3058             libbalsa_rfc2440_verify(GMIME_PART(body->mime_part),
3059 				    &body->sig_info);
3060     else {
3061         sig_res =
3062             libbalsa_rfc2440_decrypt(GMIME_PART(body->mime_part),
3063 				     &body->sig_info,
3064 				     NULL);
3065 	body->was_encrypted = (body->sig_info || sig_res == GPG_ERR_NO_ERROR);
3066 	if (sig_res == GPG_ERR_NO_ERROR) {
3067 	    /* decrypting may change the charset, so be sure to use the one
3068 	       GMimePart reports */
3069 	    g_free(body->charset);
3070 	    body->charset = NULL;
3071 	}
3072     }
3073     libbalsa_mailbox_unlock_store(body->message->mailbox);
3074 
3075     if (body->sig_info && sig_res == GPG_ERR_NO_ERROR) {
3076         if (body->sig_info->validity >= GPGME_VALIDITY_MARGINAL &&
3077             body->sig_info->key->owner_trust >= GPGME_VALIDITY_MARGINAL)
3078             libbalsa_information(LIBBALSA_INFORMATION_DEBUG,
3079                                  _("Detected a good signature"));
3080         else
3081             libbalsa_information
3082 		(LIBBALSA_INFORMATION_MESSAGE,
3083 		 _("Detected a good signature with insufficient "
3084 		   "validity/trust"));
3085     } else if (sig_res != GPG_ERR_NO_ERROR && sig_res != GPG_ERR_CANCELED)
3086 	libbalsa_information
3087 	    (chk_crypto->chk_mode == LB_MAILBOX_CHK_CRYPT_ALWAYS ?
3088 	     LIBBALSA_INFORMATION_ERROR : LIBBALSA_INFORMATION_MESSAGE,
3089 	     _("Checking the signature of the message sent by %s with "
3090 	       "subject \"%s\" returned:\n%s"),
3091 	     chk_crypto->sender, chk_crypto->subject,
3092 	     libbalsa_gpgme_sig_stat_to_gchar(sig_res));
3093 }
3094 
3095 
3096 /*
3097  * Scan a body chain for RFC 2440, RFC 2633 and RFC 3156 encrypted and/or
3098  * signed parts. The function is called recursively for nested stuff.
3099  */
3100 static LibBalsaMessageBody *
libbalsa_msg_perform_crypto_real(LibBalsaMessage * message,LibBalsaMessageBody * body,chk_crypto_t * chk_crypto)3101 libbalsa_msg_perform_crypto_real(LibBalsaMessage * message,
3102 				 LibBalsaMessageBody * body,
3103 				 chk_crypto_t * chk_crypto)
3104 {
3105     gchar * mime_type;
3106     LibBalsaMessageBody * chk_body;
3107 
3108     /* First try do decrypt RFC 3156 multipart/encrypted or any RFC 2633
3109        application/pkcs7-mime stuff. */
3110     body = libbalsa_msg_try_decrypt(message, body, chk_crypto);
3111 
3112     /* Check for multipart/signed and check the signature. */
3113     mime_type = libbalsa_message_body_get_mime_type(body);
3114     if (!g_ascii_strcasecmp(mime_type, "multipart/signed"))
3115 	libbalsa_msg_try_mp_signed(message, body, chk_crypto);
3116     g_free(mime_type);
3117 
3118     /* loop over the parts, checking for RFC 2440 stuff, but ignore
3119        application/octet-stream which might be a detached encrypted part
3120        as well as all detached signatures */
3121     chk_body = body;
3122     while (chk_body) {
3123 	mime_type = libbalsa_message_body_get_mime_type(chk_body);
3124 
3125 	if (g_ascii_strcasecmp(mime_type, "application/octet-stream") &&
3126 	    g_ascii_strcasecmp(mime_type, "application/pkcs7-signature") &&
3127 	    g_ascii_strcasecmp(mime_type, "application/x-pkcs7-signature") &&
3128 	    g_ascii_strcasecmp(mime_type, "application/pgp-signature"))
3129 	    libbalsa_msg_part_2440(message, chk_body, chk_crypto);
3130 	g_free(mime_type);
3131 
3132 	/* check nested stuff */
3133 	if (chk_body->parts)
3134 	    chk_body->parts =
3135 		libbalsa_msg_perform_crypto_real(message, chk_body->parts,
3136 						 chk_crypto);
3137 
3138 	chk_body = chk_body->next;
3139     }
3140 
3141     return body;
3142 }
3143 
3144 
3145 /*
3146  * Launches a scan of message with chk_mode and a maximum reference of max_ref.
3147  * If no_mp_signed is TRUE, multipart/signed parts are ignored, i.e. only
3148  * encrypted (either "really" encrypted or armored) parts are used. This is
3149  * useful e.g. for replying.
3150  */
3151 void
balsa_message_perform_crypto(LibBalsaMessage * message,LibBalsaChkCryptoMode chk_mode,gboolean no_mp_signed,guint max_ref)3152 balsa_message_perform_crypto(LibBalsaMessage * message,
3153 			     LibBalsaChkCryptoMode chk_mode,
3154 			     gboolean no_mp_signed, guint max_ref)
3155 {
3156     chk_crypto_t chk_crypto;
3157 
3158     if (!message->body_list)
3159 	return;
3160 
3161     /* check if the user requested to ignore any crypto stuff */
3162     if (chk_mode == LB_MAILBOX_CHK_CRYPT_NEVER)
3163 	return;
3164 
3165     /* set up... */
3166     chk_crypto.chk_mode = chk_mode;
3167     chk_crypto.no_mp_signed = no_mp_signed;
3168     chk_crypto.max_ref = max_ref;
3169     chk_crypto.sender = balsa_message_sender_to_gchar(message->headers->from, -1);
3170     chk_crypto.subject = g_strdup(LIBBALSA_MESSAGE_GET_SUBJECT(message));
3171     libbalsa_utf8_sanitize(&chk_crypto.subject, balsa_app.convert_unknown_8bit,
3172 			   NULL);
3173 
3174     /* do the real work */
3175     message->body_list =
3176 	libbalsa_msg_perform_crypto_real(message, message->body_list,
3177 					 &chk_crypto);
3178 
3179     /* clean up */
3180     g_free(chk_crypto.subject);
3181     g_free(chk_crypto.sender);
3182 }
3183 
3184 
3185 /*
3186  * Callback for the "Check Crypto" button in the message's top-level headers.
3187  * It works roughly like balsa_message_set, but with less overhead and with
3188  * "check always" mode. Note that this routine adds a temporary reference to
3189  * the message.
3190  */
3191 static void
message_recheck_crypto_cb(GtkWidget * button,BalsaMessage * bm)3192 message_recheck_crypto_cb(GtkWidget * button, BalsaMessage * bm)
3193 {
3194     LibBalsaMsgProtectState prot_state;
3195     LibBalsaMessage * message;
3196     GtkTreeIter iter;
3197     BalsaPartInfo * info;
3198     gboolean has_focus = bm->focus_state != BALSA_MESSAGE_FOCUS_STATE_NO;
3199 
3200     g_return_if_fail(bm != NULL);
3201 
3202     message = bm->message;
3203     g_return_if_fail(message != NULL);
3204 
3205     select_part(bm, NULL);
3206     balsa_message_clear_tree(bm);
3207 
3208     g_object_ref(G_OBJECT(message));
3209     if (!libbalsa_message_body_ref(message, TRUE, TRUE)) {
3210 	g_object_unref(G_OBJECT(message));
3211         return;
3212     }
3213 
3214     g_object_set_data(G_OBJECT(message), BALSA_MESSAGE_SIGNED_NOTIFIED, NULL);
3215     balsa_message_perform_crypto(message, LB_MAILBOX_CHK_CRYPT_ALWAYS, FALSE, 2);
3216 
3217     /* calculate the signature summary state */
3218     prot_state =
3219         balsa_message_scan_signatures(message->body_list, message);
3220 
3221     /* update the icon if necessary */
3222     if (message->prot_state != prot_state)
3223         message->prot_state = prot_state;
3224 
3225     /* may update the icon */
3226     libbalsa_mailbox_msgno_update_attach(bm->message->mailbox,
3227 					 bm->message->msgno, bm->message);
3228 
3229     display_headers(bm);
3230     display_content(bm);
3231 
3232 #if defined(ENABLE_TOUCH_UI)
3233     /* hide tabs so that they do not confuse keyboard navigation.
3234      * This could probably be a configuration option. */
3235     gtk_notebook_set_show_tabs(GTK_NOTEBOOK(bm), FALSE);
3236 #else
3237     gtk_notebook_set_show_tabs(GTK_NOTEBOOK(bm), bm->info_count > 1);
3238 #endif /* ENABLE_TOUCH_UI */
3239     gtk_notebook_set_current_page(GTK_NOTEBOOK(bm), 0);
3240 
3241     if (!gtk_tree_model_get_iter_first (gtk_tree_view_get_model(GTK_TREE_VIEW(bm->treeview)),
3242                                         &iter)) {
3243 	libbalsa_message_body_unref(message);
3244 	g_object_unref(G_OBJECT(message));
3245         return;
3246     }
3247 
3248     info =
3249         tree_next_valid_part_info(gtk_tree_view_get_model(GTK_TREE_VIEW(bm->treeview)),
3250                                   &iter);
3251     select_part(bm, info);
3252     if (info)
3253         g_object_unref(info);
3254 
3255     /* restore keyboard focus to the content, if it was there before */
3256     if (has_focus)
3257         balsa_message_grab_focus(bm);
3258 
3259     libbalsa_message_body_unref(message);
3260     g_object_unref(G_OBJECT(message));
3261 }
3262 
3263 #endif  /* HAVE_GPGME */
3264 
3265 /*
3266  * Public method for find-in-message.
3267  */
3268 
3269 void
balsa_message_find_in_message(BalsaMessage * bm)3270 balsa_message_find_in_message(BalsaMessage * bm)
3271 {
3272     GtkWidget *w;
3273 
3274     if (bm->current_part
3275         && (w = bm->current_part->mime_widget->widget)
3276         && (GTK_IS_TEXT_VIEW(w)
3277 #ifdef HAVE_HTML_WIDGET
3278             || libbalsa_html_can_search(w)
3279 #endif                          /* HAVE_HTML_WIDGET */
3280             )) {
3281         if (GTK_IS_TEXT_VIEW(w)) {
3282             GtkTextView *text_view = (GtkTextView *) w;
3283             GtkTextBuffer *buffer = gtk_text_view_get_buffer(text_view);
3284 
3285             gtk_text_buffer_get_start_iter(buffer, &bm->find_iter);
3286         }
3287 
3288         bm->find_forward = TRUE;
3289         gtk_entry_set_text(GTK_ENTRY(bm->find_entry), "");
3290         g_signal_connect_swapped(gtk_widget_get_toplevel(GTK_WIDGET(bm)),
3291                                  "key-press-event",
3292                                  G_CALLBACK(bm_find_pass_to_entry), bm);
3293 
3294         bm_find_set_status(bm, BM_FIND_STATUS_INIT);
3295 
3296         gtk_widget_show_all(bm->find_bar);
3297         if (gtk_widget_get_window(bm->find_entry))
3298             gtk_widget_grab_focus(bm->find_entry);
3299     }
3300 }
3301