1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2020 the Claws Mail team and Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #include "claws-features.h"
22 #endif
23 
24 #include "defs.h"
25 
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <gdk/gdk.h>
29 #include <gdk/gdkkeysyms.h>
30 #include <gtk/gtk.h>
31 #include <stdio.h>
32 #include <ctype.h>
33 #include <string.h>
34 #include <stdlib.h>
35 #include <errno.h>
36 #if HAVE_SYS_WAIT_H
37 #include <sys/wait.h>
38 #endif
39 
40 #include "main.h"
41 #include "summaryview.h"
42 #include "procheader.h"
43 #include "prefs_common.h"
44 #include "codeconv.h"
45 #include "utils.h"
46 #include "gtkutils.h"
47 #include "procmime.h"
48 #include "html.h"
49 #include "enriched.h"
50 #include "compose.h"
51 #ifndef USE_ALT_ADDRBOOK
52 	#include "addressbook.h"
53 	#include "addrindex.h"
54 #else
55 	#include "addressbook-dbus.h"
56 	#include "addressadd.h"
57 #endif
58 #include "displayheader.h"
59 #include "account.h"
60 #include "mimeview.h"
61 #include "alertpanel.h"
62 #include "menu.h"
63 #include "image_viewer.h"
64 #include "filesel.h"
65 #include "inputdialog.h"
66 #include "timing.h"
67 #include "tags.h"
68 #include "manage_window.h"
69 #include "folder_item_prefs.h"
70 #include "hooks.h"
71 #include "avatars.h"
72 #include "file-utils.h"
73 
74 static GdkColor quote_colors[3] = {
75 	{(gulong)0, (gushort)0, (gushort)0, (gushort)0},
76 	{(gulong)0, (gushort)0, (gushort)0, (gushort)0},
77 	{(gulong)0, (gushort)0, (gushort)0, (gushort)0}
78 };
79 
80 static GdkColor quote_bgcolors[3] = {
81 	{(gulong)0, (gushort)0, (gushort)0, (gushort)0},
82 	{(gulong)0, (gushort)0, (gushort)0, (gushort)0},
83 	{(gulong)0, (gushort)0, (gushort)0, (gushort)0}
84 };
85 static GdkColor signature_color = {
86 	(gulong)0,
87 	(gushort)0x7fff,
88 	(gushort)0x7fff,
89 	(gushort)0x7fff
90 };
91 
92 static GdkColor uri_color = {
93 	(gulong)0,
94 	(gushort)0,
95 	(gushort)0,
96 	(gushort)0
97 };
98 
99 static GdkColor emphasis_color = {
100 	(gulong)0,
101 	(gushort)0,
102 	(gushort)0,
103 	(gushort)0
104 };
105 
106 static GdkColor diff_added_color = {
107 	(gulong)0,
108 	(gushort)0,
109 	(gushort)0,
110 	(gushort)0
111 };
112 
113 static GdkColor diff_deleted_color = {
114 	(gulong)0,
115 	(gushort)0,
116 	(gushort)0,
117 	(gushort)0
118 };
119 
120 static GdkColor diff_hunk_color = {
121 	(gulong)0,
122 	(gushort)0,
123 	(gushort)0,
124 	(gushort)0
125 };
126 
127 static GdkColor tags_bgcolor = {
128 	(gulong)0,
129 	(gushort)0,
130 	(gushort)0,
131 	(gushort)0
132 };
133 
134 static GdkColor tags_color = {
135 	(gulong)0,
136 	(gushort)0,
137 	(gushort)0,
138 	(gushort)0
139 };
140 
141 static GdkCursor *hand_cursor = NULL;
142 static GdkCursor *text_cursor = NULL;
143 static GdkCursor *watch_cursor= NULL;
144 
145 #define TEXTVIEW_STATUSBAR_PUSH(textview, str)				    \
146 {	if (textview->messageview->statusbar)				    \
147 	gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
148 			   textview->messageview->statusbar_cid, str);	    \
149 }
150 
151 #define TEXTVIEW_STATUSBAR_POP(textview)				   \
152 {	if (textview->messageview->statusbar)				   \
153 	gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
154 			  textview->messageview->statusbar_cid);	   \
155 }
156 
157 static void textview_show_ertf		(TextView	*textview,
158 					 FILE		*fp,
159 					 CodeConverter	*conv);
160 static void textview_add_part		(TextView	*textview,
161 					 MimeInfo	*mimeinfo);
162 static void textview_add_parts		(TextView	*textview,
163 					 MimeInfo	*mimeinfo);
164 static void textview_write_body		(TextView	*textview,
165 					 MimeInfo	*mimeinfo);
166 static void textview_show_html		(TextView	*textview,
167 					 FILE		*fp,
168 					 CodeConverter	*conv);
169 
170 static void textview_write_line		(TextView	*textview,
171 					 const gchar	*str,
172 					 CodeConverter	*conv,
173 					 gboolean	 do_quote_folding);
174 static void textview_write_link		(TextView	*textview,
175 					 const gchar	*str,
176 					 const gchar	*uri,
177 					 CodeConverter	*conv);
178 
179 static GPtrArray *textview_scan_header	(TextView	*textview,
180 					 FILE		*fp);
181 static void textview_show_header	(TextView	*textview,
182 					 GPtrArray	*headers);
183 
184 static gint textview_key_pressed		(GtkWidget	*widget,
185 						 GdkEventKey	*event,
186 						 TextView	*textview);
187 static gboolean textview_motion_notify		(GtkWidget	*widget,
188 						 GdkEventMotion	*motion,
189 						 TextView	*textview);
190 static gboolean textview_leave_notify		(GtkWidget	  *widget,
191 						 GdkEventCrossing *event,
192 						 TextView	  *textview);
193 static gboolean textview_visibility_notify	(GtkWidget	*widget,
194 						 GdkEventVisibility *event,
195 						 TextView	*textview);
196 static void textview_uri_update			(TextView	*textview,
197 						 gint		x,
198 						 gint		y);
199 static gboolean textview_get_uri_range		(TextView	*textview,
200 						 GtkTextIter	*iter,
201 						 GtkTextTag	*tag,
202 						 GtkTextIter	*start_iter,
203 						 GtkTextIter	*end_iter);
204 static ClickableText *textview_get_uri_from_range	(TextView	*textview,
205 						 GtkTextIter	*iter,
206 						 GtkTextTag	*tag,
207 						 GtkTextIter	*start_iter,
208 						 GtkTextIter	*end_iter);
209 static ClickableText *textview_get_uri		(TextView	*textview,
210 						 GtkTextIter	*iter,
211 						 GtkTextTag	*tag);
212 static gboolean textview_uri_button_pressed	(GtkTextTag 	*tag,
213 						 GObject 	*obj,
214 						 GdkEvent 	*event,
215 						 GtkTextIter	*iter,
216 						 TextView 	*textview);
217 
218 static void textview_uri_list_remove_all	(GSList		*uri_list);
219 
220 static void textview_toggle_quote		(TextView 	*textview,
221 						 GSList		*start_list,
222 						 ClickableText 	*uri,
223 						 gboolean	 expand_only);
224 
225 static void open_uri_cb				(GtkAction	*action,
226 						 TextView	*textview);
227 static void copy_uri_cb				(GtkAction	*action,
228 						 TextView	*textview);
229 static void add_uri_to_addrbook_cb 		(GtkAction	*action,
230 						 TextView	*textview);
231 static void reply_to_uri_cb 			(GtkAction	*action,
232 						 TextView	*textview);
233 static void mail_to_uri_cb 			(GtkAction	*action,
234 						 TextView	*textview);
235 static void copy_mail_to_uri_cb			(GtkAction	*action,
236 						 TextView	*textview);
237 static void textview_show_tags(TextView *textview);
238 
239 static GtkActionEntry textview_link_popup_entries[] =
240 {
241 	{"TextviewPopupLink",			NULL, "TextviewPopupLink", NULL, NULL, NULL },
242 	{"TextviewPopupLink/Open",		NULL, N_("_Open in web browser"), NULL, NULL, G_CALLBACK(open_uri_cb) },
243 	{"TextviewPopupLink/Copy",		NULL, N_("Copy this _link"), NULL, NULL, G_CALLBACK(copy_uri_cb) },
244 };
245 
246 static GtkActionEntry textview_mail_popup_entries[] =
247 {
248 	{"TextviewPopupMail",			NULL, "TextviewPopupMail", NULL, NULL, NULL },
249 	{"TextviewPopupMail/Compose",		NULL, N_("Compose _new message"), NULL, NULL, G_CALLBACK(mail_to_uri_cb) },
250 	{"TextviewPopupMail/ReplyTo",		NULL, N_("_Reply to this address"), NULL, NULL, G_CALLBACK(reply_to_uri_cb) },
251 	{"TextviewPopupMail/AddAB",		NULL, N_("Add to _Address book"), NULL, NULL, G_CALLBACK(add_uri_to_addrbook_cb) },
252 	{"TextviewPopupMail/Copy",		NULL, N_("Copy this add_ress"), NULL, NULL, G_CALLBACK(copy_mail_to_uri_cb) },
253 };
254 
scrolled_cb(GtkAdjustment * adj,TextView * textview)255 static void scrolled_cb (GtkAdjustment *adj, TextView *textview)
256 {
257 #ifndef WIDTH
258 #  define WIDTH 48
259 #  define HEIGHT 48
260 #endif
261 	if (textview->image) {
262 		GtkAllocation allocation;
263 		gint x, y, x1;
264 		gtk_widget_get_allocation(textview->text, &allocation);
265 		x1 = allocation.width - WIDTH - 5;
266 		gtk_text_view_buffer_to_window_coords(
267 			GTK_TEXT_VIEW(textview->text),
268 			GTK_TEXT_WINDOW_TEXT, x1, 5, &x, &y);
269 		gtk_text_view_move_child(GTK_TEXT_VIEW(textview->text),
270 			textview->image, x1, y);
271 	}
272 }
273 
textview_size_allocate_cb(GtkWidget * widget,GtkAllocation * allocation,gpointer data)274 static void textview_size_allocate_cb	(GtkWidget	*widget,
275 					 GtkAllocation	*allocation,
276 					 gpointer	 data)
277 {
278 	scrolled_cb(NULL, (TextView *)data);
279 }
280 
textview_create(void)281 TextView *textview_create(void)
282 {
283 	TextView *textview;
284 	GtkWidget *vbox;
285 	GtkWidget *scrolledwin;
286 	GtkWidget *text;
287 	GtkTextBuffer *buffer;
288 	GtkClipboard *clipboard;
289 	GtkAdjustment *adj;
290 
291 	debug_print("Creating text view...\n");
292 	textview = g_new0(TextView, 1);
293 
294 	scrolledwin = gtk_scrolled_window_new(NULL, NULL);
295 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
296 				       GTK_POLICY_AUTOMATIC,
297 				       GTK_POLICY_AUTOMATIC);
298 	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
299 					    GTK_SHADOW_IN);
300 	gtk_widget_set_size_request
301 		(scrolledwin, prefs_common.mainview_width, -1);
302 
303 	/* create GtkSText widgets for single-byte and multi-byte character */
304 	text = gtk_text_view_new();
305 	gtk_widget_add_events(text, GDK_LEAVE_NOTIFY_MASK);
306 	gtk_widget_show(text);
307 	gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
308 	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
309 	gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
310 	gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
311 	gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
312 
313 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
314 	clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
315 	gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
316 
317 	gtk_widget_ensure_style(text);
318 
319 	g_object_ref(scrolledwin);
320 
321 	gtk_container_add(GTK_CONTAINER(scrolledwin), text);
322 
323 	g_signal_connect(G_OBJECT(text), "key-press-event",
324 			 G_CALLBACK(textview_key_pressed), textview);
325 	g_signal_connect(G_OBJECT(text), "motion-notify-event",
326 			 G_CALLBACK(textview_motion_notify), textview);
327 	g_signal_connect(G_OBJECT(text), "leave-notify-event",
328 			 G_CALLBACK(textview_leave_notify), textview);
329 	g_signal_connect(G_OBJECT(text), "visibility-notify-event",
330 			 G_CALLBACK(textview_visibility_notify), textview);
331 	adj = gtk_scrolled_window_get_vadjustment(
332 		GTK_SCROLLED_WINDOW(scrolledwin));
333 	g_signal_connect(G_OBJECT(adj), "value-changed",
334 			 G_CALLBACK(scrolled_cb), textview);
335 	g_signal_connect(G_OBJECT(text), "size_allocate",
336 			 G_CALLBACK(textview_size_allocate_cb),
337 			 textview);
338 
339 
340 	gtk_widget_show(scrolledwin);
341 
342 	vbox = gtk_vbox_new(FALSE, 0);
343 	gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
344 
345 	gtk_widget_show(vbox);
346 
347 
348 	textview->ui_manager = gtk_ui_manager_new();
349 	textview->link_action_group = cm_menu_create_action_group_full(textview->ui_manager,
350 			"TextviewPopupLink",
351 			textview_link_popup_entries,
352 			G_N_ELEMENTS(textview_link_popup_entries), (gpointer)textview);
353 	textview->mail_action_group = cm_menu_create_action_group_full(textview->ui_manager,
354 			"TextviewPopupMail",
355 			textview_mail_popup_entries,
356 			G_N_ELEMENTS(textview_mail_popup_entries), (gpointer)textview);
357 
358 	MENUITEM_ADDUI_MANAGER(textview->ui_manager, "/", "Menus", "Menus", GTK_UI_MANAGER_MENUBAR)
359 	MENUITEM_ADDUI_MANAGER(textview->ui_manager,
360 			"/Menus", "TextviewPopupLink", "TextviewPopupLink", GTK_UI_MANAGER_MENU)
361 	MENUITEM_ADDUI_MANAGER(textview->ui_manager,
362 			"/Menus", "TextviewPopupMail", "TextviewPopupMail", GTK_UI_MANAGER_MENU)
363 
364 	MENUITEM_ADDUI_MANAGER(textview->ui_manager,
365 			"/Menus/TextviewPopupLink", "Open", "TextviewPopupLink/Open", GTK_UI_MANAGER_MENUITEM)
366 	MENUITEM_ADDUI_MANAGER(textview->ui_manager,
367 			"/Menus/TextviewPopupLink", "Copy", "TextviewPopupLink/Copy", GTK_UI_MANAGER_MENUITEM)
368 	MENUITEM_ADDUI_MANAGER(textview->ui_manager,
369 			"/Menus/TextviewPopupMail", "Compose", "TextviewPopupMail/Compose", GTK_UI_MANAGER_MENUITEM)
370 	MENUITEM_ADDUI_MANAGER(textview->ui_manager,
371 			"/Menus/TextviewPopupMail", "ReplyTo", "TextviewPopupMail/ReplyTo", GTK_UI_MANAGER_MENUITEM)
372 	MENUITEM_ADDUI_MANAGER(textview->ui_manager,
373 			"/Menus/TextviewPopupMail", "AddAB", "TextviewPopupMail/AddAB", GTK_UI_MANAGER_MENUITEM)
374 	MENUITEM_ADDUI_MANAGER(textview->ui_manager,
375 			"/Menus/TextviewPopupMail", "Copy", "TextviewPopupMail/Copy", GTK_UI_MANAGER_MENUITEM)
376 
377 	textview->link_popup_menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
378 				gtk_ui_manager_get_widget(textview->ui_manager, "/Menus/TextviewPopupLink")) );
379 	textview->mail_popup_menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
380 				gtk_ui_manager_get_widget(textview->ui_manager, "/Menus/TextviewPopupMail")) );
381 
382 	textview->vbox               = vbox;
383 	textview->scrolledwin        = scrolledwin;
384 	textview->text               = text;
385 	textview->uri_list           = NULL;
386 	textview->body_pos           = 0;
387 	textview->last_buttonpress   = GDK_NOTHING;
388 	textview->image		     = NULL;
389 	return textview;
390 }
391 
textview_create_tags(GtkTextView * text,TextView * textview)392 static void textview_create_tags(GtkTextView *text, TextView *textview)
393 {
394 	GtkTextBuffer *buffer;
395 	GtkTextTag *tag, *qtag;
396 	static PangoFontDescription *font_desc, *bold_font_desc;
397 
398 	if (!font_desc)
399 		font_desc = pango_font_description_from_string
400 			(NORMAL_FONT);
401 
402 	if (!bold_font_desc) {
403 		if (prefs_common.derive_from_normal_font || !BOLD_FONT) {
404 			bold_font_desc = pango_font_description_from_string
405 				(NORMAL_FONT);
406 			pango_font_description_set_weight
407 				(bold_font_desc, PANGO_WEIGHT_BOLD);
408 		} else {
409 			bold_font_desc = pango_font_description_from_string
410 				(BOLD_FONT);
411 		}
412 	}
413 
414 	buffer = gtk_text_view_get_buffer(text);
415 
416 	gtk_text_buffer_create_tag(buffer, "header",
417 				   "pixels-above-lines", 0,
418 				   "pixels-above-lines-set", TRUE,
419 				   "pixels-below-lines", 0,
420 				   "pixels-below-lines-set", TRUE,
421 				   "font-desc", font_desc,
422 				   "left-margin", 3,
423 				   "left-margin-set", TRUE,
424 				   NULL);
425 	gtk_text_buffer_create_tag(buffer, "header_title",
426 				   "font-desc", bold_font_desc,
427 				   NULL);
428 	tag = gtk_text_buffer_create_tag(buffer, "hlink",
429 				   "pixels-above-lines", 0,
430 				   "pixels-above-lines-set", TRUE,
431 				   "pixels-below-lines", 0,
432 				   "pixels-below-lines-set", TRUE,
433 				   "font-desc", font_desc,
434 				   "left-margin", 3,
435 				   "left-margin-set", TRUE,
436 				   "foreground-gdk", &uri_color,
437 				   NULL);
438 	g_signal_connect(G_OBJECT(tag), "event",
439                          G_CALLBACK(textview_uri_button_pressed), textview);
440 	if (prefs_common.enable_bgcolor) {
441 		gtk_text_buffer_create_tag(buffer, "quote0",
442 				"foreground-gdk", &quote_colors[0],
443 				"paragraph-background-gdk", &quote_bgcolors[0],
444 				NULL);
445 		gtk_text_buffer_create_tag(buffer, "quote1",
446 				"foreground-gdk", &quote_colors[1],
447 				"paragraph-background-gdk", &quote_bgcolors[1],
448 				NULL);
449 		gtk_text_buffer_create_tag(buffer, "quote2",
450 				"foreground-gdk", &quote_colors[2],
451 				"paragraph-background-gdk", &quote_bgcolors[2],
452 				NULL);
453 	} else {
454 		gtk_text_buffer_create_tag(buffer, "quote0",
455 				"foreground-gdk", &quote_colors[0],
456 				NULL);
457 		gtk_text_buffer_create_tag(buffer, "quote1",
458 				"foreground-gdk", &quote_colors[1],
459 				NULL);
460 		gtk_text_buffer_create_tag(buffer, "quote2",
461 				"foreground-gdk", &quote_colors[2],
462 				NULL);
463 	}
464 	gtk_text_buffer_create_tag(buffer, "tags",
465 			"foreground-gdk", &tags_color,
466 			"paragraph-background-gdk", &tags_bgcolor,
467 			NULL);
468 	gtk_text_buffer_create_tag(buffer, "emphasis",
469 			"foreground-gdk", &emphasis_color,
470 			NULL);
471 	gtk_text_buffer_create_tag(buffer, "signature",
472 			"foreground-gdk", &signature_color,
473 			NULL);
474 	tag = gtk_text_buffer_create_tag(buffer, "link",
475 			"foreground-gdk", &uri_color,
476 			NULL);
477 	qtag = gtk_text_buffer_create_tag(buffer, "qlink",
478 			NULL);
479 	gtk_text_buffer_create_tag(buffer, "link-hover",
480 			"underline", PANGO_UNDERLINE_SINGLE,
481 			NULL);
482 	gtk_text_buffer_create_tag(buffer, "diff-add",
483 			"foreground-gdk", &diff_added_color,
484 			NULL);
485 	gtk_text_buffer_create_tag(buffer, "diff-del",
486 			"foreground-gdk", &diff_deleted_color,
487 			NULL);
488 	gtk_text_buffer_create_tag(buffer, "diff-add-file",
489 			"foreground-gdk", &diff_added_color,
490 			"weight", PANGO_WEIGHT_BOLD,
491 			NULL);
492 	gtk_text_buffer_create_tag(buffer, "diff-del-file",
493 			"foreground-gdk", &diff_deleted_color,
494 			"weight", PANGO_WEIGHT_BOLD,
495 			NULL);
496 	gtk_text_buffer_create_tag(buffer, "diff-hunk",
497 			"foreground-gdk", &diff_hunk_color,
498 			"weight", PANGO_WEIGHT_BOLD,
499 			NULL);
500 	g_signal_connect(G_OBJECT(qtag), "event",
501                          G_CALLBACK(textview_uri_button_pressed), textview);
502 	g_signal_connect(G_OBJECT(tag), "event",
503                          G_CALLBACK(textview_uri_button_pressed), textview);
504 /*	if (font_desc)
505 		pango_font_description_free(font_desc);
506 	if (bold_font_desc)
507 		pango_font_description_free(bold_font_desc);*/
508  }
509 
textview_init(TextView * textview)510 void textview_init(TextView *textview)
511 {
512 	if (!hand_cursor)
513 		hand_cursor = gdk_cursor_new(GDK_HAND2);
514 	if (!text_cursor)
515 		text_cursor = gdk_cursor_new(GDK_XTERM);
516 	if (!watch_cursor)
517 		watch_cursor = gdk_cursor_new(GDK_WATCH);
518 
519 	textview_reflect_prefs(textview);
520 	textview_set_font(textview, NULL);
521 	textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
522 }
523 
524  #define CHANGE_TAG_COLOR(tagname, colorfg, colorbg) { \
525 	tag = gtk_text_tag_table_lookup(tags, tagname); \
526 	if (tag) \
527 		g_object_set(G_OBJECT(tag), "foreground-gdk", colorfg, "paragraph-background-gdk", colorbg, NULL); \
528  }
529 
textview_update_message_colors(TextView * textview)530 static void textview_update_message_colors(TextView *textview)
531 {
532 	GdkColor black = {0, 0, 0, 0};
533 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
534 
535 	GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
536 	GtkTextTag *tag = NULL;
537 
538 	quote_bgcolors[0] = quote_bgcolors[1] = quote_bgcolors[2] = black;
539 	quote_colors[0] = quote_colors[1] = quote_colors[2] = black;
540 	uri_color = emphasis_color = signature_color = diff_added_color =
541 		diff_deleted_color = diff_hunk_color = black;
542 	tags_bgcolor = tags_color = black;
543 
544 	if (prefs_common.enable_color) {
545 		/* grab the quote colors, converting from an int to a GdkColor */
546 		gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1],
547 					       &quote_colors[0]);
548 		gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2],
549 					       &quote_colors[1]);
550 		gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3],
551 					       &quote_colors[2]);
552 		gtkut_convert_int_to_gdk_color(prefs_common.color[COL_URI],
553 					       &uri_color);
554 		gtkut_convert_int_to_gdk_color(prefs_common.color[COL_SIGNATURE],
555 					       &signature_color);
556 		gtkut_convert_int_to_gdk_color(prefs_common.color[COL_EMPHASIS],
557 					       &emphasis_color);
558 		gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DIFF_ADDED],
559 					       &diff_added_color);
560 		gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DIFF_DELETED],
561 					       &diff_deleted_color);
562 		gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DIFF_HUNK],
563 					       &diff_hunk_color);
564 	}
565 	if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
566 		gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1_BG],
567 						   &quote_bgcolors[0]);
568 		gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2_BG],
569 						   &quote_bgcolors[1]);
570 		gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3_BG],
571 						   &quote_bgcolors[2]);
572 		CHANGE_TAG_COLOR("quote0", &quote_colors[0], &quote_bgcolors[0]);
573 		CHANGE_TAG_COLOR("quote1", &quote_colors[1], &quote_bgcolors[1]);
574 		CHANGE_TAG_COLOR("quote2", &quote_colors[2], &quote_bgcolors[2]);
575 	} else {
576 		CHANGE_TAG_COLOR("quote0", &quote_colors[0], NULL);
577 		CHANGE_TAG_COLOR("quote1", &quote_colors[1], NULL);
578 		CHANGE_TAG_COLOR("quote2", &quote_colors[2], NULL);
579 	}
580 
581 	CHANGE_TAG_COLOR("emphasis", &emphasis_color, NULL);
582 	CHANGE_TAG_COLOR("signature", &signature_color, NULL);
583 	CHANGE_TAG_COLOR("link", &uri_color, NULL);
584 	CHANGE_TAG_COLOR("link-hover", &uri_color, NULL);
585 	CHANGE_TAG_COLOR("diff-add", &diff_added_color, NULL);
586 	CHANGE_TAG_COLOR("diff-del", &diff_deleted_color, NULL);
587 	CHANGE_TAG_COLOR("diff-add-file", &diff_added_color, NULL);
588 	CHANGE_TAG_COLOR("diff-del-file", &diff_deleted_color, NULL);
589 	CHANGE_TAG_COLOR("diff-hunk", &diff_hunk_color, NULL);
590 
591 	gtkut_convert_int_to_gdk_color(prefs_common.color[COL_TAGS_BG],
592 					   &tags_bgcolor);
593 	gtkut_convert_int_to_gdk_color(prefs_common.color[COL_TAGS],
594 					   &tags_color);
595 }
596 #undef CHANGE_TAG_COLOR
597 
textview_reflect_prefs(TextView * textview)598 void textview_reflect_prefs(TextView *textview)
599 {
600 	textview_set_font(textview, NULL);
601 	textview_update_message_colors(textview);
602 	gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(textview->text),
603 					 prefs_common.textview_cursor_visible);
604 }
605 
textview_show_part(TextView * textview,MimeInfo * mimeinfo,FILE * fp)606 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
607 {
608 	START_TIMING("");
609 	cm_return_if_fail(mimeinfo != NULL);
610 	cm_return_if_fail(fp != NULL);
611 
612 	textview->loading = TRUE;
613 	textview->stop_loading = FALSE;
614 
615 	textview_clear(textview);
616 
617 	if (mimeinfo->type == MIMETYPE_MULTIPART ||
618 	    (mimeinfo->type == MIMETYPE_MESSAGE && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822"))) {
619 		textview_add_parts(textview, mimeinfo);
620 	} else {
621 		if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
622 			perror("fseek");
623 
624 		textview_write_body(textview, mimeinfo);
625 	}
626 
627 	textview->loading = FALSE;
628 	textview->stop_loading = FALSE;
629 	textview_set_position(textview, 0);
630 
631 	END_TIMING();
632 }
633 
textview_add_part(TextView * textview,MimeInfo * mimeinfo)634 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
635 {
636 	GtkAllocation allocation;
637 	GtkTextView *text;
638 	GtkTextBuffer *buffer;
639 	GtkTextIter iter, start_iter;
640 	gchar buf[BUFFSIZE];
641 	GPtrArray *headers = NULL;
642 	const gchar *name;
643 	gchar *content_type;
644 	gint charcount;
645 
646 	START_TIMING("");
647 
648 	cm_return_if_fail(mimeinfo != NULL);
649 	text = GTK_TEXT_VIEW(textview->text);
650 	buffer = gtk_text_view_get_buffer(text);
651 	charcount = gtk_text_buffer_get_char_count(buffer);
652 	gtk_text_buffer_get_end_iter(buffer, &iter);
653 
654 	if (textview->stop_loading) {
655 		return;
656 	}
657 	if (mimeinfo->type == MIMETYPE_MULTIPART) {
658 		END_TIMING();
659 		return;
660 	}
661 
662 	textview->prev_quote_level = -1;
663 
664 	if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
665 		FILE *fp;
666 		if (mimeinfo->content == MIMECONTENT_MEM)
667 			fp = str_open_as_stream(mimeinfo->data.mem);
668 		else
669 			fp = claws_fopen(mimeinfo->data.filename, "rb");
670 		if (!fp) {
671 			FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
672 			END_TIMING();
673 			return;
674 		}
675 		if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0) {
676 			FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
677 			claws_fclose(fp);
678 			END_TIMING();
679 			return;
680 		}
681 		headers = textview_scan_header(textview, fp);
682 		if (headers) {
683 			if (charcount > 0)
684 				gtk_text_buffer_insert(buffer, &iter, "\n", 1);
685 
686 			if (procmime_mimeinfo_parent(mimeinfo) == NULL)
687 				textview_show_tags(textview);
688 			textview_show_header(textview, headers);
689 			procheader_header_array_destroy(headers);
690 		}
691 		claws_fclose(fp);
692 		END_TIMING();
693 		return;
694 	}
695 
696 	name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
697 	content_type = procmime_get_content_type_str(mimeinfo->type,
698 						     mimeinfo->subtype);
699 	if (name == NULL)
700 		name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
701 	if (name != NULL)
702 		g_snprintf(buf, sizeof(buf), _("[%s  %s (%d bytes)]"),
703 			   name, content_type, mimeinfo->length);
704 	else
705 		g_snprintf(buf, sizeof(buf), _("[%s (%d bytes)]"),
706 			   content_type, mimeinfo->length);
707 
708 	g_free(content_type);
709 
710 	if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT
711 	|| (mimeinfo->disposition == DISPOSITIONTYPE_INLINE &&
712 	    mimeinfo->type != MIMETYPE_TEXT)) {
713 		gtk_text_buffer_insert(buffer, &iter, "\n", 1);
714 		TEXTVIEW_INSERT_LINK(buf, "sc://select_attachment", mimeinfo);
715 		gtk_text_buffer_insert(buffer, &iter, " \n", -1);
716 		if (mimeinfo->type == MIMETYPE_IMAGE  &&
717 		    prefs_common.inline_img ) {
718 			GdkPixbuf *pixbuf;
719 			GError *error = NULL;
720 			ClickableText *uri;
721 
722 			START_TIMING("inserting image");
723 
724 			pixbuf = procmime_get_part_as_pixbuf(mimeinfo, &error);
725 			if (error != NULL) {
726 				g_warning("Can't load the image: %s\n", error->message);
727 				g_error_free(error);
728 				END_TIMING();
729 				return;
730 			}
731 
732 			if (textview->stop_loading) {
733 				END_TIMING();
734 				return;
735 			}
736 
737 			gtk_widget_get_allocation(textview->scrolledwin, &allocation);
738 			pixbuf = claws_load_pixbuf_fitting(pixbuf, prefs_common.inline_img,
739 					prefs_common.fit_img_height, allocation.width,
740 					allocation.height);
741 
742 			if (textview->stop_loading) {
743 				END_TIMING();
744 				return;
745 			}
746 
747 			uri = g_new0(ClickableText, 1);
748 			uri->uri = g_strdup("");
749 			uri->filename = g_strdup("sc://select_attachment");
750 			uri->data = mimeinfo;
751 
752 			uri->start = gtk_text_iter_get_offset(&iter);
753 			gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf);
754 			g_object_unref(pixbuf);
755 			if (textview->stop_loading) {
756 				g_free(uri);
757 				return;
758 			}
759 			uri->end = gtk_text_iter_get_offset(&iter);
760 
761 			textview->uri_list =
762 				g_slist_prepend(textview->uri_list, uri);
763 
764 			gtk_text_buffer_insert(buffer, &iter, " ", 1);
765 			gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, uri->start);
766 			gtk_text_buffer_apply_tag_by_name(buffer, "link",
767 						&start_iter, &iter);
768 
769 			END_TIMING();
770 			GTK_EVENTS_FLUSH();
771 		}
772 	} else if (mimeinfo->type == MIMETYPE_TEXT) {
773 		if (prefs_common.display_header && (charcount > 0))
774 			gtk_text_buffer_insert(buffer, &iter, "\n", 1);
775 
776 		if (!gtk_text_buffer_get_mark(buffer, "body_start")) {
777 			gtk_text_buffer_get_end_iter(buffer, &iter);
778 			gtk_text_buffer_create_mark(buffer, "body_start", &iter, TRUE);
779 		}
780 
781 		textview_write_body(textview, mimeinfo);
782 
783 		if (!gtk_text_buffer_get_mark(buffer, "body_end")) {
784 			gtk_text_buffer_get_end_iter(buffer, &iter);
785 			gtk_text_buffer_create_mark(buffer, "body_end", &iter, TRUE);
786 		}
787 	}
788 	END_TIMING();
789 }
790 
recursive_add_parts(TextView * textview,GNode * node)791 static void recursive_add_parts(TextView *textview, GNode *node)
792 {
793         GNode * iter;
794 	MimeInfo *mimeinfo;
795         START_TIMING("");
796 
797         mimeinfo = (MimeInfo *) node->data;
798 
799         textview_add_part(textview, mimeinfo);
800 #ifdef GENERIC_UMPC
801 	textview_set_position(textview, 0);
802 #endif
803         if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
804             (mimeinfo->type != MIMETYPE_MESSAGE)) {
805 	    	END_TIMING();
806                 return;
807         }
808         if (g_ascii_strcasecmp(mimeinfo->subtype, "alternative") == 0) {
809                 GNode * preferred_body;
810                 int preferred_score;
811 
812                 /*
813                   text/plain : score 3
814                   text/ *    : score 2
815                   other      : score 1
816                 */
817                 preferred_body = NULL;
818                 preferred_score = 0;
819 
820                 for (iter = g_node_first_child(node) ; iter != NULL ;
821                      iter = g_node_next_sibling(iter)) {
822                         int score;
823                         MimeInfo * submime;
824 
825                         score = 1;
826                         submime = (MimeInfo *) iter->data;
827                         if (submime->type == MIMETYPE_TEXT)
828                                 score = 2;
829 
830                         if (submime->subtype != NULL) {
831                                 if (g_ascii_strcasecmp(submime->subtype, "plain") == 0)
832                                         score = 3;
833                         }
834 
835                         if (score > preferred_score) {
836                                 preferred_score = score;
837                                 preferred_body = iter;
838                         }
839                 }
840 
841                 if (preferred_body != NULL) {
842                         recursive_add_parts(textview, preferred_body);
843                 }
844         }
845         else {
846                 for (iter = g_node_first_child(node) ; iter != NULL ;
847                      iter = g_node_next_sibling(iter)) {
848                         recursive_add_parts(textview, iter);
849                 }
850         }
851 	END_TIMING();
852 }
853 
textview_add_parts(TextView * textview,MimeInfo * mimeinfo)854 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
855 {
856 	cm_return_if_fail(mimeinfo != NULL);
857 	cm_return_if_fail(mimeinfo->node != NULL);
858 
859 	recursive_add_parts(textview, mimeinfo->node);
860 }
861 
textview_show_error(TextView * textview)862 void textview_show_error(TextView *textview)
863 {
864 	GtkTextView *text;
865 	GtkTextBuffer *buffer;
866 	GtkTextIter iter;
867 
868 	textview_set_font(textview, NULL);
869 	textview_clear(textview);
870 
871 	text = GTK_TEXT_VIEW(textview->text);
872 	buffer = gtk_text_view_get_buffer(text);
873 	gtk_text_buffer_get_start_iter(buffer, &iter);
874 
875 	TEXTVIEW_INSERT(_("\n"
876 		      "  This message can't be displayed.\n"
877 		      "  This is probably due to a network error.\n"
878 		      "\n"
879 		      "  Use "));
880 	TEXTVIEW_INSERT_LINK(_("'Network Log'"), "sc://view_log", NULL);
881 	TEXTVIEW_INSERT(_(" in the Tools menu for more information."));
882 	textview_show_icon(textview, GTK_STOCK_DIALOG_ERROR);
883 }
884 
textview_show_info(TextView * textview,const gchar * info_str)885 void textview_show_info(TextView *textview, const gchar *info_str)
886 {
887 	GtkTextView *text;
888 	GtkTextBuffer *buffer;
889 	GtkTextIter iter;
890 
891 	textview_set_font(textview, NULL);
892 	textview_clear(textview);
893 
894 	text = GTK_TEXT_VIEW(textview->text);
895 	buffer = gtk_text_view_get_buffer(text);
896 	gtk_text_buffer_get_start_iter(buffer, &iter);
897 
898 	TEXTVIEW_INSERT(info_str);
899 	textview_show_icon(textview, GTK_STOCK_DIALOG_INFO);
900 	textview_cursor_normal(textview);
901 }
902 
textview_show_mime_part(TextView * textview,MimeInfo * partinfo)903 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
904 {
905 	GtkTextView *text;
906 	GtkTextBuffer *buffer;
907 	GtkTextIter iter;
908 	const gchar *name;
909 	gchar *content_type;
910 	GtkUIManager *ui_manager;
911 #ifndef GENERIC_UMPC
912 	gchar *shortcut;
913 #endif
914 
915 	if (!partinfo) return;
916 
917 	if (textview->messageview->window != NULL)
918 		ui_manager = textview->messageview->ui_manager;
919 	else
920 		ui_manager = textview->messageview->mainwin->ui_manager;
921 
922 	textview_set_font(textview, NULL);
923 	textview_clear(textview);
924 
925 	text = GTK_TEXT_VIEW(textview->text);
926 	buffer = gtk_text_view_get_buffer(text);
927 	gtk_text_buffer_get_start_iter(buffer, &iter);
928 
929 	TEXTVIEW_INSERT("\n");
930 
931 	name = procmime_mimeinfo_get_parameter(partinfo, "filename");
932 	if (name == NULL)
933 		name = procmime_mimeinfo_get_parameter(partinfo, "name");
934 	if (name != NULL) {
935 		content_type = procmime_get_content_type_str(partinfo->type,
936 						     partinfo->subtype);
937 		TEXTVIEW_INSERT("  ");
938 		TEXTVIEW_INSERT_BOLD(name);
939 		TEXTVIEW_INSERT(" (");
940 		TEXTVIEW_INSERT(content_type);
941 		TEXTVIEW_INSERT(", ");
942 		TEXTVIEW_INSERT(to_human_readable((goffset)partinfo->length));
943 		TEXTVIEW_INSERT("):\n\n");
944 
945 		g_free(content_type);
946 	}
947 	TEXTVIEW_INSERT(_("  The following can be performed on this part\n"));
948 #ifndef GENERIC_UMPC
949 	TEXTVIEW_INSERT(_("  by right-clicking the icon or list item:"));
950 #endif
951 	TEXTVIEW_INSERT("\n");
952 
953 	TEXTVIEW_INSERT(_("     - To save, select "));
954 	TEXTVIEW_INSERT_LINK(_("'Save as...'"), "sc://save_as", NULL);
955 #ifndef GENERIC_UMPC
956 	TEXTVIEW_INSERT(_(" (Shortcut key: '"));
957 	shortcut = cm_menu_item_get_shortcut(ui_manager, "Menu/File/SavePartAs");
958 	TEXTVIEW_INSERT(shortcut);
959 	g_free(shortcut);
960 	TEXTVIEW_INSERT("')");
961 #endif
962 	TEXTVIEW_INSERT("\n");
963 
964 	TEXTVIEW_INSERT(_("     - To display as text, select "));
965 	TEXTVIEW_INSERT_LINK(_("'Display as text'"), "sc://display_as_text", NULL);
966 
967 #ifndef GENERIC_UMPC
968 	TEXTVIEW_INSERT(_(" (Shortcut key: '"));
969 	shortcut = cm_menu_item_get_shortcut(ui_manager, "Menu/View/Part/AsText");
970 	TEXTVIEW_INSERT(shortcut);
971 	g_free(shortcut);
972 	TEXTVIEW_INSERT("')");
973 #endif
974 	TEXTVIEW_INSERT("\n");
975 
976 	TEXTVIEW_INSERT(_("     - To open with an external program, select "));
977 	TEXTVIEW_INSERT_LINK(_("'Open'"), "sc://open", NULL);
978 
979 #ifndef GENERIC_UMPC
980 	TEXTVIEW_INSERT(_(" (Shortcut key: '"));
981 	shortcut = cm_menu_item_get_shortcut(ui_manager, "Menu/View/Part/Open");
982 	TEXTVIEW_INSERT(shortcut);
983 	g_free(shortcut);
984 	TEXTVIEW_INSERT("')\n");
985 	TEXTVIEW_INSERT(_("       (alternately double-click, or click the middle "));
986 	TEXTVIEW_INSERT(_("mouse button)\n"));
987 #ifndef G_OS_WIN32
988 	TEXTVIEW_INSERT(_("     - Or use "));
989 	TEXTVIEW_INSERT_LINK(_("'Open with...'"), "sc://open_with", NULL);
990 	TEXTVIEW_INSERT(_(" (Shortcut key: '"));
991 	shortcut = cm_menu_item_get_shortcut(ui_manager, "Menu/View/Part/OpenWith");
992 	TEXTVIEW_INSERT(shortcut);
993 	g_free(shortcut);
994 	TEXTVIEW_INSERT("')");
995 #endif
996 #endif
997 	TEXTVIEW_INSERT("\n");
998 
999 	textview_show_icon(textview, GTK_STOCK_DIALOG_INFO);
1000 }
1001 
textview_write_body(TextView * textview,MimeInfo * mimeinfo)1002 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
1003 {
1004 	FILE *tmpfp;
1005 	gchar buf[BUFFSIZE];
1006 	CodeConverter *conv;
1007 	const gchar *charset;
1008 #ifndef G_OS_WIN32
1009 	const gchar *p, *cmd;
1010 #endif
1011 	GSList *cur;
1012 	gboolean continue_write = TRUE;
1013 	size_t wrote = 0, i = 0;
1014 
1015 	if (textview->messageview->forced_charset)
1016 		charset = textview->messageview->forced_charset;
1017 	else {
1018 		/* use supersets transparently when possible */
1019 		charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
1020 		if (charset && !strcasecmp(charset, CS_ISO_8859_1))
1021 			charset = CS_WINDOWS_1252;
1022 		else if (charset && !strcasecmp(charset, CS_X_GBK))
1023 			charset = CS_GB18030;
1024 		else if (charset && !strcasecmp(charset, CS_GBK))
1025 			charset = CS_GB18030;
1026 		else if (charset && !strcasecmp(charset, CS_GB2312))
1027 			charset = CS_GB18030;
1028 	}
1029 
1030 	textview_set_font(textview, charset);
1031 
1032 	conv = conv_code_converter_new(charset);
1033 
1034 	procmime_force_encoding(textview->messageview->forced_encoding);
1035 
1036 	textview->is_in_signature = FALSE;
1037 	textview->is_diff = FALSE;
1038 	textview->is_attachment = FALSE;;
1039 	textview->is_in_git_patch = FALSE;
1040 
1041 	procmime_decode_content(mimeinfo);
1042 
1043 	account_sigsep_matchlist_create();
1044 
1045 	if (!g_ascii_strcasecmp(mimeinfo->subtype, "html") &&
1046 	    prefs_common.render_html) {
1047 		gchar *filename;
1048 
1049 		filename = procmime_get_tmp_file_name(mimeinfo);
1050 		if (procmime_get_part(filename, mimeinfo) == 0) {
1051 			tmpfp = claws_fopen(filename, "rb");
1052 			if (tmpfp) {
1053 				textview_show_html(textview, tmpfp, conv);
1054 				claws_fclose(tmpfp);
1055 			}
1056 			claws_unlink(filename);
1057 		}
1058 		g_free(filename);
1059 	} else if (!g_ascii_strcasecmp(mimeinfo->subtype, "enriched")) {
1060 		gchar *filename;
1061 
1062 		filename = procmime_get_tmp_file_name(mimeinfo);
1063 		if (procmime_get_part(filename, mimeinfo) == 0) {
1064 			tmpfp = claws_fopen(filename, "rb");
1065 			if (tmpfp) {
1066 				textview_show_ertf(textview, tmpfp, conv);
1067 				claws_fclose(tmpfp);
1068 			}
1069 			claws_unlink(filename);
1070 		}
1071 		g_free(filename);
1072 #ifndef G_OS_WIN32
1073 	} else if ( g_ascii_strcasecmp(mimeinfo->subtype, "plain") &&
1074 		   (cmd = prefs_common.mime_textviewer) && *cmd &&
1075 		   (p = strchr(cmd, '%')) && *(p + 1) == 's') {
1076 		int pid, pfd[2];
1077 		const gchar *fname;
1078 
1079 		fname  = procmime_get_tmp_file_name(mimeinfo);
1080 		if (procmime_get_part(fname, mimeinfo)) goto textview_default;
1081 
1082 		g_snprintf(buf, sizeof(buf), cmd, fname);
1083 		debug_print("Viewing text content of type: %s (length: %d) "
1084 			"using %s\n", mimeinfo->subtype, mimeinfo->length, buf);
1085 
1086 		if (pipe(pfd) < 0) {
1087 			g_snprintf(buf, sizeof(buf),
1088 				"pipe failed for textview\n\n%s\n", g_strerror(errno));
1089 			textview_write_line(textview, buf, conv, TRUE);
1090 			goto textview_default;
1091 		}
1092 		pid = fork();
1093 		if (pid < 0) {
1094 			g_snprintf(buf, sizeof(buf),
1095 				"fork failed for textview\n\n%s\n", g_strerror(errno));
1096 			textview_write_line(textview, buf, conv, TRUE);
1097 			close(pfd[0]);
1098 			close(pfd[1]);
1099 			goto textview_default;
1100 		}
1101 		if (pid == 0) { /* child */
1102 			int rc;
1103 			gchar **argv;
1104 			argv = strsplit_with_quote(buf, " ", 0);
1105 			close(1);
1106 			close(pfd[0]);
1107 			rc = dup(pfd[1]);
1108 			rc = execvp(argv[0], argv);
1109 			perror("execvp");
1110 			close(pfd[1]);
1111 			g_print(_("The command to view attachment "
1112 			        "as text failed:\n"
1113 			        "    %s\n"
1114 			        "Exit code %d\n"), buf, rc);
1115 			exit(255);
1116 		}
1117 		close(pfd[1]);
1118 		tmpfp = claws_fdopen(pfd[0], "rb");
1119 		while (claws_fgets(buf, sizeof(buf), tmpfp)) {
1120 			textview_write_line(textview, buf, conv, TRUE);
1121 
1122 			if (textview->stop_loading) {
1123 				claws_fclose(tmpfp);
1124 				waitpid(pid, pfd, 0);
1125 				g_unlink(fname);
1126 				account_sigsep_matchlist_delete();
1127 				return;
1128 			}
1129 		}
1130 
1131 		claws_fclose(tmpfp);
1132 		waitpid(pid, pfd, 0);
1133 		g_unlink(fname);
1134 #endif
1135 	} else {
1136 #ifndef G_OS_WIN32
1137 textview_default:
1138 #endif
1139 		if (!g_ascii_strcasecmp(mimeinfo->subtype, "x-patch")
1140 				|| !g_ascii_strcasecmp(mimeinfo->subtype, "x-diff"))
1141 			textview->is_diff = TRUE;
1142 
1143 		/* Displayed part is an attachment, but not an attached
1144 		 * e-mail. Set a flag, so that elsewhere in the code we
1145 		 * know not to try making collapsible quotes in it. */
1146 		if (mimeinfo->disposition == DISPOSITIONTYPE_ATTACHMENT &&
1147 				mimeinfo->type != MIMETYPE_MESSAGE)
1148 			textview->is_attachment = TRUE;
1149 
1150 		if (mimeinfo->content == MIMECONTENT_MEM)
1151 			tmpfp = str_open_as_stream(mimeinfo->data.mem);
1152 		else
1153 			tmpfp = claws_fopen(mimeinfo->data.filename, "rb");
1154 		if (!tmpfp) {
1155 			FILE_OP_ERROR(mimeinfo->data.filename, "claws_fopen");
1156 			account_sigsep_matchlist_delete();
1157 			return;
1158 		}
1159 		if (fseek(tmpfp, mimeinfo->offset, SEEK_SET) < 0) {
1160 			FILE_OP_ERROR(mimeinfo->data.filename, "fseek");
1161 			claws_fclose(tmpfp);
1162 			account_sigsep_matchlist_delete();
1163 			return;
1164 		}
1165 		debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
1166 		while (((i = ftell(tmpfp)) < mimeinfo->offset + mimeinfo->length) &&
1167 		       (claws_fgets(buf, sizeof(buf), tmpfp) != NULL)
1168 		       && continue_write) {
1169 			textview_write_line(textview, buf, conv, TRUE);
1170 			if (textview->stop_loading) {
1171 				claws_fclose(tmpfp);
1172 				account_sigsep_matchlist_delete();
1173 				return;
1174 			}
1175 			wrote += ftell(tmpfp)-i;
1176 			if (mimeinfo->length > 1024*1024
1177 			&&  wrote > 1024*1024
1178 			&& !textview->messageview->show_full_text) {
1179 				continue_write = FALSE;
1180 			}
1181 		}
1182 		claws_fclose(tmpfp);
1183 	}
1184 
1185 	account_sigsep_matchlist_delete();
1186 
1187 	conv_code_converter_destroy(conv);
1188 	procmime_force_encoding(0);
1189 
1190 	textview->uri_list = g_slist_reverse(textview->uri_list);
1191 	for (cur = textview->uri_list; cur; cur = cur->next) {
1192 		ClickableText *uri = (ClickableText *)cur->data;
1193 		if (!uri->is_quote)
1194 			continue;
1195 		if (!prefs_common.hide_quotes ||
1196 		    uri->quote_level+1 < prefs_common.hide_quotes) {
1197 			textview_toggle_quote(textview, cur, uri, TRUE);
1198 			if (textview->stop_loading) {
1199 				return;
1200 			}
1201 		}
1202 	}
1203 
1204 	if (continue_write == FALSE) {
1205 		messageview_show_partial_display(
1206 			textview->messageview,
1207 			textview->messageview->msginfo,
1208 			mimeinfo->length);
1209 	}
1210 	GTK_EVENTS_FLUSH();
1211 }
1212 
textview_show_html(TextView * textview,FILE * fp,CodeConverter * conv)1213 static void textview_show_html(TextView *textview, FILE *fp,
1214 			       CodeConverter *conv)
1215 {
1216 	SC_HTMLParser *parser;
1217 	gchar *str;
1218 	gint lines = 0;
1219 
1220 	parser = sc_html_parser_new(fp, conv);
1221 	cm_return_if_fail(parser != NULL);
1222 
1223 	account_sigsep_matchlist_create();
1224 
1225 	while ((str = sc_html_parse(parser)) != NULL) {
1226 	        if (parser->state == SC_HTML_HREF) {
1227 		        /* first time : get and copy the URL */
1228 		        if (parser->href == NULL) {
1229 				/* ALF - the claws html parser returns an empty string,
1230 				 * if still inside an <a>, but already parsed past HREF */
1231 				str = strtok(str, " ");
1232 				if (str) {
1233 					while (str && *str && g_ascii_isspace(*str))
1234 						str++;
1235 					parser->href = g_strdup(str);
1236 					/* the URL may (or not) be followed by the
1237 					 * referenced text */
1238 					str = strtok(NULL, "");
1239 				}
1240 		        }
1241 		        if (str != NULL)
1242 			        textview_write_link(textview, str, parser->href, NULL);
1243 	        } else
1244 		        textview_write_line(textview, str, NULL, FALSE);
1245 		lines++;
1246 		if (lines % 500 == 0)
1247 			GTK_EVENTS_FLUSH();
1248 		if (textview->stop_loading) {
1249 			account_sigsep_matchlist_delete();
1250 			return;
1251 		}
1252 	}
1253 	textview_write_line(textview, "\n", NULL, FALSE);
1254 
1255 	account_sigsep_matchlist_delete();
1256 
1257 	sc_html_parser_destroy(parser);
1258 }
1259 
textview_show_ertf(TextView * textview,FILE * fp,CodeConverter * conv)1260 static void textview_show_ertf(TextView *textview, FILE *fp,
1261 			       CodeConverter *conv)
1262 {
1263 	ERTFParser *parser;
1264 	gchar *str;
1265 	gint lines = 0;
1266 
1267 	parser = ertf_parser_new(fp, conv);
1268 	cm_return_if_fail(parser != NULL);
1269 
1270 	account_sigsep_matchlist_create();
1271 
1272 	while ((str = ertf_parse(parser)) != NULL) {
1273 		textview_write_line(textview, str, NULL, FALSE);
1274 		lines++;
1275 		if (lines % 500 == 0)
1276 			GTK_EVENTS_FLUSH();
1277 		if (textview->stop_loading) {
1278 			account_sigsep_matchlist_delete();
1279 			return;
1280 		}
1281 	}
1282 
1283 	account_sigsep_matchlist_delete();
1284 
1285 	ertf_parser_destroy(parser);
1286 }
1287 
1288 #define ADD_TXT_POS(bp_, ep_, pti_) \
1289 	if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1290 		last = last->next; \
1291 		last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1292 		last->next = NULL; \
1293 	} else { \
1294 		g_warning("alloc error scanning URIs"); \
1295 		gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
1296 							 linebuf, -1, \
1297 							 fg_tag, NULL); \
1298 		return; \
1299 	}
1300 
1301 #define ADD_TXT_POS_LATER(bp_, ep_, pti_) \
1302 	if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
1303 		last = last->next; \
1304 		last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
1305 		last->next = NULL; \
1306 	} else { \
1307 		g_warning("alloc error scanning URIs"); \
1308 	}
1309 
1310 /* textview_make_clickable_parts() - colorizes clickable parts */
textview_make_clickable_parts(TextView * textview,const gchar * fg_tag,const gchar * uri_tag,const gchar * linebuf,gboolean hdr)1311 static void textview_make_clickable_parts(TextView *textview,
1312 					  const gchar *fg_tag,
1313 					  const gchar *uri_tag,
1314 					  const gchar *linebuf,
1315 					  gboolean hdr)
1316 {
1317 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1318 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1319 	GtkTextIter iter;
1320 	gchar *mybuf = g_strdup(linebuf);
1321 
1322 	/* parse table - in order of priority */
1323 	struct table {
1324 		const gchar *needle; /* token */
1325 
1326 		/* token search function */
1327 		gchar    *(*search)	(const gchar *haystack,
1328 					 const gchar *needle);
1329 		/* part parsing function */
1330 		gboolean  (*parse)	(const gchar *start,
1331 					 const gchar *scanpos,
1332 					 const gchar **bp_,
1333 					 const gchar **ep_,
1334 					 gboolean hdr);
1335 		/* part to URI function */
1336 		gchar    *(*build_uri)	(const gchar *bp,
1337 					 const gchar *ep);
1338 	};
1339 
1340 	static struct table parser[] = {
1341 		{"http://",  strcasestr, get_uri_part,   make_uri_string},
1342 		{"https://", strcasestr, get_uri_part,   make_uri_string},
1343 		{"ftp://",   strcasestr, get_uri_part,   make_uri_string},
1344 		{"sftp://",  strcasestr, get_uri_part,   make_uri_string},
1345 		{"gopher://",strcasestr, get_uri_part,   make_uri_string},
1346 		{"www.",     strcasestr, get_uri_part,   make_http_string},
1347 		{"mailto:",  strcasestr, get_uri_part,   make_uri_string},
1348 		{"@",        strcasestr, get_email_part, make_email_string}
1349 	};
1350 	const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1351 
1352 	gint  n;
1353 	const gchar *walk, *bp, *ep;
1354 
1355 	struct txtpos {
1356 		const gchar	*bp, *ep;	/* text position */
1357 		gint		 pti;		/* index in parse table */
1358 		struct txtpos	*next;		/* next */
1359 	} head = {NULL, NULL, 0,  NULL}, *last = &head;
1360 
1361 	if (!g_utf8_validate(linebuf, -1, NULL)) {
1362 		g_free(mybuf);
1363 		mybuf = g_malloc(strlen(linebuf)*2 +1);
1364 		conv_localetodisp(mybuf, strlen(linebuf)*2 +1, linebuf);
1365 	}
1366 
1367 	gtk_text_buffer_get_end_iter(buffer, &iter);
1368 
1369 	/* parse for clickable parts, and build a list of begin and end positions  */
1370 	for (walk = mybuf;;) {
1371 		gint last_index = PARSE_ELEMS;
1372 		gchar *scanpos = NULL;
1373 
1374 		/* FIXME: this looks phony. scanning for anything in the parse table */
1375 		for (n = 0; n < PARSE_ELEMS; n++) {
1376 			gchar *tmp;
1377 
1378 			tmp = parser[n].search(walk, parser[n].needle);
1379 			if (tmp) {
1380 				if (scanpos == NULL || tmp < scanpos) {
1381 					scanpos = tmp;
1382 					last_index = n;
1383 				}
1384 			}
1385 		}
1386 
1387 		if (scanpos) {
1388 			/* check if URI can be parsed */
1389 			if (parser[last_index].parse(walk, scanpos, &bp, &ep, hdr)
1390 			    && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1391 					ADD_TXT_POS(bp, ep, last_index);
1392 					walk = ep;
1393 			} else
1394 				walk = scanpos +
1395 					strlen(parser[last_index].needle);
1396 		} else
1397 			break;
1398 	}
1399 
1400 	/* colorize this line */
1401 	if (head.next) {
1402 		const gchar *normal_text = mybuf;
1403 
1404 		/* insert URIs */
1405 		for (last = head.next; last != NULL;
1406 		     normal_text = last->ep, last = last->next) {
1407 			ClickableText *uri;
1408 			uri = g_new0(ClickableText, 1);
1409 			if (last->bp - normal_text > 0)
1410 				gtk_text_buffer_insert_with_tags_by_name
1411 					(buffer, &iter,
1412 					 normal_text,
1413 					 last->bp - normal_text,
1414 					 fg_tag, NULL);
1415 			uri->uri = parser[last->pti].build_uri(last->bp,
1416 							       last->ep);
1417 			uri->start = gtk_text_iter_get_offset(&iter);
1418 			gtk_text_buffer_insert_with_tags_by_name
1419 				(buffer, &iter, last->bp, last->ep - last->bp,
1420 				 uri_tag, fg_tag, NULL);
1421 			uri->end = gtk_text_iter_get_offset(&iter);
1422 			uri->filename = NULL;
1423 			textview->uri_list =
1424 				g_slist_prepend(textview->uri_list, uri);
1425 		}
1426 
1427 		if (*normal_text)
1428 			gtk_text_buffer_insert_with_tags_by_name
1429 				(buffer, &iter, normal_text, -1, fg_tag, NULL);
1430 	} else {
1431 		gtk_text_buffer_insert_with_tags_by_name
1432 			(buffer, &iter, mybuf, -1, fg_tag, NULL);
1433 	}
1434 	g_free(mybuf);
1435 }
1436 
1437 /* textview_make_clickable_parts() - colorizes clickable parts */
textview_make_clickable_parts_later(TextView * textview,gint start,gint end)1438 static void textview_make_clickable_parts_later(TextView *textview,
1439 					  gint start, gint end)
1440 {
1441 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1442 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1443 	GtkTextIter start_iter, end_iter;
1444 	gchar *mybuf;
1445 	gint offset = 0;
1446 	/* parse table - in order of priority */
1447 	struct table {
1448 		const gchar *needle; /* token */
1449 
1450 		/* token search function */
1451 		gchar    *(*search)	(const gchar *haystack,
1452 					 const gchar *needle);
1453 		/* part parsing function */
1454 		gboolean  (*parse)	(const gchar *start,
1455 					 const gchar *scanpos,
1456 					 const gchar **bp_,
1457 					 const gchar **ep_,
1458 					 gboolean hdr);
1459 		/* part to URI function */
1460 		gchar    *(*build_uri)	(const gchar *bp,
1461 					 const gchar *ep);
1462 	};
1463 
1464 	static struct table parser[] = {
1465 		{"http://",  strcasestr, get_uri_part,   make_uri_string},
1466 		{"https://", strcasestr, get_uri_part,   make_uri_string},
1467 		{"ftp://",   strcasestr, get_uri_part,   make_uri_string},
1468 		{"sftp://",  strcasestr, get_uri_part,   make_uri_string},
1469 		{"www.",     strcasestr, get_uri_part,   make_http_string},
1470 		{"mailto:",  strcasestr, get_uri_part,   make_uri_string},
1471 		{"@",        strcasestr, get_email_part, make_email_string}
1472 	};
1473 	const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
1474 
1475 	gint  n;
1476 	const gchar *walk, *bp, *ep;
1477 
1478 	struct txtpos {
1479 		const gchar	*bp, *ep;	/* text position */
1480 		gint		 pti;		/* index in parse table */
1481 		struct txtpos	*next;		/* next */
1482 	} head = {NULL, NULL, 0,  NULL}, *last = &head;
1483 
1484 	gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
1485 	gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
1486 	mybuf = gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, FALSE);
1487 	offset = gtk_text_iter_get_offset(&start_iter);
1488 
1489 	/* parse for clickable parts, and build a list of begin and end positions  */
1490 	for (walk = mybuf;;) {
1491 		gint last_index = PARSE_ELEMS;
1492 		gchar *scanpos = NULL;
1493 
1494 		/* FIXME: this looks phony. scanning for anything in the parse table */
1495 		for (n = 0; n < PARSE_ELEMS; n++) {
1496 			gchar *tmp;
1497 
1498 			tmp = parser[n].search(walk, parser[n].needle);
1499 			if (tmp) {
1500 				if (scanpos == NULL || tmp < scanpos) {
1501 					scanpos = tmp;
1502 					last_index = n;
1503 				}
1504 			}
1505 		}
1506 
1507 		if (scanpos) {
1508 			/* check if URI can be parsed */
1509 			if (parser[last_index].parse(walk, scanpos, &bp, &ep, FALSE)
1510 			    && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1511 					ADD_TXT_POS_LATER(bp, ep, last_index);
1512 					walk = ep;
1513 			} else
1514 				walk = scanpos +
1515 					strlen(parser[last_index].needle);
1516 		} else
1517 			break;
1518 	}
1519 
1520 	/* colorize this line */
1521 	if (head.next) {
1522 		/* insert URIs */
1523 		for (last = head.next; last != NULL; last = last->next) {
1524 			ClickableText *uri;
1525 			gint start_offset, end_offset;
1526 			gchar *tmp_str;
1527 			gchar old_char;
1528 			uri = g_new0(ClickableText, 1);
1529 			uri->uri = parser[last->pti].build_uri(last->bp,
1530 							       last->ep);
1531 
1532 			tmp_str = mybuf;
1533 			old_char = tmp_str[last->ep - mybuf];
1534 			tmp_str[last->ep - mybuf] = '\0';
1535 			end_offset = g_utf8_strlen(tmp_str, -1);
1536 			tmp_str[last->ep - mybuf] = old_char;
1537 
1538 			old_char = tmp_str[last->bp - mybuf];
1539 			tmp_str[last->bp - mybuf] = '\0';
1540 			start_offset = g_utf8_strlen(tmp_str, -1);
1541 			tmp_str[last->bp - mybuf] = old_char;
1542 
1543 			gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start_offset + offset);
1544 			gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end_offset + offset);
1545 
1546 			uri->start = gtk_text_iter_get_offset(&start_iter);
1547 
1548 			gtk_text_buffer_apply_tag_by_name(buffer, "link", &start_iter, &end_iter);
1549 
1550 			uri->end = gtk_text_iter_get_offset(&end_iter);
1551 			uri->filename = NULL;
1552 			textview->uri_list =
1553 				g_slist_prepend(textview->uri_list, uri);
1554 		}
1555 	}
1556 
1557 	g_free(mybuf);
1558 }
1559 
1560 #undef ADD_TXT_POS
1561 
textview_write_line(TextView * textview,const gchar * str,CodeConverter * conv,gboolean do_quote_folding)1562 static void textview_write_line(TextView *textview, const gchar *str,
1563 				CodeConverter *conv, gboolean do_quote_folding)
1564 {
1565 	GtkTextView *text;
1566 	GtkTextBuffer *buffer;
1567 	GtkTextIter iter;
1568 	gchar buf[BUFFSIZE];
1569 	gchar *fg_color;
1570 	gint quotelevel = -1, real_quotelevel = -1;
1571 	gchar quote_tag_str[10];
1572 
1573 	text = GTK_TEXT_VIEW(textview->text);
1574 	buffer = gtk_text_view_get_buffer(text);
1575 	gtk_text_buffer_get_end_iter(buffer, &iter);
1576 
1577 	if (!conv)
1578 		strncpy2(buf, str, sizeof(buf));
1579 	else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1580 		conv_localetodisp(buf, sizeof(buf), str);
1581 
1582 	strcrchomp(buf);
1583 	fg_color = NULL;
1584 
1585 	/* change color of quotation
1586 	   >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1587 	   Up to 3 levels of quotations are detected, and each
1588 	   level is colored using a different color. */
1589 	if (prefs_common.enable_color
1590 	    && !textview->is_attachment
1591 	    && line_has_quote_char(buf, prefs_common.quote_chars)) {
1592 		real_quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1593 		quotelevel = real_quotelevel;
1594 		/* set up the correct foreground color */
1595 		if (quotelevel > 2) {
1596 			/* recycle colors */
1597 			if (prefs_common.recycle_quote_colors)
1598 				quotelevel %= 3;
1599 			else
1600 				quotelevel = 2;
1601 		}
1602 	}
1603 
1604 	if (quotelevel == -1)
1605 		fg_color = NULL;
1606 	else {
1607 		g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1608 			   "quote%d", quotelevel);
1609 		fg_color = quote_tag_str;
1610 	}
1611 
1612 	if (prefs_common.enable_color) {
1613 		if (textview->is_diff || textview->is_in_git_patch) {
1614 			if (strncmp(buf, "+++ ", 4) == 0)
1615 				fg_color = "diff-add-file";
1616 			else if (buf[0] == '+')
1617 				fg_color = "diff-add";
1618 			else if (strncmp(buf, "--- ", 4) == 0)
1619 				fg_color = "diff-del-file";
1620 			else if (buf[0] == '-')
1621 				fg_color = "diff-del";
1622 			else if (strncmp(buf, "@@ ", 3) == 0 &&
1623 				 strstr(&buf[3], " @@"))
1624 				fg_color = "diff-hunk";
1625 
1626 			if (account_sigsep_matchlist_nchar_found(buf, "%s\n")) {
1627 				textview->is_in_git_patch = FALSE;
1628 				textview->is_in_signature = TRUE;
1629 				fg_color = "signature";
1630 			}
1631 		} else if (account_sigsep_matchlist_str_found(buf, "%s\n")
1632 				|| account_sigsep_matchlist_str_found(buf, "- %s\n")
1633 				|| textview->is_in_signature) {
1634 			fg_color = "signature";
1635 			textview->is_in_signature = TRUE;
1636 		} else if (strncmp(buf, "diff --git ", 11) == 0) {
1637 			textview->is_in_git_patch = TRUE;
1638 		}
1639 	}
1640 
1641 	if (!textview->is_attachment && real_quotelevel > -1 && do_quote_folding) {
1642 		if (!g_utf8_validate(buf, -1, NULL)) {
1643 			gchar *utf8buf = NULL;
1644 			utf8buf = g_malloc(BUFFSIZE);
1645 			conv_localetodisp(utf8buf, BUFFSIZE, buf);
1646 			strncpy2(buf, utf8buf, BUFFSIZE-1);
1647 			g_free(utf8buf);
1648 		}
1649 do_quote:
1650 		if ( textview->prev_quote_level != real_quotelevel ) {
1651 			ClickableText *uri;
1652 			uri = g_new0(ClickableText, 1);
1653 			uri->uri = g_strdup("");
1654 			uri->data = g_strdup(buf);
1655 			uri->data_len = strlen(uri->data);
1656 			uri->start = gtk_text_iter_get_offset(&iter);
1657 			uri->is_quote = TRUE;
1658 			uri->quote_level = real_quotelevel;
1659 			uri->fg_color = g_strdup(fg_color);
1660 
1661 			gtk_text_buffer_insert_with_tags_by_name
1662 					(buffer, &iter, " [...]", -1,
1663 					 "qlink", fg_color, NULL);
1664 			uri->end = gtk_text_iter_get_offset(&iter);
1665 			gtk_text_buffer_insert(buffer, &iter, "  \n", -1);
1666 
1667 			uri->filename = NULL;
1668 			textview->uri_list =
1669 				g_slist_prepend(textview->uri_list, uri);
1670 
1671 			textview->prev_quote_level = real_quotelevel;
1672 		} else {
1673 			GSList *last = textview->uri_list;
1674 			ClickableText *lasturi = NULL;
1675 			gint e_len = 0, n_len = 0;
1676 
1677 			if (textview->uri_list) {
1678 				lasturi = (ClickableText *)last->data;
1679 			} else {
1680 				g_print("oops (%d %d)\n",
1681 					real_quotelevel, textview->prev_quote_level);
1682 			}
1683 			if (lasturi) {
1684 				if (lasturi->is_quote == FALSE) {
1685 					textview->prev_quote_level = -1;
1686 					goto do_quote;
1687 				}
1688 				e_len = lasturi->data ? lasturi->data_len:0;
1689 				n_len = strlen(buf);
1690 				lasturi->data = g_realloc((gchar *)lasturi->data, e_len + n_len + 1);
1691 				strcpy((gchar *)lasturi->data + e_len, buf);
1692 				*((gchar *)lasturi->data + e_len + n_len) = '\0';
1693 				lasturi->data_len += n_len;
1694 			}
1695 		}
1696 	} else {
1697 		textview_make_clickable_parts(textview, fg_color, "link", buf, FALSE);
1698 		textview->prev_quote_level = -1;
1699 	}
1700 }
1701 
textview_write_link(TextView * textview,const gchar * str,const gchar * uri,CodeConverter * conv)1702 void textview_write_link(TextView *textview, const gchar *str,
1703 			 const gchar *uri, CodeConverter *conv)
1704 {
1705 	GtkTextView *text;
1706 	GtkTextBuffer *buffer;
1707 	GtkTextIter iter;
1708 	gchar buf[BUFFSIZE];
1709 	gchar *bufp;
1710 	ClickableText *r_uri;
1711 
1712 	if (!str || *str == '\0')
1713 		return;
1714 	if (!uri)
1715 		return;
1716 
1717 	while (uri && *uri && g_ascii_isspace(*uri))
1718 		uri++;
1719 
1720 	text = GTK_TEXT_VIEW(textview->text);
1721 	buffer = gtk_text_view_get_buffer(text);
1722 	gtk_text_buffer_get_end_iter(buffer, &iter);
1723 
1724 	if (!conv)
1725 		strncpy2(buf, str, sizeof(buf));
1726 	else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1727 		conv_utf8todisp(buf, sizeof(buf), str);
1728 
1729 	if (g_utf8_validate(buf, -1, NULL) == FALSE)
1730 		return;
1731 
1732 	strcrchomp(buf);
1733 
1734 	gtk_text_buffer_get_end_iter(buffer, &iter);
1735 	for (bufp = buf; *bufp != '\0'; bufp = g_utf8_next_char(bufp)) {
1736 		gunichar ch;
1737 
1738 		ch = g_utf8_get_char(bufp);
1739 		if (!g_unichar_isspace(ch))
1740 			break;
1741 	}
1742 	if (bufp > buf)
1743 		gtk_text_buffer_insert(buffer, &iter, buf, bufp - buf);
1744 
1745 	r_uri = g_new0(ClickableText, 1);
1746 	r_uri->uri = g_strdup(uri);
1747 	r_uri->start = gtk_text_iter_get_offset(&iter);
1748 	gtk_text_buffer_insert_with_tags_by_name
1749 		(buffer, &iter, bufp, -1, "link", NULL);
1750 	r_uri->end = gtk_text_iter_get_offset(&iter);
1751 	r_uri->filename = NULL;
1752 	textview->uri_list = g_slist_prepend(textview->uri_list, r_uri);
1753 }
1754 
textview_set_cursor(GdkWindow * window,GdkCursor * cursor)1755 static void textview_set_cursor(GdkWindow *window, GdkCursor *cursor)
1756 {
1757 	if (GDK_IS_WINDOW(window))
1758 		gdk_window_set_cursor(window, cursor);
1759 }
textview_clear(TextView * textview)1760 void textview_clear(TextView *textview)
1761 {
1762 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1763 	GtkTextBuffer *buffer;
1764 	GdkWindow *window = gtk_text_view_get_window(text,
1765 				GTK_TEXT_WINDOW_TEXT);
1766 
1767 	buffer = gtk_text_view_get_buffer(text);
1768 	gtk_text_buffer_set_text(buffer, "", -1);
1769 	if (gtk_text_buffer_get_mark(buffer, "body_start"))
1770 		gtk_text_buffer_delete_mark_by_name(buffer, "body_start");
1771 	if (gtk_text_buffer_get_mark(buffer, "body_end"))
1772 		gtk_text_buffer_delete_mark_by_name(buffer, "body_end");
1773 
1774 	TEXTVIEW_STATUSBAR_POP(textview);
1775 	textview_uri_list_remove_all(textview->uri_list);
1776 	textview->uri_list = NULL;
1777 	textview->uri_hover = NULL;
1778 	textview->prev_quote_level = -1;
1779 
1780 	textview->body_pos = 0;
1781 	if (textview->image)
1782 		gtk_widget_destroy(textview->image);
1783 	textview->image = NULL;
1784 	textview->avatar_type = 0;
1785 
1786 	if (textview->messageview->mainwin->cursor_count == 0) {
1787 		textview_set_cursor(window, text_cursor);
1788 	} else {
1789 		textview_set_cursor(window, watch_cursor);
1790 	}
1791 }
1792 
textview_destroy(TextView * textview)1793 void textview_destroy(TextView *textview)
1794 {
1795 	GtkTextBuffer *buffer;
1796 	GtkClipboard *clipboard;
1797 
1798 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1799 	clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1800 	gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
1801 
1802 	textview_uri_list_remove_all(textview->uri_list);
1803 	textview->uri_list = NULL;
1804 	textview->prev_quote_level = -1;
1805 
1806 	g_free(textview);
1807 }
1808 
1809 #define CHANGE_TAG_FONT(tagname, font) { \
1810 	tag = gtk_text_tag_table_lookup(tags, tagname); \
1811 	if (tag) \
1812 		g_object_set(G_OBJECT(tag), "font-desc", font, NULL); \
1813 }
1814 
textview_set_font(TextView * textview,const gchar * codeset)1815 void textview_set_font(TextView *textview, const gchar *codeset)
1816 {
1817 	GtkTextTag *tag;
1818 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
1819 	GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
1820 	PangoFontDescription *font_desc, *bold_font_desc;
1821 
1822 	font_desc = pango_font_description_from_string
1823 					(NORMAL_FONT);
1824 	if (font_desc) {
1825 		gtk_widget_modify_font(textview->text, font_desc);
1826 		CHANGE_TAG_FONT("header", font_desc);
1827 		CHANGE_TAG_FONT("hlink", font_desc);
1828 		pango_font_description_free(font_desc);
1829 	}
1830 	if (prefs_common.derive_from_normal_font || !BOLD_FONT) {
1831 		bold_font_desc = pango_font_description_from_string
1832 						(NORMAL_FONT);
1833 		if (bold_font_desc)
1834 			pango_font_description_set_weight
1835 				(bold_font_desc, PANGO_WEIGHT_BOLD);
1836 	} else {
1837 		bold_font_desc = pango_font_description_from_string
1838 						(BOLD_FONT);
1839 	}
1840 	if (bold_font_desc) {
1841 		CHANGE_TAG_FONT("header_title", bold_font_desc);
1842 		pango_font_description_free(bold_font_desc);
1843 	}
1844 
1845 	if (prefs_common.textfont) {
1846 		PangoFontDescription *font_desc;
1847 
1848 		font_desc = pango_font_description_from_string
1849 						(prefs_common.textfont);
1850 		if (font_desc) {
1851 			gtk_widget_modify_font(textview->text, font_desc);
1852 			pango_font_description_free(font_desc);
1853 		}
1854 	}
1855 	gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1856 					     prefs_common.line_space / 2);
1857 	gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1858 					     prefs_common.line_space / 2);
1859 }
1860 
textview_set_text(TextView * textview,const gchar * text)1861 void textview_set_text(TextView *textview, const gchar *text)
1862 {
1863 	GtkTextView *view;
1864 	GtkTextBuffer *buffer;
1865 
1866 	cm_return_if_fail(textview != NULL);
1867 	cm_return_if_fail(text != NULL);
1868 
1869 	textview_clear(textview);
1870 
1871 	view = GTK_TEXT_VIEW(textview->text);
1872 	buffer = gtk_text_view_get_buffer(view);
1873 	gtk_text_buffer_set_text(buffer, text, strlen(text));
1874 }
1875 
1876 enum
1877 {
1878 	H_DATE		= 0,
1879 	H_FROM		= 1,
1880 	H_TO		= 2,
1881 	H_NEWSGROUPS	= 3,
1882 	H_SUBJECT	= 4,
1883 	H_CC		= 5,
1884 	H_REPLY_TO	= 6,
1885 	H_FOLLOWUP_TO	= 7,
1886 	H_X_MAILER	= 8,
1887 	H_X_NEWSREADER	= 9,
1888 	H_USER_AGENT	= 10,
1889 	H_ORGANIZATION	= 11,
1890 };
1891 
textview_set_position(TextView * textview,gint pos)1892 void textview_set_position(TextView *textview, gint pos)
1893 {
1894 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1895 
1896 	gtkut_text_view_set_position(text, pos);
1897 }
1898 
textview_scan_header(TextView * textview,FILE * fp)1899 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1900 {
1901 	gchar buf[BUFFSIZE];
1902 	GPtrArray *headers, *sorted_headers;
1903 	GSList *disphdr_list;
1904 	Header *header;
1905 	gint i;
1906 
1907 	cm_return_val_if_fail(fp != NULL, NULL);
1908 
1909 	if (prefs_common.show_all_headers) {
1910 		headers = procheader_get_header_array_asis(fp);
1911 		sorted_headers = g_ptr_array_new();
1912 		for (i = 0; i < headers->len; i++) {
1913 			header = g_ptr_array_index(headers, i);
1914 			if (!procheader_header_is_internal(header->name))
1915 				g_ptr_array_add(sorted_headers, header);
1916 			else
1917 				procheader_header_free(header);
1918 		}
1919 		g_ptr_array_free(headers, TRUE);
1920 		return sorted_headers;
1921 	}
1922 
1923 	if (!prefs_common.display_header) {
1924 		while (claws_fgets(buf, sizeof(buf), fp) != NULL)
1925 			if (buf[0] == '\r' || buf[0] == '\n') break;
1926 		return NULL;
1927 	}
1928 
1929 	headers = procheader_get_header_array_asis(fp);
1930 
1931 	sorted_headers = g_ptr_array_new();
1932 
1933 	for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1934 	     disphdr_list = disphdr_list->next) {
1935 		DisplayHeaderProp *dp =
1936 			(DisplayHeaderProp *)disphdr_list->data;
1937 
1938 		for (i = 0; i < headers->len; i++) {
1939 			header = g_ptr_array_index(headers, i);
1940 
1941 			if (procheader_headername_equal(header->name,
1942 							dp->name)) {
1943 				if (dp->hidden)
1944 					procheader_header_free(header);
1945 				else
1946 					g_ptr_array_add(sorted_headers, header);
1947 
1948 				g_ptr_array_remove_index(headers, i);
1949 				i--;
1950 			}
1951 		}
1952 	}
1953 
1954 	if (prefs_common.show_other_header) {
1955 		for (i = 0; i < headers->len; i++) {
1956 			header = g_ptr_array_index(headers, i);
1957 			if (!procheader_header_is_internal(header->name)) {
1958 				g_ptr_array_add(sorted_headers, header);
1959 			} else {
1960 				procheader_header_free(header);
1961 			}
1962 		}
1963 		g_ptr_array_free(headers, TRUE);
1964 	} else
1965 		procheader_header_array_destroy(headers);
1966 
1967 
1968 	return sorted_headers;
1969 }
1970 
textview_show_avatar(TextView * textview)1971 static void textview_show_avatar(TextView *textview)
1972 {
1973 	GtkAllocation allocation;
1974 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1975 	MsgInfo *msginfo = textview->messageview->msginfo;
1976 	int x = 0;
1977 	AvatarRender *avatarr;
1978 
1979 	if (prefs_common.display_header_pane || !prefs_common.display_xface)
1980 		goto bail;
1981 
1982 	avatarr = avatars_avatarrender_new(msginfo);
1983 	hooks_invoke(AVATAR_IMAGE_RENDER_HOOKLIST, avatarr);
1984 
1985 	if (!avatarr->image) {
1986 		avatars_avatarrender_free(avatarr);
1987 		goto bail;
1988 	}
1989 
1990 	if (textview->image)
1991 		gtk_widget_destroy(textview->image);
1992 
1993 	textview->image = avatarr->image;
1994 	textview->avatar_type = avatarr->type;
1995 	avatarr->image = NULL; /* avoid destroying */
1996 	avatars_avatarrender_free(avatarr);
1997 
1998 	gtk_widget_show(textview->image);
1999 
2000 	gtk_widget_get_allocation(textview->text, &allocation);
2001 	x = allocation.width - WIDTH -5;
2002 
2003 	gtk_text_view_add_child_in_window(text, textview->image,
2004 		GTK_TEXT_WINDOW_TEXT, x, 5);
2005 
2006 	gtk_widget_show_all(textview->text);
2007 
2008 	return;
2009 bail:
2010 	if (textview->image)
2011 		gtk_widget_destroy(textview->image);
2012 	textview->image = NULL;
2013 	textview->avatar_type = 0;
2014 }
2015 
textview_show_icon(TextView * textview,const gchar * stock_id)2016 void textview_show_icon(TextView *textview, const gchar *stock_id)
2017 {
2018 	GtkAllocation allocation;
2019 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
2020 	int x = 0;
2021 
2022 	if (textview->image)
2023 		gtk_widget_destroy(textview->image);
2024 
2025 	textview->image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_DIALOG);
2026 	cm_return_if_fail(textview->image != NULL);
2027 
2028 	gtk_widget_show(textview->image);
2029 
2030 	gtk_widget_get_allocation(textview->text, &allocation);
2031 	x = allocation.width - WIDTH -5;
2032 
2033 	gtk_text_view_add_child_in_window(text, textview->image,
2034 		GTK_TEXT_WINDOW_TEXT, x, 5);
2035 
2036 	gtk_widget_show_all(textview->text);
2037 
2038 
2039 	return;
2040 }
2041 
textview_save_contact_pic(TextView * textview)2042 static void textview_save_contact_pic(TextView *textview)
2043 {
2044 #ifndef USE_ALT_ADDRBOOK
2045 	MsgInfo *msginfo = textview->messageview->msginfo;
2046 	gchar *filename = NULL;
2047 	GError *error = NULL;
2048 	GdkPixbuf *picture = NULL;
2049 
2050 	if (!msginfo->extradata || !msginfo->extradata->avatars)
2051 		return;
2052 
2053 	if (textview->avatar_type > AVATAR_FACE)
2054 		return;
2055 
2056 	if (textview->image)
2057 		picture = gtk_image_get_pixbuf(GTK_IMAGE(textview->image));
2058 
2059 	filename = addrindex_get_picture_file(msginfo->from);
2060 	if (!filename)
2061 		return;
2062 	if (!is_file_exist(filename)) {
2063 		gdk_pixbuf_save(picture, filename, "png", &error, NULL);
2064 		if (error) {
2065 			g_warning("Failed to save image: %s",
2066 					error->message);
2067 			g_error_free(error);
2068 		}
2069 	}
2070 	g_free(filename);
2071 #else
2072 	/* new address book */
2073 #endif
2074 }
2075 
textview_show_contact_pic(TextView * textview)2076 static void textview_show_contact_pic(TextView *textview)
2077 {
2078 #ifndef USE_ALT_ADDRBOOK
2079 	MsgInfo *msginfo = textview->messageview->msginfo;
2080 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
2081 	int x = 0;
2082 	gchar *filename = NULL;
2083 	GError *error = NULL;
2084 	GdkPixbuf *picture = NULL;
2085 	gint w, h;
2086 	GtkAllocation allocation;
2087 
2088 	if (prefs_common.display_header_pane
2089 		|| !prefs_common.display_xface)
2090 		goto bail;
2091 
2092 	if (msginfo->extradata && msginfo->extradata->avatars)
2093 		return;
2094 
2095 	if (textview->image)
2096 		gtk_widget_destroy(textview->image);
2097 
2098 	filename = addrindex_get_picture_file(msginfo->from);
2099 
2100 	if (!filename)
2101 		goto bail;
2102 	if (!is_file_exist(filename)) {
2103 		g_free(filename);
2104 		goto bail;
2105 	}
2106 
2107 	gdk_pixbuf_get_file_info(filename, &w, &h);
2108 
2109 	if (w > 48 || h > 48)
2110 		picture = gdk_pixbuf_new_from_file_at_scale(filename,
2111 						48, 48, TRUE, &error);
2112 	else
2113 		picture = gdk_pixbuf_new_from_file(filename, &error);
2114 
2115 	if (error) {
2116 		debug_print("Failed to import image: %s\n",
2117 				error->message);
2118 		g_error_free(error);
2119 		goto bail;
2120 	}
2121 	g_free(filename);
2122 
2123 	if (picture) {
2124 		textview->image = gtk_image_new_from_pixbuf(picture);
2125 		g_object_unref(picture);
2126 	}
2127 	cm_return_if_fail(textview->image != NULL);
2128 
2129 	gtk_widget_show(textview->image);
2130 
2131 	gtk_widget_get_allocation(textview->text, &allocation);
2132 	x = allocation.width - WIDTH -5;
2133 
2134 	gtk_text_view_add_child_in_window(text, textview->image,
2135 		GTK_TEXT_WINDOW_TEXT, x, 5);
2136 
2137 	gtk_widget_show_all(textview->text);
2138 
2139 	return;
2140 bail:
2141 	if (textview->image)
2142 		gtk_widget_destroy(textview->image);
2143 	textview->image = NULL;
2144 	textview->avatar_type = 0;
2145 #else
2146 	/* new address book */
2147 #endif
2148 }
2149 
textview_tag_cmp_list(gconstpointer a,gconstpointer b)2150 static gint textview_tag_cmp_list(gconstpointer a, gconstpointer b)
2151 {
2152 	gint id_a = GPOINTER_TO_INT(a);
2153 	gint id_b = GPOINTER_TO_INT(b);
2154 	const gchar *tag_a = tags_get_tag(id_a);
2155 	const gchar *tag_b = tags_get_tag(id_b);
2156 
2157 	if (tag_a == NULL)
2158 		return tag_b == NULL ? 0:1;
2159 
2160 	if (tag_b == NULL)
2161 		return 1;
2162 
2163 	return g_utf8_collate(tag_a, tag_b);
2164 }
2165 
2166 
textview_show_tags(TextView * textview)2167 static void textview_show_tags(TextView *textview)
2168 {
2169 	MsgInfo *msginfo = textview->messageview->msginfo;
2170 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
2171 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2172 	GtkTextIter iter;
2173 	ClickableText *uri;
2174 	GSList *cur, *orig;
2175 	gboolean found_tag = FALSE;
2176 
2177 	if (!msginfo->tags)
2178 		return;
2179 
2180 	cur = orig = g_slist_sort(g_slist_copy(msginfo->tags), textview_tag_cmp_list);
2181 
2182 	for (; cur; cur = cur->next) {
2183 		if (tags_get_tag(GPOINTER_TO_INT(cur->data)) != NULL) {
2184 			found_tag = TRUE;
2185 			break;
2186 		}
2187 	}
2188 	if (!found_tag) {
2189 		g_slist_free(orig);
2190 		return;
2191 	}
2192 
2193 	gtk_text_buffer_get_end_iter (buffer, &iter);
2194 	gtk_text_buffer_insert_with_tags_by_name(buffer,
2195 		&iter, _("Tags: "), -1,
2196 		"header_title", "header", "tags", NULL);
2197 
2198 	for (cur = orig; cur; cur = cur->next) {
2199 		const gchar *cur_tag = tags_get_tag(GPOINTER_TO_INT(cur->data));
2200 		if (!cur_tag)
2201 			continue;
2202 		uri = g_new0(ClickableText, 1);
2203 		uri->uri = g_strdup("");
2204 		uri->start = gtk_text_iter_get_offset(&iter);
2205 		gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
2206 			cur_tag, -1,
2207 			"link", "header", "tags", NULL);
2208 		uri->end = gtk_text_iter_get_offset(&iter);
2209 		uri->filename = g_strdup_printf("sc://search_tags:%s", cur_tag);
2210 		uri->data = NULL;
2211 		textview->uri_list =
2212 			g_slist_prepend(textview->uri_list, uri);
2213 		if (cur->next && tags_get_tag(GPOINTER_TO_INT(cur->next->data)))
2214 			gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, ", ", 2,
2215 				"header", "tags", NULL);
2216 		else
2217 			gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, " ", 1,
2218 				"header", "tags", NULL);
2219 	}
2220 	g_slist_free(orig);
2221 
2222 	gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
2223 		"header", "tags", NULL);
2224 }
2225 
textview_show_header(TextView * textview,GPtrArray * headers)2226 static void textview_show_header(TextView *textview, GPtrArray *headers)
2227 {
2228 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
2229 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2230 	GtkTextIter iter;
2231 	Header *header;
2232 	gint i;
2233 
2234 	cm_return_if_fail(headers != NULL);
2235 
2236 	for (i = 0; i < headers->len; i++) {
2237 		header = g_ptr_array_index(headers, i);
2238 		cm_return_if_fail(header->name != NULL);
2239 
2240 		gtk_text_buffer_get_end_iter (buffer, &iter);
2241 		if(prefs_common.trans_hdr == TRUE) {
2242 			gchar *hdr = g_strndup(header->name, strlen(header->name) - 1);
2243 			gchar *trans_hdr = gettext(hdr);
2244 			gtk_text_buffer_insert_with_tags_by_name(buffer,
2245 				&iter, trans_hdr, -1,
2246 				"header_title", "header", NULL);
2247 			gtk_text_buffer_insert_with_tags_by_name(buffer,
2248 				&iter, ":", 1, "header_title", "header", NULL);
2249 			g_free(hdr);
2250 		} else {
2251 			gtk_text_buffer_insert_with_tags_by_name(buffer,
2252 				&iter, header->name,
2253 				-1, "header_title", "header", NULL);
2254 		}
2255 		if (header->name[strlen(header->name) - 1] != ' ')
2256 		gtk_text_buffer_insert_with_tags_by_name
2257 				(buffer, &iter, " ", 1,
2258 				 "header_title", "header", NULL);
2259 
2260 		if (procheader_headername_equal(header->name, "Subject") ||
2261 		    procheader_headername_equal(header->name, "From") ||
2262 		    procheader_headername_equal(header->name, "To") ||
2263 		    procheader_headername_equal(header->name, "Cc") ||
2264 		    procheader_headername_equal(header->name, "Bcc") ||
2265 		    procheader_headername_equal(header->name, "Reply-To") ||
2266 		    procheader_headername_equal(header->name, "Sender") ||
2267 		    procheader_headername_equal(header->name, "Resent-From") ||
2268 		    procheader_headername_equal(header->name, "Resent-To"))
2269 			unfold_line(header->body);
2270 
2271 		if (procheader_headername_equal(header->name, "Date") &&
2272 		    prefs_common.msgview_date_format) {
2273 			gchar hbody[80];
2274 
2275 			procheader_date_parse(hbody, header->body, sizeof(hbody));
2276 			gtk_text_buffer_get_end_iter (buffer, &iter);
2277 			gtk_text_buffer_insert_with_tags_by_name
2278 				(buffer, &iter, hbody, -1, "header", NULL);
2279 		} else if ((procheader_headername_equal(header->name, "X-Mailer") ||
2280 				procheader_headername_equal(header->name,
2281 						 "X-Newsreader")) &&
2282 				(strstr(header->body, "Claws Mail") != NULL ||
2283 				strstr(header->body, "Sylpheed-Claws") != NULL)) {
2284 			gtk_text_buffer_get_end_iter (buffer, &iter);
2285 			gtk_text_buffer_insert_with_tags_by_name
2286 				(buffer, &iter, header->body, -1,
2287 				 "header", "emphasis", NULL);
2288 		} else {
2289 			gboolean hdr =
2290 			  procheader_headername_equal(header->name, "From") ||
2291 			  procheader_headername_equal(header->name, "To") ||
2292 			  procheader_headername_equal(header->name, "Cc") ||
2293 			  procheader_headername_equal(header->name, "Bcc") ||
2294 			  procheader_headername_equal(header->name, "Reply-To") ||
2295 			  procheader_headername_equal(header->name, "Sender") ||
2296 			  procheader_headername_equal(header->name, "Resent-From") ||
2297 			  procheader_headername_equal(header->name, "Resent-To");
2298 			textview_make_clickable_parts(textview, "header",
2299 						      "hlink", header->body,
2300 						      hdr);
2301 		}
2302 		gtk_text_buffer_get_end_iter (buffer, &iter);
2303 		gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
2304 							 "header", NULL);
2305 	}
2306 
2307 	textview_show_avatar(textview);
2308 	if (prefs_common.save_xface)
2309 		textview_save_contact_pic(textview);
2310 	textview_show_contact_pic(textview);
2311 }
2312 
textview_search_string(TextView * textview,const gchar * str,gboolean case_sens)2313 gboolean textview_search_string(TextView *textview, const gchar *str,
2314 				gboolean case_sens)
2315 {
2316 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
2317 
2318 	return gtkut_text_view_search_string(text, str, case_sens);
2319 }
2320 
textview_search_string_backward(TextView * textview,const gchar * str,gboolean case_sens)2321 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
2322 					 gboolean case_sens)
2323 {
2324 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
2325 
2326 	return gtkut_text_view_search_string_backward(text, str, case_sens);
2327 }
2328 
textview_scroll_one_line(TextView * textview,gboolean up)2329 void textview_scroll_one_line(TextView *textview, gboolean up)
2330 {
2331 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
2332 	GtkAdjustment *vadj = gtk_text_view_get_vadjustment(text);
2333 
2334 	gtkutils_scroll_one_line(GTK_WIDGET(text), vadj, up);
2335 }
2336 
textview_scroll_page(TextView * textview,gboolean up)2337 gboolean textview_scroll_page(TextView *textview, gboolean up)
2338 {
2339 	GtkTextView *text = GTK_TEXT_VIEW(textview->text);
2340 	GtkAdjustment *vadj = gtk_text_view_get_vadjustment(text);
2341 
2342 	return gtkutils_scroll_page(GTK_WIDGET(text), vadj, up);
2343 }
2344 
textview_scroll_max(TextView * textview,gboolean up)2345 void textview_scroll_max(TextView *textview, gboolean up)
2346 {
2347 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2348 	GtkTextIter iter;
2349 
2350 	if (up) {
2351 		gtk_text_buffer_get_start_iter(buffer, &iter);
2352 		gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(textview->text),
2353 						&iter, 0.0, TRUE, 0.0, 1.0);
2354 
2355 	} else {
2356 		gtk_text_buffer_get_end_iter(buffer, &iter);
2357 		gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(textview->text),
2358 						&iter, 0.0, TRUE, 0.0, 0.0);
2359 	}
2360 }
2361 
2362 #define KEY_PRESS_EVENT_STOP() \
2363 	g_signal_stop_emission_by_name(G_OBJECT(widget), \
2364 				       "key_press_event");
2365 
textview_key_pressed(GtkWidget * widget,GdkEventKey * event,TextView * textview)2366 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
2367 				 TextView *textview)
2368 {
2369 	GdkWindow *window = NULL;
2370 	SummaryView *summaryview = NULL;
2371 	MessageView *messageview = textview->messageview;
2372 	gboolean mod_pressed;
2373 
2374 	if (!event) return FALSE;
2375 	if (messageview->mainwin)
2376 		summaryview = messageview->mainwin->summaryview;
2377 
2378 	switch (event->keyval) {
2379 	case GDK_KEY_Tab:
2380 	case GDK_KEY_Left:
2381 	case GDK_KEY_Up:
2382 	case GDK_KEY_Right:
2383 	case GDK_KEY_Down:
2384 	case GDK_KEY_Control_L:
2385 	case GDK_KEY_Control_R:
2386 		return FALSE;
2387 	case GDK_KEY_Home:
2388 	case GDK_KEY_End:
2389 		textview_scroll_max(textview,(event->keyval == GDK_KEY_Home));
2390 		return TRUE;
2391 	case GDK_KEY_space:
2392 		mod_pressed = ((event->state & (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
2393 		if (!mimeview_scroll_page(messageview->mimeview, mod_pressed) &&
2394 				summaryview != NULL) {
2395 			if (mod_pressed)
2396 				summary_select_prev_unread(summaryview);
2397 			else
2398 				summary_select_next_unread(summaryview);
2399 		}
2400 		break;
2401 	case GDK_KEY_Page_Down:
2402 		mimeview_scroll_page(messageview->mimeview, FALSE);
2403 		break;
2404 	case GDK_KEY_Page_Up:
2405 	case GDK_KEY_BackSpace:
2406 		mimeview_scroll_page(messageview->mimeview, TRUE);
2407 		break;
2408 	case GDK_KEY_Return:
2409 	case GDK_KEY_KP_Enter:
2410 		mimeview_scroll_one_line
2411 			(messageview->mimeview, (event->state &
2412 				    (GDK_SHIFT_MASK|GDK_MOD1_MASK)) != 0);
2413 		break;
2414 	case GDK_KEY_Delete:
2415 		if (summaryview)
2416 			summary_pass_key_press_event(summaryview, event);
2417 		break;
2418 	default:
2419 		if (messageview->mainwin) {
2420 			window = gtk_widget_get_window(messageview->mainwin->window);
2421 			if (summaryview &&
2422 			    event->window != window) {
2423 				GdkEventKey tmpev = *event;
2424 
2425 				tmpev.window = window;
2426 				KEY_PRESS_EVENT_STOP();
2427 				gtk_widget_event(messageview->mainwin->window,
2428 						 (GdkEvent *)&tmpev);
2429 			}
2430 		}
2431 		break;
2432 	}
2433 
2434 	return TRUE;
2435 }
2436 
textview_motion_notify(GtkWidget * widget,GdkEventMotion * event,TextView * textview)2437 static gboolean textview_motion_notify(GtkWidget *widget,
2438 				       GdkEventMotion *event,
2439 				       TextView *textview)
2440 {
2441 	if (textview->loading)
2442 		return FALSE;
2443 	textview_uri_update(textview, event->x, event->y);
2444 	gdk_window_get_pointer(gtk_widget_get_window(widget), NULL, NULL, NULL);
2445 
2446 	return FALSE;
2447 }
2448 
textview_leave_notify(GtkWidget * widget,GdkEventCrossing * event,TextView * textview)2449 static gboolean textview_leave_notify(GtkWidget *widget,
2450 				      GdkEventCrossing *event,
2451 				      TextView *textview)
2452 {
2453 	if (textview->loading)
2454 		return FALSE;
2455 	textview_uri_update(textview, -1, -1);
2456 
2457 	return FALSE;
2458 }
2459 
textview_visibility_notify(GtkWidget * widget,GdkEventVisibility * event,TextView * textview)2460 static gboolean textview_visibility_notify(GtkWidget *widget,
2461 					   GdkEventVisibility *event,
2462 					   TextView *textview)
2463 {
2464 	gint wx, wy;
2465 	GdkWindow *window;
2466 
2467 	if (textview->loading)
2468 		return FALSE;
2469 
2470 	window = gtk_text_view_get_window(GTK_TEXT_VIEW(widget),
2471 					  GTK_TEXT_WINDOW_TEXT);
2472 
2473 	/* check if occurred for the text window part */
2474 	if (window != event->window)
2475 		return FALSE;
2476 
2477 	gdk_window_get_pointer(gtk_widget_get_window(widget), &wx, &wy, NULL);
2478 	textview_uri_update(textview, wx, wy);
2479 
2480 	return FALSE;
2481 }
2482 
textview_cursor_wait(TextView * textview)2483 void textview_cursor_wait(TextView *textview)
2484 {
2485 	GdkWindow *window = gtk_text_view_get_window(
2486 			GTK_TEXT_VIEW(textview->text),
2487 			GTK_TEXT_WINDOW_TEXT);
2488 	textview_set_cursor(window, watch_cursor);
2489 }
2490 
textview_cursor_normal(TextView * textview)2491 void textview_cursor_normal(TextView *textview)
2492 {
2493 	GdkWindow *window = gtk_text_view_get_window(
2494 			GTK_TEXT_VIEW(textview->text),
2495 			GTK_TEXT_WINDOW_TEXT);
2496 	textview_set_cursor(window, text_cursor);
2497 }
2498 
textview_uri_update(TextView * textview,gint x,gint y)2499 static void textview_uri_update(TextView *textview, gint x, gint y)
2500 {
2501 	GtkTextBuffer *buffer;
2502 	GtkTextIter start_iter, end_iter;
2503 	ClickableText *uri = NULL;
2504 
2505 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2506 
2507 	if (x != -1 && y != -1) {
2508 		gint bx, by;
2509 		GtkTextIter iter;
2510 		GSList *tags;
2511 		GSList *cur;
2512 
2513 		gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(textview->text),
2514 						      GTK_TEXT_WINDOW_WIDGET,
2515 						      x, y, &bx, &by);
2516 		gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(textview->text),
2517 						   &iter, bx, by);
2518 
2519 		tags = gtk_text_iter_get_tags(&iter);
2520 		for (cur = tags; cur != NULL; cur = cur->next) {
2521 			GtkTextTag *tag = cur->data;
2522 			char *name;
2523 
2524 			g_object_get(G_OBJECT(tag), "name", &name, NULL);
2525 
2526 			if ((!strcmp(name, "link") || !strcmp(name, "hlink"))
2527 			    && textview_get_uri_range(textview, &iter, tag,
2528 						      &start_iter, &end_iter)) {
2529 
2530 				uri = textview_get_uri_from_range(textview,
2531 								  &iter, tag,
2532 								  &start_iter,
2533 								  &end_iter);
2534 			}
2535 			g_free(name);
2536 			if (uri)
2537 				break;
2538 		}
2539 		g_slist_free(tags);
2540 	}
2541 
2542 	if (uri != textview->uri_hover) {
2543 		GdkWindow *window;
2544 
2545 		if (textview->uri_hover)
2546 			gtk_text_buffer_remove_tag_by_name(buffer,
2547 							   "link-hover",
2548 							   &textview->uri_hover_start_iter,
2549 							   &textview->uri_hover_end_iter);
2550 
2551 		textview->uri_hover = uri;
2552 		if (uri) {
2553 			textview->uri_hover_start_iter = start_iter;
2554 			textview->uri_hover_end_iter = end_iter;
2555 		}
2556 
2557 		window = gtk_text_view_get_window(GTK_TEXT_VIEW(textview->text),
2558 						  GTK_TEXT_WINDOW_TEXT);
2559 		if (textview->messageview->mainwin->cursor_count == 0) {
2560 			textview_set_cursor(window, uri ? hand_cursor : text_cursor);
2561 		} else {
2562 			textview_set_cursor(window, watch_cursor);
2563 		}
2564 
2565 		TEXTVIEW_STATUSBAR_POP(textview);
2566 
2567 		if (uri) {
2568 			if (!uri->is_quote)
2569 				gtk_text_buffer_apply_tag_by_name(buffer,
2570 							  "link-hover",
2571 							  &start_iter,
2572 							  &end_iter);
2573 			TEXTVIEW_STATUSBAR_PUSH(textview, uri->uri);
2574 		}
2575 	}
2576 }
2577 
textview_get_uri_range(TextView * textview,GtkTextIter * iter,GtkTextTag * tag,GtkTextIter * start_iter,GtkTextIter * end_iter)2578 static gboolean textview_get_uri_range(TextView *textview,
2579 				       GtkTextIter *iter,
2580 				       GtkTextTag *tag,
2581 				       GtkTextIter *start_iter,
2582 				       GtkTextIter *end_iter)
2583 {
2584 	return get_tag_range(iter, tag, start_iter, end_iter);
2585 }
2586 
textview_get_uri_from_range(TextView * textview,GtkTextIter * iter,GtkTextTag * tag,GtkTextIter * start_iter,GtkTextIter * end_iter)2587 static ClickableText *textview_get_uri_from_range(TextView *textview,
2588 					      GtkTextIter *iter,
2589 					      GtkTextTag *tag,
2590 					      GtkTextIter *start_iter,
2591 					      GtkTextIter *end_iter)
2592 {
2593 	gint start_pos, end_pos, cur_pos;
2594 	ClickableText *uri = NULL;
2595 	GSList *cur;
2596 
2597 	start_pos = gtk_text_iter_get_offset(start_iter);
2598 	end_pos = gtk_text_iter_get_offset(end_iter);
2599 	cur_pos = gtk_text_iter_get_offset(iter);
2600 
2601 	for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
2602 		ClickableText *uri_ = (ClickableText *)cur->data;
2603 		if (start_pos == uri_->start &&
2604 		    end_pos ==  uri_->end) {
2605 			uri = uri_;
2606 			break;
2607 		}
2608 	}
2609 	for (cur = textview->uri_list; uri == NULL && cur != NULL; cur = cur->next) {
2610 		ClickableText *uri_ = (ClickableText *)cur->data;
2611 		if (start_pos == uri_->start ||
2612 			   end_pos == uri_->end) {
2613 			/* in case of contiguous links, textview_get_uri_range
2614 			 * returns a broader range (start of 1st link to end
2615 			 * of last link).
2616 			 * In that case, correct link is the one covering
2617 			 * current iter.
2618 			 */
2619 			if (uri_->start <= cur_pos && cur_pos <= uri_->end) {
2620 				uri = uri_;
2621 				break;
2622 			}
2623 		}
2624 	}
2625 
2626 	return uri;
2627 }
2628 
textview_get_uri(TextView * textview,GtkTextIter * iter,GtkTextTag * tag)2629 static ClickableText *textview_get_uri(TextView *textview,
2630 				   GtkTextIter *iter,
2631 				   GtkTextTag *tag)
2632 {
2633 	GtkTextIter start_iter, end_iter;
2634 	ClickableText *uri = NULL;
2635 
2636 	if (textview_get_uri_range(textview, iter, tag, &start_iter,
2637 				   &end_iter))
2638 		uri = textview_get_uri_from_range(textview, iter, tag,
2639 						  &start_iter, &end_iter);
2640 
2641 	return uri;
2642 }
2643 
textview_shift_uris_after(TextView * textview,GSList * start_list,gint start,gint shift)2644 static void textview_shift_uris_after(TextView *textview, GSList *start_list, gint start, gint shift)
2645 {
2646 	GSList *cur;
2647 	if (!start_list)
2648 		start_list = textview->uri_list;
2649 
2650 	for (cur = start_list; cur; cur = cur->next) {
2651 		ClickableText *uri = (ClickableText *)cur->data;
2652 		if (uri->start <= start)
2653 			continue;
2654 		uri->start += shift;
2655 		uri->end += shift;
2656 	}
2657 }
2658 
textview_remove_uris_in(TextView * textview,gint start,gint end)2659 static void textview_remove_uris_in(TextView *textview, gint start, gint end)
2660 {
2661 	GSList *cur;
2662 	for (cur = textview->uri_list; cur; ) {
2663 		ClickableText *uri = (ClickableText *)cur->data;
2664 		if (uri->start > start && uri->end < end) {
2665 			cur = cur->next;
2666 			textview->uri_list = g_slist_remove(textview->uri_list, uri);
2667 			g_free(uri->uri);
2668 			g_free(uri->filename);
2669 			if (uri->is_quote) {
2670 				g_free(uri->fg_color);
2671 				g_free(uri->data);
2672 				/* (only free data in quotes uris) */
2673 			}
2674 			g_free(uri);
2675 		} else {
2676 			cur = cur->next;
2677 		}
2678 
2679 	}
2680 }
2681 
textview_toggle_quote(TextView * textview,GSList * start_list,ClickableText * uri,gboolean expand_only)2682 static void textview_toggle_quote(TextView *textview, GSList *start_list, ClickableText *uri, gboolean expand_only)
2683 {
2684 	GtkTextIter start, end;
2685 	GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2686 
2687 	if (!uri->is_quote)
2688 		return;
2689 
2690 	if (uri->q_expanded && expand_only)
2691 		return;
2692 
2693 	gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2694 	gtk_text_buffer_get_iter_at_offset(buffer, &end,   uri->end);
2695 	if (textview->uri_hover)
2696 		gtk_text_buffer_remove_tag_by_name(buffer,
2697 						   "link-hover",
2698 						   &textview->uri_hover_start_iter,
2699 						   &textview->uri_hover_end_iter);
2700 	textview->uri_hover = NULL;
2701 	gtk_text_buffer_remove_tag_by_name(buffer,
2702 					   "qlink",
2703 					   &start,
2704 					   &end);
2705 	/* when shifting URIs start and end, we have to do it per-UTF8-char
2706 	 * so use g_utf8_strlen(). OTOH, when inserting in the text buffer,
2707 	 * we have to pass a number of bytes, so use strlen(). disturbing. */
2708 
2709 	if (!uri->q_expanded) {
2710 		gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2711 		gtk_text_buffer_get_iter_at_offset(buffer, &end,   uri->end);
2712 		textview_shift_uris_after(textview, start_list, uri->start,
2713 			g_utf8_strlen((gchar *)uri->data, -1)-strlen(" [...]\n"));
2714 		gtk_text_buffer_delete(buffer, &start, &end);
2715 		gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2716 		gtk_text_buffer_insert_with_tags_by_name
2717 				(buffer, &start, (gchar *)uri->data,
2718 				 strlen((gchar *)uri->data)-1,
2719 				 "qlink", (gchar *)uri->fg_color, NULL);
2720 		uri->end = gtk_text_iter_get_offset(&start);
2721 		textview_make_clickable_parts_later(textview,
2722 					  uri->start, uri->end);
2723 		uri->q_expanded = TRUE;
2724 	} else {
2725 		gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2726 		gtk_text_buffer_get_iter_at_offset(buffer, &end,   uri->end);
2727 		textview_remove_uris_in(textview, uri->start, uri->end);
2728 		textview_shift_uris_after(textview, start_list, uri->start,
2729 			strlen(" [...]\n")-g_utf8_strlen((gchar *)uri->data, -1));
2730 		gtk_text_buffer_delete(buffer, &start, &end);
2731 		gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2732 		gtk_text_buffer_insert_with_tags_by_name
2733 				(buffer, &start, " [...]", -1,
2734 				 "qlink", (gchar *)uri->fg_color, NULL);
2735 		uri->end = gtk_text_iter_get_offset(&start);
2736 		uri->q_expanded = FALSE;
2737 	}
2738 	if (textview->messageview->mainwin->cursor_count == 0) {
2739 		textview_cursor_normal(textview);
2740 	} else {
2741 		textview_cursor_wait(textview);
2742 	}
2743 }
textview_uri_button_pressed(GtkTextTag * tag,GObject * obj,GdkEvent * event,GtkTextIter * iter,TextView * textview)2744 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
2745 					    GdkEvent *event, GtkTextIter *iter,
2746 					    TextView *textview)
2747 {
2748 	GdkEventButton *bevent;
2749 	ClickableText *uri = NULL;
2750 	char *tagname;
2751 	gboolean qlink = FALSE;
2752 
2753 	if (!event)
2754 		return FALSE;
2755 
2756 	if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
2757 		&& event->type != GDK_MOTION_NOTIFY)
2758 		return FALSE;
2759 
2760 	uri = textview_get_uri(textview, iter, tag);
2761 	if (!uri)
2762 		return FALSE;
2763 
2764 	g_object_get(G_OBJECT(tag), "name", &tagname, NULL);
2765 
2766 	if (!strcmp(tagname, "qlink"))
2767 		qlink = TRUE;
2768 
2769 	g_free(tagname);
2770 
2771 	bevent = (GdkEventButton *) event;
2772 
2773 	/* doubleclick: open compose / add address / browser */
2774 	if (qlink && event->type == GDK_BUTTON_PRESS && bevent->button != 1) {
2775 		/* pass rightclick through */
2776 		return FALSE;
2777 	} else if ((event->type == (qlink ? GDK_2BUTTON_PRESS:GDK_BUTTON_PRESS) && bevent->button == 1) ||
2778 		bevent->button == 2 || bevent->button == 3) {
2779 		if (uri->filename && !g_ascii_strncasecmp(uri->filename, "sc://", 5)) {
2780 			MimeView *mimeview =
2781 				(textview->messageview)?
2782 					textview->messageview->mimeview:NULL;
2783 			if (mimeview && bevent->button == 1) {
2784 				mimeview_handle_cmd(mimeview, uri->filename, NULL, uri->data);
2785 			} else if (mimeview && bevent->button == 2 &&
2786 				!g_ascii_strcasecmp(uri->filename, "sc://select_attachment")) {
2787 				mimeview_handle_cmd(mimeview, "sc://open_attachment", NULL, uri->data);
2788 			} else if (mimeview && bevent->button == 3 &&
2789 				!g_ascii_strcasecmp(uri->filename, "sc://select_attachment")) {
2790 				mimeview_handle_cmd(mimeview, "sc://menu_attachment", bevent, uri->data);
2791 			}
2792 			return TRUE;
2793 		} else if (qlink && bevent->button == 1) {
2794 			if (prefs_common.hide_quoted) {
2795 				textview_toggle_quote(textview, NULL, uri, FALSE);
2796 				return TRUE;
2797 			} else
2798 				return FALSE;
2799 		} else if (!g_ascii_strncasecmp(uri->uri, "mailto:", 7)) {
2800 			if (bevent->button == 3) {
2801 				g_object_set_data(
2802 					G_OBJECT(textview->mail_popup_menu),
2803 					"menu_button", uri);
2804 				gtk_menu_popup(GTK_MENU(textview->mail_popup_menu),
2805 					       NULL, NULL, NULL, NULL,
2806 					       bevent->button, bevent->time);
2807 			} else {
2808 				PrefsAccount *account = NULL;
2809 				FolderItem   *folder_item = NULL;
2810 				Compose *compose;
2811 
2812 				if (textview->messageview && textview->messageview->msginfo &&
2813 				    textview->messageview->msginfo->folder) {
2814 
2815 
2816 					folder_item = textview->messageview->msginfo->folder;
2817 					if (folder_item->prefs && folder_item->prefs->enable_default_account)
2818 						account = account_find_from_id(folder_item->prefs->default_account);
2819 					if (!account)
2820 						account = account_find_from_item(folder_item);
2821 				}
2822 				compose = compose_new_with_folderitem(account,
2823 								folder_item, uri->uri + 7);
2824 				compose_check_for_email_account(compose);
2825 			}
2826 			return TRUE;
2827 		} else if (g_ascii_strncasecmp(uri->uri, "file:", 5)) {
2828 			if (bevent->button == 1 &&
2829 			    textview_uri_security_check(textview, uri) == TRUE)
2830 					open_uri(uri->uri,
2831 						 prefs_common_get_uri_cmd());
2832 			else if (bevent->button == 3 && !qlink) {
2833 				g_object_set_data(
2834 					G_OBJECT(textview->link_popup_menu),
2835 					"menu_button", uri);
2836 				gtk_menu_popup(GTK_MENU(textview->link_popup_menu),
2837 					       NULL, NULL, NULL, NULL,
2838 					       bevent->button, bevent->time);
2839 			}
2840 			return TRUE;
2841 		} else {
2842 			if (bevent->button == 3 && !qlink) {
2843 				g_object_set_data(
2844 					G_OBJECT(textview->file_popup_menu),
2845 					"menu_button", uri);
2846 				gtk_menu_popup(GTK_MENU(textview->file_popup_menu),
2847 					       NULL, NULL, NULL, NULL,
2848 					       bevent->button, bevent->time);
2849 				return TRUE;
2850 			}
2851 		}
2852 	}
2853 
2854 	return FALSE;
2855 }
2856 
textview_get_visible_uri(TextView * textview,ClickableText * uri)2857 gchar *textview_get_visible_uri		(TextView 	*textview,
2858 					 ClickableText 	*uri)
2859 {
2860 	GtkTextBuffer *buffer;
2861 	GtkTextIter start, end;
2862 
2863 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview->text));
2864 
2865 	gtk_text_buffer_get_iter_at_offset(buffer, &start, uri->start);
2866 	gtk_text_buffer_get_iter_at_offset(buffer, &end,   uri->end);
2867 
2868 	return gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2869 }
2870 
2871 /*!
2872  *\brief    Check to see if a web URL has been disguised as a different
2873  *          URL (possible with HTML email).
2874  *
2875  *\param    uri The uri to check
2876  *
2877  *\param    textview The TextView the URL is contained in
2878  *
2879  *\return   gboolean TRUE if the URL is ok, or if the user chose to open
2880  *          it anyway, otherwise FALSE
2881  */
textview_uri_security_check(TextView * textview,ClickableText * uri)2882 gboolean textview_uri_security_check(TextView *textview, ClickableText *uri)
2883 {
2884 	gchar *visible_str;
2885 	gboolean retval = TRUE;
2886 
2887 	if (is_uri_string(uri->uri) == FALSE)
2888 		return FALSE;
2889 
2890 	visible_str = textview_get_visible_uri(textview, uri);
2891 	if (visible_str == NULL)
2892 		return TRUE;
2893 
2894 	g_strstrip(visible_str);
2895 
2896 	if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
2897 		gchar *uri_path;
2898 		gchar *visible_uri_path;
2899 
2900 		uri_path = get_uri_path(uri->uri);
2901 		visible_uri_path = get_uri_path(visible_str);
2902 		if (path_cmp(uri_path, visible_uri_path) != 0)
2903 			retval = FALSE;
2904 	}
2905 
2906 	if (retval == FALSE) {
2907 		gchar *msg;
2908 		AlertValue aval;
2909 
2910 		msg = g_markup_printf_escaped("%s\n\n"
2911 						"<b>%s</b> %s\n\n"
2912 						"<b>%s</b> %s\n\n"
2913 						"%s",
2914 						_("The real URL is different from the displayed URL."),
2915 						_("Displayed URL:"), visible_str,
2916 						_("Real URL:"), uri->uri,
2917 						_("Open it anyway?"));
2918 		aval = alertpanel_full(_("Phishing attempt warning"), msg,
2919 				       GTK_STOCK_CANCEL, _("_Open URL"), NULL, ALERTFOCUS_FIRST,
2920 							 FALSE, NULL, ALERT_WARNING);
2921 		g_free(msg);
2922 		if (aval == G_ALERTALTERNATE)
2923 			retval = TRUE;
2924 	}
2925 	if (strlen(uri->uri) > get_uri_len(uri->uri))
2926 		retval = FALSE;
2927 
2928 	g_free(visible_str);
2929 
2930 	return retval;
2931 }
2932 
textview_uri_list_remove_all(GSList * uri_list)2933 static void textview_uri_list_remove_all(GSList *uri_list)
2934 {
2935 	GSList *cur;
2936 
2937 	for (cur = uri_list; cur != NULL; cur = cur->next) {
2938 		if (cur->data) {
2939 			g_free(((ClickableText *)cur->data)->uri);
2940 			g_free(((ClickableText *)cur->data)->filename);
2941 			if (((ClickableText *)cur->data)->is_quote) {
2942 				g_free(((ClickableText *)cur->data)->fg_color);
2943 				g_free(((ClickableText *)cur->data)->data);
2944 				/* (only free data in quotes uris) */
2945 			}
2946 			g_free(cur->data);
2947 		}
2948 	}
2949 
2950 	g_slist_free(uri_list);
2951 }
2952 
open_uri_cb(GtkAction * action,TextView * textview)2953 static void open_uri_cb (GtkAction *action, TextView *textview)
2954 {
2955 	ClickableText *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2956 					   "menu_button");
2957 	const gchar *raw_url = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2958 					   "raw_url");
2959 
2960 	if (uri) {
2961 		if (textview_uri_security_check(textview, uri) == TRUE)
2962 			open_uri(uri->uri,
2963 				 prefs_common_get_uri_cmd());
2964 		g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button",
2965 				  NULL);
2966 	}
2967 	if (raw_url) {
2968 		open_uri(raw_url, prefs_common_get_uri_cmd());
2969 		g_object_set_data(G_OBJECT(textview->link_popup_menu), "raw_url",
2970 				  NULL);
2971 	}
2972 }
2973 
copy_uri_cb(GtkAction * action,TextView * textview)2974 static void copy_uri_cb	(GtkAction *action, TextView *textview)
2975 {
2976 	ClickableText *uri = g_object_get_data(G_OBJECT(textview->link_popup_menu),
2977 					   "menu_button");
2978 	const gchar *raw_url =  g_object_get_data(G_OBJECT(textview->link_popup_menu),
2979 					   "raw_url");
2980 	if (uri) {
2981 		if (textview_uri_security_check(textview, uri) == TRUE) {
2982 			gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), uri->uri, -1);
2983 			gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), uri->uri, -1);
2984 			g_object_set_data(G_OBJECT(textview->link_popup_menu), "menu_button", NULL);
2985 		}
2986 	}
2987 	if (raw_url) {
2988 		gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), raw_url, -1);
2989 		gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), raw_url, -1);
2990 		g_object_set_data(G_OBJECT(textview->link_popup_menu), "raw_url", NULL);
2991 	}
2992 }
2993 
add_uri_to_addrbook_cb(GtkAction * action,TextView * textview)2994 static void add_uri_to_addrbook_cb (GtkAction *action, TextView *textview)
2995 {
2996 	gchar *fromname, *fromaddress;
2997 	ClickableText *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
2998 					   "menu_button");
2999 	AvatarRender *avatarr = NULL;
3000 	GdkPixbuf *picture = NULL;
3001 	gboolean use_picture = FALSE;
3002 
3003 	if (uri == NULL)
3004 		return;
3005 
3006 	/* extract url */
3007 	fromaddress = g_strdup(uri->uri + 7);
3008 
3009 	if (textview->messageview->msginfo &&
3010 	   !g_strcmp0(fromaddress, textview->messageview->msginfo->from))
3011 		use_picture = TRUE;
3012 
3013 	fromname = procheader_get_fromname(fromaddress);
3014 	extract_address(fromaddress);
3015 
3016 	if (use_picture) {
3017 		avatarr = avatars_avatarrender_new(textview->messageview->msginfo);
3018 		hooks_invoke(AVATAR_IMAGE_RENDER_HOOKLIST, avatarr);
3019 	}
3020 
3021 	if (avatarr && avatarr->image) {
3022 		picture = gtk_image_get_pixbuf(GTK_IMAGE(avatarr->image));
3023 	}
3024 	if (avatarr) {
3025 		avatars_avatarrender_free(avatarr);
3026 	}
3027 
3028 #ifndef USE_ALT_ADDRBOOK
3029 	addressbook_add_contact( fromname, fromaddress, NULL, picture);
3030 #else
3031 	if (addressadd_selection(fromname, fromaddress, NULL, picture)) {
3032 		debug_print( "addressbook_add_contact - added\n" );
3033 	}
3034 #endif
3035 
3036 	g_free(fromaddress);
3037 	g_free(fromname);
3038 }
3039 
reply_to_uri_cb(GtkAction * action,TextView * textview)3040 static void reply_to_uri_cb (GtkAction *action, TextView *textview)
3041 {
3042 	ClickableText *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
3043 					   "menu_button");
3044 	if (!textview->messageview || !uri)
3045 		return;
3046 
3047 	compose_reply_to_address (textview->messageview,
3048 				  textview->messageview->msginfo, uri->uri+7);
3049 }
3050 
mail_to_uri_cb(GtkAction * action,TextView * textview)3051 static void mail_to_uri_cb (GtkAction *action, TextView *textview)
3052 {
3053 	PrefsAccount *account = NULL;
3054 	Compose *compose;
3055 	ClickableText *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
3056 					   "menu_button");
3057 	if (uri == NULL)
3058 		return;
3059 
3060 	if (textview->messageview && textview->messageview->msginfo &&
3061 	    textview->messageview->msginfo->folder) {
3062 		FolderItem   *folder_item;
3063 
3064 		folder_item = textview->messageview->msginfo->folder;
3065 		if (folder_item->prefs && folder_item->prefs->enable_default_account)
3066 			account = account_find_from_id(folder_item->prefs->default_account);
3067 
3068 		compose = compose_new_with_folderitem(account, folder_item, uri->uri+7);
3069 	} else {
3070 		compose = compose_new(account, uri->uri + 7, NULL);
3071 	}
3072 	compose_check_for_email_account(compose);
3073 }
3074 
copy_mail_to_uri_cb(GtkAction * action,TextView * textview)3075 static void copy_mail_to_uri_cb	(GtkAction *action, TextView *textview)
3076 {
3077 	ClickableText *uri = g_object_get_data(G_OBJECT(textview->mail_popup_menu),
3078 					   "menu_button");
3079 	if (uri == NULL)
3080 		return;
3081 
3082 	gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), uri->uri +7, -1);
3083 	gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), uri->uri +7, -1);
3084 	g_object_set_data(G_OBJECT(textview->mail_popup_menu), "menu_button",
3085 			  NULL);
3086 }
3087 
textview_get_selection_offsets(TextView * textview,gint * sel_start,gint * sel_end)3088 void textview_get_selection_offsets(TextView *textview, gint *sel_start, gint *sel_end)
3089 {
3090 		GtkTextView *text = GTK_TEXT_VIEW(textview->text);
3091 		GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3092 		GtkTextIter start, end;
3093 		if (gtk_text_buffer_get_selection_bounds(buffer, &start, &end)) {
3094 			if (sel_start)
3095 				*sel_start = gtk_text_iter_get_offset(&start);
3096 			if (sel_end)
3097 				*sel_end = gtk_text_iter_get_offset(&end);
3098 		} else {
3099 			if (sel_start)
3100 				*sel_start = -1;
3101 			if (sel_end)
3102 				*sel_end = -1;
3103 		}
3104 }
3105