e659a4e5b8212cfe0bc1cdee1c2b3fcc55284d17
[claws.git] / src / textview.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2004 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 2 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, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <gdk/gdk.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <gtk/gtkvbox.h>
30 #include <gtk/gtkscrolledwindow.h>
31 #include <gtk/gtksignal.h>
32 #include <stdio.h>
33 #include <ctype.h>
34 #include <string.h>
35 #include <stdlib.h>
36
37 #include "intl.h"
38 #include "main.h"
39 #include "summaryview.h"
40 #include "procheader.h"
41 #include "prefs_common.h"
42 #include "codeconv.h"
43 #include "utils.h"
44 #include "gtkutils.h"
45 #include "procmime.h"
46 #include "html.h"
47 #include "enriched.h"
48 #include "compose.h"
49 #include "addressbook.h"
50 #include "displayheader.h"
51 #include "account.h"
52 #include "mimeview.h"
53 #include "alertpanel.h"
54
55 typedef struct _RemoteURI       RemoteURI;
56
57 struct _RemoteURI
58 {
59         gchar *uri;
60
61         guint start;
62         guint end;
63 };
64
65 static GdkColor quote_colors[3] = {
66         {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
67         {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
68         {(gulong)0, (gushort)0, (gushort)0, (gushort)0}
69 };
70
71 static GdkColor signature_color = {
72         (gulong)0,
73         (gushort)0x7fff,
74         (gushort)0x7fff,
75         (gushort)0x7fff
76 };
77         
78 static GdkColor uri_color = {
79         (gulong)0,
80         (gushort)0,
81         (gushort)0,
82         (gushort)0
83 };
84
85 static GdkColor emphasis_color = {
86         (gulong)0,
87         (gushort)0,
88         (gushort)0,
89         (gushort)0xcfff
90 };
91
92 #if 0
93 static GdkColor error_color = {
94         (gulong)0,
95         (gushort)0xefff,
96         (gushort)0,
97         (gushort)0
98 };
99 #endif
100
101
102 #define TEXTVIEW_STATUSBAR_PUSH(textview, str)                                      \
103 {                                                                           \
104         gtk_statusbar_push(GTK_STATUSBAR(textview->messageview->statusbar), \
105                            textview->messageview->statusbar_cid, str);      \
106 }
107
108 #define TEXTVIEW_STATUSBAR_POP(textview)                                                   \
109 {                                                                          \
110         gtk_statusbar_pop(GTK_STATUSBAR(textview->messageview->statusbar), \
111                           textview->messageview->statusbar_cid);           \
112 }
113
114 static void textview_show_ertf          (TextView       *textview,
115                                          FILE           *fp,
116                                          CodeConverter  *conv);
117 static void textview_add_part           (TextView       *textview,
118                                          MimeInfo       *mimeinfo);
119 static void textview_add_parts          (TextView       *textview,
120                                          MimeInfo       *mimeinfo);
121 static void textview_write_body         (TextView       *textview,
122                                          MimeInfo       *mimeinfo);
123 static void textview_show_html          (TextView       *textview,
124                                          FILE           *fp,
125                                          CodeConverter  *conv);
126
127 static void textview_write_line         (TextView       *textview,
128                                          const gchar    *str,
129                                          CodeConverter  *conv);
130 static void textview_write_link         (TextView       *textview,
131                                          const gchar    *str,
132                                          const gchar    *uri,
133                                          CodeConverter  *conv);
134
135 static GPtrArray *textview_scan_header  (TextView       *textview,
136                                          FILE           *fp);
137 static void textview_show_header        (TextView       *textview,
138                                          GPtrArray      *headers);
139
140 static gint textview_key_pressed        (GtkWidget      *widget,
141                                          GdkEventKey    *event,
142                                          TextView       *textview);
143 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
144                                             GdkEvent *event, GtkTextIter *iter,
145                                             TextView *textview);
146 static void textview_smooth_scroll_do           (TextView       *textview,
147                                                  gfloat          old_value,
148                                                  gfloat          last_value,
149                                                  gint            step);
150 static void textview_smooth_scroll_one_line     (TextView       *textview,
151                                                  gboolean        up);
152 static gboolean textview_smooth_scroll_page     (TextView       *textview,
153                                                  gboolean        up);
154
155 static gboolean textview_uri_security_check     (TextView       *textview,
156                                                  RemoteURI      *uri);
157 static void textview_uri_list_remove_all        (GSList         *uri_list);
158
159
160 static void populate_popup(GtkTextView *textview, GtkMenu *menu,
161                            gpointer *dummy)
162 {
163         gtk_menu_detach(menu);
164 }
165
166 TextView *textview_create(void)
167 {
168         TextView *textview;
169         GtkWidget *vbox;
170         GtkWidget *scrolledwin;
171         GtkWidget *text;
172         GtkTextBuffer *buffer;
173         GtkClipboard *clipboard;
174         PangoFontDescription *font_desc = NULL;
175
176         debug_print("Creating text view...\n");
177         textview = g_new0(TextView, 1);
178
179         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
180         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
181                                        GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
182         gtk_widget_set_size_request(scrolledwin, prefs_common.mainview_width, -1);
183         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
184                                             GTK_SHADOW_IN);
185
186         /* create GtkSText widgets for single-byte and multi-byte character */
187         text = gtk_text_view_new();
188         gtk_widget_show(text);
189         gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
190         gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
191         gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text), FALSE);
192         g_signal_connect(G_OBJECT(text), "populate-popup",
193                  G_CALLBACK(populate_popup), NULL);
194
195
196         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
197         clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
198         gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
199
200         gtk_widget_ensure_style(text);
201
202         if (prefs_common.normalfont)
203                 font_desc = pango_font_description_from_string
204                                         (prefs_common.normalfont);
205         if (font_desc) {
206                 gtk_widget_modify_font(text, font_desc);
207         }
208         pango_font_description_free(font_desc);
209
210         gtk_widget_ref(scrolledwin);
211
212         gtk_container_add(GTK_CONTAINER(scrolledwin), text);
213
214         g_signal_connect(G_OBJECT(text), "key_press_event",
215                          G_CALLBACK(textview_key_pressed),
216                          textview);
217
218         gtk_widget_show(scrolledwin);
219
220         vbox = gtk_vbox_new(FALSE, 0);
221         gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
222
223         gtk_widget_show(vbox);
224
225         textview->vbox             = vbox;
226         textview->scrolledwin      = scrolledwin;
227         textview->text             = text;
228         textview->uri_list         = NULL;
229         textview->body_pos         = 0;
230         textview->show_all_headers = FALSE;
231         textview->last_buttonpress = GDK_NOTHING;
232         textview->show_url_msgid   = 0;
233
234         return textview;
235 }
236
237 static void textview_create_tags(GtkTextView *text, TextView *textview)
238 {
239         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
240         GtkTextTag *tag;
241
242         gtk_text_buffer_create_tag(buffer, "header",
243                                    "pixels-above-lines", 0,
244                                    "pixels-above-lines-set", TRUE,
245                                    "pixels-below-lines", 0,
246                                    "pixels-below-lines-set", TRUE,
247                                    "left-margin", 0,
248                                    "left-margin-set", TRUE,
249                                    NULL);
250         gtk_text_buffer_create_tag(buffer, "header_title",
251                                    "font", prefs_common.boldfont,
252                                    NULL);
253         gtk_text_buffer_create_tag(buffer, "quote0",
254                                    "foreground-gdk", &quote_colors[0],
255                                    NULL);
256         gtk_text_buffer_create_tag(buffer, "quote1",
257                                    "foreground-gdk", &quote_colors[1],
258                                    NULL);
259         gtk_text_buffer_create_tag(buffer, "quote2",
260                                    "foreground-gdk", &quote_colors[2],
261                                    NULL);
262         gtk_text_buffer_create_tag(buffer, "emphasis",
263                                    "foreground-gdk", &emphasis_color,
264                                    NULL);
265         gtk_text_buffer_create_tag(buffer, "signature",
266                                    "foreground-gdk", &signature_color,
267                                    NULL);
268         tag = gtk_text_buffer_create_tag(buffer, "link",
269                                          "foreground-gdk", &uri_color,
270                                          NULL);
271
272        g_signal_connect(G_OBJECT(tag), "event",
273                          G_CALLBACK(textview_uri_button_pressed), textview);
274  }
275
276 void textview_init(TextView *textview)
277 {
278         gtkut_widget_disable_theme_engine(textview->text);
279         textview_update_message_colors();
280         textview_set_all_headers(textview, FALSE);
281         textview_set_font(textview, NULL);
282
283         textview_create_tags(GTK_TEXT_VIEW(textview->text), textview);
284 }
285
286 void textview_update_message_colors(void)
287 {
288         GdkColor black = {0, 0, 0, 0};
289
290         if (prefs_common.enable_color) {
291                 /* grab the quote colors, converting from an int to a GdkColor */
292                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
293                                                &quote_colors[0]);
294                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
295                                                &quote_colors[1]);
296                 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
297                                                &quote_colors[2]);
298                 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
299                                                &uri_color);
300                 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
301                                                &signature_color);
302         } else {
303                 quote_colors[0] = quote_colors[1] = quote_colors[2] = 
304                         uri_color = emphasis_color = signature_color = black;
305         }
306 }
307
308 void textview_show_message(TextView *textview, MimeInfo *mimeinfo,
309                            const gchar *file)
310 {
311         FILE *fp;
312
313         if ((fp = fopen(file, "rb")) == NULL) {
314                 FILE_OP_ERROR(file, "fopen");
315                 return;
316         }
317
318         textview_clear(textview);
319
320         textview_add_parts(textview, mimeinfo);
321
322         fclose(fp);
323
324         textview_set_position(textview, 0);
325 }
326
327 void textview_show_part(TextView *textview, MimeInfo *mimeinfo, FILE *fp)
328 {
329         GtkTextView *text;
330
331         g_return_if_fail(mimeinfo != NULL);
332         g_return_if_fail(fp != NULL);
333
334         if ((mimeinfo->type == MIMETYPE_MULTIPART) ||
335             ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822"))) {
336                 textview_clear(textview);
337                 textview_add_parts(textview, mimeinfo);
338                 return;
339         }
340
341         if (fseek(fp, mimeinfo->offset, SEEK_SET) < 0)
342                 perror("fseek");
343
344         textview_clear(textview);
345
346         if (mimeinfo->type == MIMETYPE_MULTIPART)
347                 textview_add_parts(textview, mimeinfo);
348         else
349                 textview_write_body(textview, mimeinfo);
350
351 }
352
353 static void textview_add_part(TextView *textview, MimeInfo *mimeinfo)
354 {
355         GtkTextView *text;
356         GtkTextBuffer *buffer;
357         GtkTextIter iter;
358         gchar buf[BUFFSIZE];
359         GPtrArray *headers = NULL;
360         const gchar *name;
361         gchar *content_type;
362         gint charcount;
363
364         g_return_if_fail(mimeinfo != NULL);
365         text = GTK_TEXT_VIEW(textview->text);
366         buffer = gtk_text_view_get_buffer(text);
367         charcount = gtk_text_buffer_get_char_count(buffer);
368         gtk_text_buffer_get_end_iter(buffer, &iter);
369
370         if (mimeinfo->type == MIMETYPE_MULTIPART) return;
371
372         if ((mimeinfo->type == MIMETYPE_MESSAGE) && !g_strcasecmp(mimeinfo->subtype, "rfc822")) {
373                 FILE *fp;
374
375                 fp = fopen(mimeinfo->data.filename, "rb");
376                 fseek(fp, mimeinfo->offset, SEEK_SET);
377                 headers = textview_scan_header(textview, fp);
378                 if (headers) {
379                         if (charcount > 0)
380                                 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
381                         textview_show_header(textview, headers);
382                         procheader_header_array_destroy(headers);
383                 }
384                 fclose(fp);
385                 return;
386         }
387
388         name = procmime_mimeinfo_get_parameter(mimeinfo, "filename");
389         content_type = procmime_get_content_type_str(mimeinfo->type,
390                                                      mimeinfo->subtype);
391         if (name == NULL)
392                 name = procmime_mimeinfo_get_parameter(mimeinfo, "name");
393         if (name != NULL)
394                 g_snprintf(buf, sizeof(buf), "\n[%s  %s (%d bytes)]\n",
395                            name, content_type, mimeinfo->length);
396         else
397                 g_snprintf(buf, sizeof(buf), "\n[%s (%d bytes)]\n",
398                            content_type, mimeinfo->length);
399
400         g_free(content_type);                      
401
402         if (mimeinfo->type != MIMETYPE_TEXT) {
403                 gtk_text_buffer_insert(buffer, &iter, buf, -1);
404         } else if (mimeinfo->disposition != DISPOSITIONTYPE_ATTACHMENT) {
405                 if (prefs_common.display_header && (charcount > 0))
406                         gtk_text_buffer_insert(buffer, &iter, "\n", 1);
407
408                 textview_write_body(textview, mimeinfo);
409         }
410 }
411
412 static void recursive_add_parts(TextView *textview, GNode *node)
413 {
414         GNode * iter;
415         MimeInfo *mimeinfo;
416         
417         mimeinfo = (MimeInfo *) node->data;
418         
419         textview_add_part(textview, mimeinfo);
420         
421         if ((mimeinfo->type != MIMETYPE_MULTIPART) &&
422             (mimeinfo->type != MIMETYPE_MESSAGE))
423                 return;
424         
425         if (g_strcasecmp(mimeinfo->subtype, "alternative") == 0) {
426                 GNode * prefered_body;
427                 int prefered_score;
428                 
429                 /*
430                   text/plain : score 3
431                   text/ *    : score 2
432                   other      : score 1
433                 */
434                 prefered_body = NULL;
435                 prefered_score = 0;
436                 
437                 for(iter = g_node_first_child(node) ; iter != NULL ;
438                     iter = g_node_next_sibling(iter)) {
439                         int score;
440                         MimeInfo * submime;
441                         
442                         score = 1;
443                         submime = (MimeInfo *) iter->data;
444                         if (submime->type == MIMETYPE_TEXT)
445                                 score = 2;
446                         
447                         if (submime->subtype != NULL) {
448                                 if (g_strcasecmp(submime->subtype, "plain") == 0)
449                                         score = 3;
450                         }
451                         
452                         if (score > prefered_score) {
453                                 prefered_score = score;
454                                 prefered_body = iter;
455                         }
456                 }
457                 
458                 if (prefered_body != NULL) {
459                         recursive_add_parts(textview, prefered_body);
460                 }
461         }
462         else {
463                 for(iter = g_node_first_child(node) ; iter != NULL ;
464                     iter = g_node_next_sibling(iter)) {
465                         recursive_add_parts(textview, iter);
466                 }
467         }
468 }
469
470 static void textview_add_parts(TextView *textview, MimeInfo *mimeinfo)
471 {
472         g_return_if_fail(mimeinfo != NULL);
473         
474         recursive_add_parts(textview, mimeinfo->node);
475 }
476
477 #define TEXT_INSERT(str) \
478         gtk_text_buffer_insert(buffer, &iter, str, -1)
479
480 void textview_show_error(TextView *textview)
481 {
482         GtkTextView *text;
483         GtkTextBuffer *buffer;
484         GtkTextIter iter;
485
486         textview_set_font(textview, NULL);
487         textview_clear(textview);
488
489         text = GTK_TEXT_VIEW(textview->text);
490         buffer = gtk_text_view_get_buffer(text);
491         gtk_text_buffer_get_start_iter(buffer, &iter);
492
493         TEXT_INSERT(_("This message can't be displayed.\n"));
494
495 }
496
497 void textview_show_mime_part(TextView *textview, MimeInfo *partinfo)
498 {
499         GtkTextView *text;
500         GtkTextBuffer *buffer;
501         GtkTextIter iter;
502
503         if (!partinfo) return;
504
505         textview_set_font(textview, NULL);
506         textview_clear(textview);
507
508         text = GTK_TEXT_VIEW(textview->text);
509         buffer = gtk_text_view_get_buffer(text);
510         gtk_text_buffer_get_start_iter(buffer, &iter);
511
512         TEXT_INSERT(_("The following can be performed on this part by "));
513         TEXT_INSERT(_("right-clicking the icon or list item:\n"));
514
515         TEXT_INSERT(_("    To save select 'Save as...' (Shortcut key: 'y')\n"));
516         TEXT_INSERT(_("    To display as text select 'Display as text' "));
517         TEXT_INSERT(_("(Shortcut key: 't')\n"));
518         TEXT_INSERT(_("    To open with an external program select 'Open' "));
519         TEXT_INSERT(_("(Shortcut key: 'l'),\n"));
520         TEXT_INSERT(_("    (alternately double-click, or click the middle "));
521         TEXT_INSERT(_("mouse button),\n"));
522         TEXT_INSERT(_("    or 'Open with...' (Shortcut key: 'o')\n"));
523
524 }
525
526 #undef TEXT_INSERT
527
528 static void textview_write_body(TextView *textview, MimeInfo *mimeinfo)
529 {
530         FILE *tmpfp;
531         gchar buf[BUFFSIZE];
532         CodeConverter *conv;
533         const gchar *charset;
534         
535         if (textview->messageview->forced_charset)
536                 charset = textview->messageview->forced_charset;
537         else
538                 charset = procmime_mimeinfo_get_parameter(mimeinfo, "charset");
539
540         textview_set_font(textview, charset);
541
542         conv = conv_code_converter_new(charset);
543
544         procmime_force_encoding(textview->messageview->forced_encoding);
545         
546         textview->is_in_signature = FALSE;
547
548         procmime_decode_content(mimeinfo);
549
550         if (!g_strcasecmp(mimeinfo->subtype, "html")) {
551                 gchar *filename;
552                 
553                 filename = procmime_get_tmp_file_name(mimeinfo);
554                 if (procmime_get_part(filename, mimeinfo) == 0) {
555                         tmpfp = fopen(filename, "rb");
556                         textview_show_html(textview, tmpfp, conv);
557                         fclose(tmpfp);
558                         unlink(filename);
559                 }
560                 g_free(filename);
561         } else if (!g_strcasecmp(mimeinfo->subtype, "enriched")) {
562                 gchar *filename;
563                 
564                 filename = procmime_get_tmp_file_name(mimeinfo);
565                 if (procmime_get_part(filename, mimeinfo) == 0) {
566                         tmpfp = fopen(filename, "rb");
567                         textview_show_ertf(textview, tmpfp, conv);
568                         fclose(tmpfp);
569                         unlink(filename);
570                 }
571                 g_free(filename);
572         } else {
573                 tmpfp = fopen(mimeinfo->data.filename, "rb");
574                 fseek(tmpfp, mimeinfo->offset, SEEK_SET);
575                 debug_print("Viewing text content of type: %s (length: %d)\n", mimeinfo->subtype, mimeinfo->length);
576                 while ((fgets(buf, sizeof(buf), tmpfp) != NULL) && 
577                        (ftell(tmpfp) <= mimeinfo->offset + mimeinfo->length))
578                         textview_write_line(textview, buf, conv);
579                 fclose(tmpfp);
580         }
581
582         conv_code_converter_destroy(conv);
583         procmime_force_encoding(0);
584 }
585
586 static void textview_show_html(TextView *textview, FILE *fp,
587                                CodeConverter *conv)
588 {
589         HTMLParser *parser;
590         gchar *str;
591
592         parser = html_parser_new(fp, conv);
593         g_return_if_fail(parser != NULL);
594
595         while ((str = html_parse(parser)) != NULL) {
596                 if (parser->state == HTML_HREF) {
597                         /* first time : get and copy the URL */
598                         if (parser->href == NULL) {
599                                 /* ALF - the sylpheed html parser returns an empty string,
600                                  * if still inside an <a>, but already parsed past HREF */
601                                 str = strtok(str, " ");
602                                 if (str) { 
603                                         parser->href = g_strdup(str);
604                                         /* the URL may (or not) be followed by the
605                                          * referenced text */
606                                         str = strtok(NULL, "");
607                                 }       
608                         }
609                         if (str != NULL)
610                                 textview_write_link(textview, str, parser->href, NULL);
611                 } else
612                         textview_write_line(textview, str, NULL);
613         }
614         html_parser_destroy(parser);
615 }
616
617 static void textview_show_ertf(TextView *textview, FILE *fp,
618                                CodeConverter *conv)
619 {
620         ERTFParser *parser;
621         gchar *str;
622
623         parser = ertf_parser_new(fp, conv);
624         g_return_if_fail(parser != NULL);
625
626         while ((str = ertf_parse(parser)) != NULL) {
627                 textview_write_line(textview, str, NULL);
628         }
629         
630         ertf_parser_destroy(parser);
631 }
632
633 /* get_uri_part() - retrieves a URI starting from scanpos.
634                     Returns TRUE if succesful */
635 static gboolean get_uri_part(const gchar *start, const gchar *scanpos,
636                              const gchar **bp, const gchar **ep)
637 {
638         const gchar *ep_;
639
640         g_return_val_if_fail(start != NULL, FALSE);
641         g_return_val_if_fail(scanpos != NULL, FALSE);
642         g_return_val_if_fail(bp != NULL, FALSE);
643         g_return_val_if_fail(ep != NULL, FALSE);
644
645         *bp = scanpos;
646
647         /* find end point of URI */
648         for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
649                 if (!isgraph(*(const guchar *)ep_) ||
650                     !IS_ASCII(*(const guchar *)ep_) ||
651                     strchr("()<>\"", *ep_))
652                         break;
653         }
654
655         /* no punctuation at end of string */
656
657         /* FIXME: this stripping of trailing punctuations may bite with other URIs.
658          * should pass some URI type to this function and decide on that whether
659          * to perform punctuation stripping */
660
661 #define IS_REAL_PUNCT(ch)       (ispunct(ch) && ((ch) != '/')) 
662
663         for (; ep_ - 1 > scanpos + 1 &&
664                IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
665              ep_--)
666                 ;
667
668 #undef IS_REAL_PUNCT
669
670         *ep = ep_;
671
672         return TRUE;            
673 }
674
675 static gchar *make_uri_string(const gchar *bp, const gchar *ep)
676 {
677         return g_strndup(bp, ep - bp);
678 }
679
680 /* valid mail address characters */
681 #define IS_RFC822_CHAR(ch) \
682         (IS_ASCII(ch) && \
683          (ch) > 32   && \
684          (ch) != 127 && \
685          !isspace(ch) && \
686          !strchr("(),;<>\"", (ch)))
687
688 /* alphabet and number within 7bit ASCII */
689 #define IS_ASCII_ALNUM(ch)      (IS_ASCII(ch) && isalnum(ch))
690 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
691
692 static GHashTable *create_domain_tab(void)
693 {
694         static const gchar *toplvl_domains [] = {
695             "museum", "aero",
696             "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
697             "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
698             "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
699             "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
700             "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
701             "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
702             "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
703             "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
704             "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
705             "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
706             "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
707             "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
708             "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
709             "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
710             "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
711             "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
712             "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
713             "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
714             "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
715             "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
716             "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
717             "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
718             "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
719             "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
720             "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
721             "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw" 
722         };
723         gint n;
724         GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
725         
726         g_return_val_if_fail(htab, NULL);
727         for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++) 
728                 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
729         return htab;
730 }
731
732 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
733 {
734         const gint MAX_LVL_DOM_NAME_LEN = 6;
735         gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
736         const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
737         register gchar *p;
738         
739         if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
740                 return FALSE;
741
742         for (p = buf; p < m &&  first < last; *p++ = *first++)
743                 ;
744         *p = 0;
745
746         return g_hash_table_lookup(tab, buf) != NULL;
747 }
748
749 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
750 static gboolean get_email_part(const gchar *start, const gchar *scanpos,
751                                const gchar **bp, const gchar **ep)
752 {
753         /* more complex than the uri part because we need to scan back and forward starting from
754          * the scan position. */
755         gboolean result = FALSE;
756         const gchar *bp_ = NULL;
757         const gchar *ep_ = NULL;
758         static GHashTable *dom_tab;
759         const gchar *last_dot = NULL;
760         const gchar *prelast_dot = NULL;
761         const gchar *last_tld_char = NULL;
762
763         /* the informative part of the email address (describing the name
764          * of the email address owner) may contain quoted parts. the
765          * closure stack stores the last encountered quotes. */
766         gchar closure_stack[128];
767         gchar *ptr = closure_stack;
768
769         g_return_val_if_fail(start != NULL, FALSE);
770         g_return_val_if_fail(scanpos != NULL, FALSE);
771         g_return_val_if_fail(bp != NULL, FALSE);
772         g_return_val_if_fail(ep != NULL, FALSE);
773
774         if (!dom_tab)
775                 dom_tab = create_domain_tab();
776         g_return_val_if_fail(dom_tab, FALSE);   
777
778         /* scan start of address */
779         for (bp_ = scanpos - 1;
780              bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
781                 ;
782
783         /* TODO: should start with an alnum? */
784         bp_++;
785         for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
786                 ;
787
788         if (bp_ != scanpos) {
789                 /* scan end of address */
790                 for (ep_ = scanpos + 1;
791                      *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
792                         if (*ep_ == '.') {
793                                 prelast_dot = last_dot;
794                                 last_dot = ep_;
795                                 if (*(last_dot + 1) == '.') {
796                                         if (prelast_dot == NULL)
797                                                 return FALSE;
798                                         last_dot = prelast_dot;
799                                         break;
800                                 }
801                         }
802
803                 /* TODO: really should terminate with an alnum? */
804                 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
805                      --ep_)
806                         ;
807                 ep_++;
808
809                 if (last_dot == NULL)
810                         return FALSE;
811                 if (last_dot >= ep_)
812                         last_dot = prelast_dot;
813                 if (last_dot == NULL || (scanpos + 1 >= last_dot))
814                         return FALSE;
815                 last_dot++;
816
817                 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
818                         if (*last_tld_char == '?')
819                                 break;
820
821                 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
822                         result = TRUE;
823
824                 *ep = ep_;
825                 *bp = bp_;
826         }
827
828         if (!result) return FALSE;
829
830         /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
831         if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_)) 
832                 return FALSE;
833
834         /* see if this is <bracketed>; in this case we also scan for the informative part. */
835         if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
836                 return TRUE;
837
838 #define FULL_STACK()    ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
839 #define IN_STACK()      (ptr > closure_stack)
840 /* has underrun check */
841 #define POP_STACK()     if(IN_STACK()) --ptr
842 /* has overrun check */
843 #define PUSH_STACK(c)   if(!FULL_STACK()) *ptr++ = (c); else return TRUE
844 /* has underrun check */
845 #define PEEK_STACK()    (IN_STACK() ? *(ptr - 1) : 0)
846
847         ep_++;
848
849         /* scan for the informative part. */
850         for (bp_ -= 2; bp_ >= start; bp_--) {
851                 /* if closure on the stack keep scanning */
852                 if (PEEK_STACK() == *bp_) {
853                         POP_STACK();
854                         continue;
855                 }
856                 if (*bp_ == '\'' || *bp_ == '"') {
857                         PUSH_STACK(*bp_);
858                         continue;
859                 }
860
861                 /* if nothing in the closure stack, do the special conditions
862                  * the following if..else expression simply checks whether 
863                  * a token is acceptable. if not acceptable, the clause
864                  * should terminate the loop with a 'break' */
865                 if (!PEEK_STACK()) {
866                         if (*bp_ == '-'
867                         && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
868                         && (((bp_ + 1) < ep_)    && isalnum(*(bp_ + 1)))) {
869                                 /* hyphens are allowed, but only in
870                                    between alnums */
871                         } else if (!ispunct(*bp_)) {
872                                 /* but anything not being a punctiation
873                                    is ok */
874                         } else {
875                                 break; /* anything else is rejected */
876                         }
877                 }
878         }
879
880         bp_++;
881
882 #undef PEEK_STACK
883 #undef PUSH_STACK
884 #undef POP_STACK
885 #undef IN_STACK
886 #undef FULL_STACK
887
888         /* scan forward (should start with an alnum) */
889         for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
890                 ;
891
892         *ep = ep_;
893         *bp = bp_;
894
895         return result;
896 }
897
898 #undef IS_QUOTE
899 #undef IS_ASCII_ALNUM
900 #undef IS_RFC822_CHAR
901
902 static gchar *make_email_string(const gchar *bp, const gchar *ep)
903 {
904         /* returns a mailto: URI; mailto: is also used to detect the
905          * uri type later on in the button_pressed signal handler */
906         gchar *tmp;
907         gchar *result;
908
909         tmp = g_strndup(bp, ep - bp);
910         result = g_strconcat("mailto:", tmp, NULL);
911         g_free(tmp);
912
913         return result;
914 }
915
916 static gchar *make_http_string(const gchar *bp, const gchar *ep)
917 {
918         /* returns an http: URI; */
919         gchar *tmp;
920         gchar *result;
921
922         tmp = g_strndup(bp, ep - bp);
923         result = g_strconcat("http://", tmp, NULL);
924         g_free(tmp);
925
926         return result;
927 }
928
929 #define ADD_TXT_POS(bp_, ep_, pti_) \
930         if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
931                 last = last->next; \
932                 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
933                 last->next = NULL; \
934         } else { \
935                 g_warning("alloc error scanning URIs\n"); \
936                 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, \
937                                                          linebuf, -1, \
938                                                          fg_tag, NULL); \
939                 return; \
940         }
941
942 /* textview_make_clickable_parts() - colorizes clickable parts */
943 static void textview_make_clickable_parts(TextView *textview,
944                                           const gchar *fg_tag,
945                                           const gchar *uri_tag,
946                                           const gchar *linebuf)
947 {
948         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
949         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
950         GtkTextIter iter;
951
952         /* parse table - in order of priority */
953         struct table {
954                 const gchar *needle; /* token */
955
956                 /* token search function */
957                 gchar    *(*search)     (const gchar *haystack,
958                                          const gchar *needle);
959                 /* part parsing function */
960                 gboolean  (*parse)      (const gchar *start,
961                                          const gchar *scanpos,
962                                          const gchar **bp_,
963                                          const gchar **ep_);
964                 /* part to URI function */
965                 gchar    *(*build_uri)  (const gchar *bp,
966                                          const gchar *ep);
967         };
968
969         static struct table parser[] = {
970                 {"http://",  strcasestr, get_uri_part,   make_uri_string},
971                 {"https://", strcasestr, get_uri_part,   make_uri_string},
972                 {"ftp://",   strcasestr, get_uri_part,   make_uri_string},
973                 {"www.",     strcasestr, get_uri_part,   make_http_string},
974                 {"mailto:",  strcasestr, get_uri_part,   make_uri_string},
975                 {"@",        strcasestr, get_email_part, make_email_string}
976         };
977         const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
978
979         gint  n;
980         const gchar *walk, *bp, *ep;
981
982         struct txtpos {
983                 const gchar     *bp, *ep;       /* text position */
984                 gint             pti;           /* index in parse table */
985                 struct txtpos   *next;          /* next */
986         } head = {NULL, NULL, 0,  NULL}, *last = &head;
987
988         gtk_text_buffer_get_end_iter(buffer, &iter);
989
990         /* parse for clickable parts, and build a list of begin and end positions  */
991         for (walk = linebuf, n = 0;;) {
992                 gint last_index = PARSE_ELEMS;
993                 gchar *scanpos = NULL;
994
995                 /* FIXME: this looks phony. scanning for anything in the parse table */
996                 for (n = 0; n < PARSE_ELEMS; n++) {
997                         gchar *tmp;
998
999                         tmp = parser[n].search(walk, parser[n].needle);
1000                         if (tmp) {
1001                                 if (scanpos == NULL || tmp < scanpos) {
1002                                         scanpos = tmp;
1003                                         last_index = n;
1004                                 }
1005                         }                                       
1006                 }
1007
1008                 if (scanpos) {
1009                         /* check if URI can be parsed */
1010                         if (parser[last_index].parse(walk, scanpos, &bp, &ep)
1011                             && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
1012                                         ADD_TXT_POS(bp, ep, last_index);
1013                                         walk = ep;
1014                         } else
1015                                 walk = scanpos +
1016                                         strlen(parser[last_index].needle);
1017                 } else
1018                         break;
1019         }
1020
1021         /* colorize this line */
1022         if (head.next) {
1023                 const gchar *normal_text = linebuf;
1024
1025                 /* insert URIs */
1026                 for (last = head.next; last != NULL;
1027                      normal_text = last->ep, last = last->next) {
1028                         RemoteURI *uri;
1029
1030                         uri = g_new(RemoteURI, 1);
1031                         if (last->bp - normal_text > 0)
1032                                 gtk_text_buffer_insert_with_tags_by_name
1033                                         (buffer, &iter,
1034                                          normal_text,
1035                                          last->bp - normal_text,
1036                                          fg_tag, NULL);
1037                         uri->uri = parser[last->pti].build_uri(last->bp,
1038                                                                last->ep);
1039                         uri->start = gtk_text_iter_get_offset(&iter);
1040                         gtk_text_buffer_insert_with_tags_by_name
1041                                 (buffer, &iter,
1042                                  last->bp, last->ep - last->bp,
1043                                  uri_tag, NULL);
1044                         uri->end = gtk_text_iter_get_offset(&iter);
1045                         textview->uri_list =
1046                                 g_slist_append(textview->uri_list, uri);
1047                 }
1048
1049                 if (*normal_text)
1050                         gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1051                                                                  normal_text, -1,
1052                                                                  fg_tag, NULL);
1053         } else {
1054                 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
1055                                                          linebuf, -1,
1056                                                          fg_tag, NULL);
1057         }
1058 }
1059
1060 #undef ADD_TXT_POS
1061
1062 static void textview_write_line(TextView *textview, const gchar *str,
1063                                 CodeConverter *conv)
1064 {
1065         GtkTextView *text;
1066         GtkTextBuffer *buffer;
1067         GtkTextIter iter;
1068         gchar buf[BUFFSIZE];
1069         gchar *fg_color;
1070         gint quotelevel = -1;
1071         gchar quote_tag_str[10];
1072
1073         text = GTK_TEXT_VIEW(textview->text);
1074         buffer = gtk_text_view_get_buffer(text);
1075         gtk_text_buffer_get_end_iter(buffer, &iter);
1076
1077         if (!conv) {
1078                 strncpy2(buf, str, sizeof(buf));
1079         } else if (conv_convert(conv, buf, sizeof(buf), str) < 0) {
1080                 conv_localetodisp(buf, sizeof(buf), str);
1081         }
1082
1083         strcrchomp(buf);
1084         if (prefs_common.conv_mb_alnum) conv_mb_alnum(buf);
1085         fg_color = NULL;
1086
1087         /* change color of quotation
1088            >, foo>, _> ... ok, <foo>, foo bar>, foo-> ... ng
1089            Up to 3 levels of quotations are detected, and each
1090            level is colored using a different color. */
1091         if (prefs_common.enable_color 
1092             && line_has_quote_char(buf, prefs_common.quote_chars)) {
1093                 quotelevel = get_quote_level(buf, prefs_common.quote_chars);
1094
1095                 /* set up the correct foreground color */
1096                 if (quotelevel > 2) {
1097                         /* recycle colors */
1098                         if (prefs_common.recycle_quote_colors)
1099                                 quotelevel %= 3;
1100                         else
1101                                 quotelevel = 2;
1102                 }
1103         }
1104
1105         if (quotelevel == -1)
1106                 fg_color = NULL;
1107         else {
1108                 g_snprintf(quote_tag_str, sizeof(quote_tag_str),
1109                            "quote%d", quotelevel);
1110                 fg_color = quote_tag_str;
1111         }
1112
1113         if (prefs_common.enable_color && (strcmp(buf,"-- \n") == 0 || textview->is_in_signature)) {
1114                 fg_color = "signature";
1115                 textview->is_in_signature = TRUE;
1116         }
1117
1118         if (prefs_common.enable_color)
1119                 textview_make_clickable_parts(textview, fg_color, "link", buf);
1120         else
1121                 textview_make_clickable_parts(textview, fg_color, NULL, buf);
1122 }
1123
1124 void textview_write_link(TextView *textview, const gchar *str,
1125                          const gchar *uri, CodeConverter *conv)
1126 {
1127         GdkColor *link_color = NULL;
1128         GtkTextView *text;
1129         GtkTextBuffer *buffer;
1130         GtkTextIter iter;
1131         gchar buf[BUFFSIZE];
1132         gchar *bufp;
1133         RemoteURI *r_uri;
1134
1135         if (*str == '\0')
1136                 return;
1137
1138         text = GTK_TEXT_VIEW(textview->text);
1139         buffer = gtk_text_view_get_buffer(text);
1140         gtk_text_buffer_get_end_iter(buffer, &iter);
1141
1142 #warning FIXME_GTK2
1143 #if 0
1144         if (!conv) {
1145                 if (textview->text_is_mb)
1146                         conv_localetodisp(buf, sizeof(buf), str);
1147                 else
1148                         strncpy2(buf, str, sizeof(buf));
1149         } else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1150                 conv_localetodisp(buf, sizeof(buf), str);
1151         else if (textview->text_is_mb)
1152                 conv_unreadable_locale(buf);
1153 #else
1154         if (!conv)
1155                 strncpy2(buf, str, sizeof(buf));
1156         else if (conv_convert(conv, buf, sizeof(buf), str) < 0)
1157                 conv_localetodisp(buf, sizeof(buf), str);
1158 #endif
1159
1160         strcrchomp(buf);
1161
1162         gtk_text_buffer_get_end_iter(buffer, &iter);
1163
1164         for (bufp = buf; isspace(*(guchar *)bufp); bufp++)
1165                 gtk_text_buffer_insert(buffer, &iter, bufp, 1);
1166
1167         if (prefs_common.enable_color) {
1168                 link_color = &uri_color;
1169         }
1170         r_uri = g_new(RemoteURI, 1);
1171         r_uri->uri = g_strdup(uri);
1172         r_uri->start = gtk_text_iter_get_offset(&iter);
1173         gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, bufp, -1,
1174                                                  "link", NULL);
1175         r_uri->end = gtk_text_iter_get_offset(&iter);
1176         textview->uri_list = g_slist_append(textview->uri_list, r_uri);
1177 }
1178
1179 void textview_clear(TextView *textview)
1180 {
1181         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1182         GtkTextBuffer *buffer;
1183
1184         buffer = gtk_text_view_get_buffer(text);
1185         gtk_text_buffer_set_text(buffer, "\0", -1);
1186
1187         TEXTVIEW_STATUSBAR_POP(textview);
1188         textview_uri_list_remove_all(textview->uri_list);
1189         textview->uri_list = NULL;
1190
1191         textview->body_pos = 0;
1192 }
1193
1194 void textview_destroy(TextView *textview)
1195 {
1196         textview_uri_list_remove_all(textview->uri_list);
1197         textview->uri_list = NULL;
1198
1199         g_free(textview);
1200 }
1201
1202 void textview_set_all_headers(TextView *textview, gboolean all_headers)
1203 {
1204         textview->show_all_headers = all_headers;
1205 }
1206
1207 void textview_set_font(TextView *textview, const gchar *codeset)
1208 {
1209         if (prefs_common.textfont) {
1210                 PangoFontDescription *font_desc = NULL;
1211
1212                 if (prefs_common.textfont)
1213                         font_desc = pango_font_description_from_string
1214                                                 (prefs_common.textfont);
1215                 if (font_desc) {
1216                         gtk_widget_modify_font(textview->text, font_desc);
1217                         pango_font_description_free(font_desc);
1218                 }
1219         }
1220         gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(textview->text),
1221                                              prefs_common.line_space / 2);
1222         gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(textview->text),
1223                                              prefs_common.line_space / 2);
1224         if (prefs_common.head_space) {
1225                 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 6);
1226         } else {
1227                 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview->text), 0);
1228         }
1229 }
1230
1231 void textview_set_text(TextView *textview, const gchar *text)
1232 {
1233         GtkTextView *view;
1234         GtkTextBuffer *buffer;
1235
1236         g_return_if_fail(textview != NULL);
1237         g_return_if_fail(text != NULL);
1238
1239         textview_clear(textview);
1240
1241         view = GTK_TEXT_VIEW(textview->text);
1242         buffer = gtk_text_view_get_buffer(view);
1243         gtk_text_buffer_set_text(buffer, text, strlen(text));
1244 }
1245
1246 enum
1247 {
1248         H_DATE          = 0,
1249         H_FROM          = 1,
1250         H_TO            = 2,
1251         H_NEWSGROUPS    = 3,
1252         H_SUBJECT       = 4,
1253         H_CC            = 5,
1254         H_REPLY_TO      = 6,
1255         H_FOLLOWUP_TO   = 7,
1256         H_X_MAILER      = 8,
1257         H_X_NEWSREADER  = 9,
1258         H_USER_AGENT    = 10,
1259         H_ORGANIZATION  = 11,
1260 };
1261
1262 void textview_set_position(TextView *textview, gint pos)
1263 {
1264         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1265         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1266         GtkTextIter iter;
1267
1268         gtk_text_buffer_get_iter_at_offset(buffer, &iter, pos);
1269         gtk_text_buffer_place_cursor(buffer, &iter);
1270         gtk_text_view_scroll_to_iter(text, &iter, 0.0, FALSE, 0.0, 0.0);
1271 }
1272
1273 static GPtrArray *textview_scan_header(TextView *textview, FILE *fp)
1274 {
1275         gchar buf[BUFFSIZE];
1276         GPtrArray *headers, *sorted_headers;
1277         GSList *disphdr_list;
1278         Header *header;
1279         gint i;
1280
1281         g_return_val_if_fail(fp != NULL, NULL);
1282
1283         if (textview->show_all_headers)
1284                 return procheader_get_header_array_asis(fp);
1285
1286         if (!prefs_common.display_header) {
1287                 while (fgets(buf, sizeof(buf), fp) != NULL)
1288                         if (buf[0] == '\r' || buf[0] == '\n') break;
1289                 return NULL;
1290         }
1291
1292         headers = procheader_get_header_array_asis(fp);
1293
1294         sorted_headers = g_ptr_array_new();
1295
1296         for (disphdr_list = prefs_common.disphdr_list; disphdr_list != NULL;
1297              disphdr_list = disphdr_list->next) {
1298                 DisplayHeaderProp *dp =
1299                         (DisplayHeaderProp *)disphdr_list->data;
1300
1301                 for (i = 0; i < headers->len; i++) {
1302                         header = g_ptr_array_index(headers, i);
1303
1304                         if (procheader_headername_equal(header->name,
1305                                                         dp->name)) {
1306                                 if (dp->hidden)
1307                                         procheader_header_free(header);
1308                                 else
1309                                         g_ptr_array_add(sorted_headers, header);
1310
1311                                 g_ptr_array_remove_index(headers, i);
1312                                 i--;
1313                         }
1314                 }
1315         }
1316
1317         if (prefs_common.show_other_header) {
1318                 for (i = 0; i < headers->len; i++) {
1319                         header = g_ptr_array_index(headers, i);
1320                         g_ptr_array_add(sorted_headers, header);
1321                 }
1322                 g_ptr_array_free(headers, TRUE);
1323         } else
1324                 procheader_header_array_destroy(headers);
1325
1326
1327         return sorted_headers;
1328 }
1329
1330 static void textview_show_header(TextView *textview, GPtrArray *headers)
1331 {
1332         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1333         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1334         GtkTextIter iter;
1335         Header *header;
1336         gint i;
1337
1338         g_return_if_fail(headers != NULL);
1339
1340         for (i = 0; i < headers->len; i++) {
1341                 header = g_ptr_array_index(headers, i);
1342                 g_return_if_fail(header->name != NULL);
1343
1344                 gtk_text_buffer_get_end_iter (buffer, &iter);
1345                 gtk_text_buffer_insert_with_tags_by_name
1346                         (buffer, &iter, header->name, -1,
1347                          "header_title", "header", NULL);
1348                 if (header->name[strlen(header->name) - 1] != ' ')
1349                 gtk_text_buffer_insert_with_tags_by_name
1350                                 (buffer, &iter, " ", 1,
1351                                  "header_title", "header", NULL);
1352
1353                 if (procheader_headername_equal(header->name, "Subject") ||
1354                     procheader_headername_equal(header->name, "From")    ||
1355                     procheader_headername_equal(header->name, "To")      ||
1356                     procheader_headername_equal(header->name, "Cc"))
1357                         unfold_line(header->body);
1358
1359 #warning FIXME_GTK2
1360 #if 0
1361                 if (textview->text_is_mb == TRUE)
1362                         conv_unreadable_locale(header->body);
1363 #endif
1364
1365                 if (prefs_common.enable_color &&
1366                     (procheader_headername_equal(header->name, "X-Mailer") ||
1367                      procheader_headername_equal(header->name,
1368                                                  "X-Newsreader")) &&
1369                     strstr(header->body, "Sylpheed") != NULL) {
1370                         gtk_text_buffer_get_end_iter (buffer, &iter);
1371                         gtk_text_buffer_insert_with_tags_by_name
1372                                 (buffer, &iter, header->body, -1,
1373                                  "header", "emphasis", NULL);
1374                 } else if (prefs_common.enable_color) {
1375                         textview_make_clickable_parts(textview, "header", "link",
1376                                                       header->body);
1377                 } else {
1378                         textview_make_clickable_parts(textview, "header", NULL,
1379                                                       header->body);
1380                 }
1381                 gtk_text_buffer_get_end_iter (buffer, &iter);
1382                 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
1383                                                          "header", NULL);
1384         }
1385 }
1386
1387 gboolean textview_search_string(TextView *textview, const gchar *str,
1388                                 gboolean case_sens)
1389 {
1390         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1391         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1392         GtkTextMark *mark;
1393         GtkTextIter iter, start, end, real_end, *pos;
1394         gboolean found = FALSE;
1395         gint insert_offset, selbound_offset;
1396
1397         /* reset selection */
1398         mark = gtk_text_buffer_get_mark(buffer, "insert");
1399         gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1400         insert_offset = gtk_text_iter_get_offset(&start);
1401         mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1402         gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1403         selbound_offset = gtk_text_iter_get_offset(&end);
1404
1405         pos = insert_offset > selbound_offset ? &start : &end;
1406         gtk_text_buffer_place_cursor(buffer, pos);
1407
1408         /* search */
1409         mark = gtk_text_buffer_get_insert(buffer);
1410         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1411         if (case_sens) {
1412                 found = gtk_text_iter_forward_search(&iter, str,
1413                                              GTK_TEXT_SEARCH_VISIBLE_ONLY,
1414                                              &start, &end, NULL);
1415         } else {
1416                 gchar *text = NULL;
1417                 int i = 0;
1418                 gtk_text_buffer_get_end_iter(buffer, &real_end);
1419                 text = strdup(gtk_text_buffer_get_text(buffer, &iter, 
1420                                                        &real_end, FALSE));
1421                 
1422                 while (!found && i++ < strlen(text) - 1) {
1423                         found = (strncasecmp(text+i, str, strlen(str)) == 0);
1424                 }
1425                 
1426                 i += gtk_text_iter_get_offset(&end);
1427                 
1428                 if (found) {
1429                         gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1430                         gtk_text_buffer_get_iter_at_offset(buffer, &end, 
1431                                                            i + strlen(str));
1432                 }
1433                 
1434                 g_free(text);
1435         }
1436         
1437         if (found) {
1438                 gtk_text_buffer_place_cursor(buffer, &start);
1439                 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", 
1440                                                   &end);
1441                 mark = gtk_text_buffer_get_mark(buffer, "insert");
1442                 gtk_text_view_scroll_mark_onscreen(text, mark);
1443         }
1444
1445         return found;
1446 }
1447
1448 gboolean textview_search_string_backward(TextView *textview, const gchar *str,
1449                                          gboolean case_sens)
1450 {
1451         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1452         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
1453         GtkTextMark *mark;
1454         GtkTextIter iter, start, real_start, end, *pos;
1455         gboolean found = FALSE;
1456         gint insert_offset, selbound_offset;
1457
1458         /* reset selection */
1459         mark = gtk_text_buffer_get_mark(buffer, "insert");
1460         gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
1461         insert_offset = gtk_text_iter_get_offset(&start);
1462         mark = gtk_text_buffer_get_mark(buffer, "selection_bound");
1463         gtk_text_buffer_get_iter_at_mark(buffer, &end, mark);
1464         selbound_offset = gtk_text_iter_get_offset(&end);
1465
1466         pos = insert_offset < selbound_offset ? &start : &end;
1467         gtk_text_buffer_place_cursor(buffer, pos);
1468
1469         /* search */
1470         mark = gtk_text_buffer_get_insert(buffer);
1471         gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
1472         if (case_sens) {
1473                 found = gtk_text_iter_backward_search(&iter, str,
1474                                               GTK_TEXT_SEARCH_VISIBLE_ONLY,
1475                                               &start, &end, NULL);
1476         } else {
1477                 gchar *text = NULL;
1478                 int i = 0;
1479                 if (gtk_text_iter_get_offset(&iter) == 0) 
1480                         gtk_text_buffer_get_end_iter(buffer, &iter);
1481                 
1482                 i = gtk_text_iter_get_offset(&iter) - strlen(str) - 1;
1483                 gtk_text_buffer_get_start_iter(buffer, &real_start);
1484                 
1485                 text = strdup(gtk_text_buffer_get_text(buffer, &real_start, 
1486                                                        &iter, FALSE));
1487
1488                 while (!found && i-- > 0) {
1489                         found = (strncasecmp(text+i, str, strlen(str)) == 0);
1490                 }
1491                                 
1492                 if (found) {
1493                         gtk_text_buffer_get_iter_at_offset(buffer, &start, i);
1494                         gtk_text_buffer_get_iter_at_offset(buffer, &end, 
1495                                                            i + strlen(str));
1496                 }
1497                 
1498                 g_free(text);
1499         }
1500                 
1501         if (found) {
1502                 gtk_text_buffer_place_cursor(buffer, &end);
1503                 gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", 
1504                                                   &start);
1505                 mark = gtk_text_buffer_get_mark(buffer, "insert");
1506                 gtk_text_view_scroll_mark_onscreen(text, mark);
1507         }
1508
1509         return found;
1510 }
1511
1512 void textview_scroll_one_line(TextView *textview, gboolean up)
1513 {
1514         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1515         GtkAdjustment *vadj = text->vadjustment;
1516         gfloat upper;
1517
1518         if (prefs_common.enable_smooth_scroll) {
1519                 textview_smooth_scroll_one_line(textview, up);
1520                 return;
1521         }
1522
1523         if (!up) {
1524                 upper = vadj->upper - vadj->page_size;
1525                 if (vadj->value < upper) {
1526                         vadj->value +=
1527                                 vadj->step_increment * 4;
1528                         vadj->value =
1529                                 MIN(vadj->value, upper);
1530                         g_signal_emit_by_name(G_OBJECT(vadj),
1531                                               "value_changed", 0);
1532                 }
1533         } else {
1534                 if (vadj->value > 0.0) {
1535                         vadj->value -=
1536                                 vadj->step_increment * 4;
1537                         vadj->value =
1538                                 MAX(vadj->value, 0.0);
1539                         g_signal_emit_by_name(G_OBJECT(vadj),
1540                                               "value_changed", 0);
1541                 }
1542         }
1543 }
1544
1545 gboolean textview_scroll_page(TextView *textview, gboolean up)
1546 {
1547         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1548         GtkAdjustment *vadj = text->vadjustment;
1549         gfloat upper;
1550         gfloat page_incr;
1551
1552         if (prefs_common.enable_smooth_scroll)
1553                 return textview_smooth_scroll_page(textview, up);
1554
1555         if (prefs_common.scroll_halfpage)
1556                 page_incr = vadj->page_increment / 2;
1557         else
1558                 page_incr = vadj->page_increment;
1559
1560         if (!up) {
1561                 upper = vadj->upper - vadj->page_size;
1562                 if (vadj->value < upper) {
1563                         vadj->value += page_incr;
1564                         vadj->value = MIN(vadj->value, upper);
1565                         g_signal_emit_by_name(G_OBJECT(vadj),
1566                                               "value_changed", 0);
1567                 } else
1568                         return FALSE;
1569         } else {
1570                 if (vadj->value > 0.0) {
1571                         vadj->value -= page_incr;
1572                         vadj->value = MAX(vadj->value, 0.0);
1573                         g_signal_emit_by_name(G_OBJECT(vadj),
1574                                               "value_changed", 0);
1575                 } else
1576                         return FALSE;
1577         }
1578
1579         return TRUE;
1580 }
1581
1582 static void textview_smooth_scroll_do(TextView *textview,
1583                                       gfloat old_value, gfloat last_value,
1584                                       gint step)
1585 {
1586         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1587         GtkAdjustment *vadj = text->vadjustment;
1588         gint change_value;
1589         gboolean up;
1590         gint i;
1591
1592         if (old_value < last_value) {
1593                 change_value = last_value - old_value;
1594                 up = FALSE;
1595         } else {
1596                 change_value = old_value - last_value;
1597                 up = TRUE;
1598         }
1599
1600         for (i = step; i <= change_value; i += step) {
1601                 vadj->value = old_value + (up ? -i : i);
1602                 g_signal_emit_by_name(G_OBJECT(vadj),
1603                                       "value_changed", 0);
1604         }
1605
1606         vadj->value = last_value;
1607         g_signal_emit_by_name(G_OBJECT(vadj), "value_changed", 0);
1608 }
1609
1610 static void textview_smooth_scroll_one_line(TextView *textview, gboolean up)
1611 {
1612         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1613         GtkAdjustment *vadj = text->vadjustment;
1614         gfloat upper;
1615         gfloat old_value;
1616         gfloat last_value;
1617
1618         if (!up) {
1619                 upper = vadj->upper - vadj->page_size;
1620                 if (vadj->value < upper) {
1621                         old_value = vadj->value;
1622                         last_value = vadj->value +
1623                                 vadj->step_increment * 4;
1624                         last_value = MIN(last_value, upper);
1625
1626                         textview_smooth_scroll_do(textview, old_value,
1627                                                   last_value,
1628                                                   prefs_common.scroll_step);
1629                 }
1630         } else {
1631                 if (vadj->value > 0.0) {
1632                         old_value = vadj->value;
1633                         last_value = vadj->value -
1634                                 vadj->step_increment * 4;
1635                         last_value = MAX(last_value, 0.0);
1636
1637                         textview_smooth_scroll_do(textview, old_value,
1638                                                   last_value,
1639                                                   prefs_common.scroll_step);
1640                 }
1641         }
1642 }
1643
1644 static gboolean textview_smooth_scroll_page(TextView *textview, gboolean up)
1645 {
1646         GtkTextView *text = GTK_TEXT_VIEW(textview->text);
1647         GtkAdjustment *vadj = text->vadjustment;
1648         gfloat upper;
1649         gfloat page_incr;
1650         gfloat old_value;
1651         gfloat last_value;
1652
1653         if (prefs_common.scroll_halfpage)
1654                 page_incr = vadj->page_increment / 2;
1655         else
1656                 page_incr = vadj->page_increment;
1657
1658         if (!up) {
1659                 upper = vadj->upper - vadj->page_size;
1660                 if (vadj->value < upper) {
1661                         old_value = vadj->value;
1662                         last_value = vadj->value + page_incr;
1663                         last_value = MIN(last_value, upper);
1664
1665                         textview_smooth_scroll_do(textview, old_value,
1666                                                   last_value,
1667                                                   prefs_common.scroll_step);
1668                 } else
1669                         return FALSE;
1670         } else {
1671                 if (vadj->value > 0.0) {
1672                         old_value = vadj->value;
1673                         last_value = vadj->value - page_incr;
1674                         last_value = MAX(last_value, 0.0);
1675
1676                         textview_smooth_scroll_do(textview, old_value,
1677                                                   last_value,
1678                                                   prefs_common.scroll_step);
1679                 } else
1680                         return FALSE;
1681         }
1682
1683         return TRUE;
1684 }
1685
1686 #define KEY_PRESS_EVENT_STOP() \
1687         g_signal_stop_emission_by_name(G_OBJECT(widget), \
1688                                        "key_press_event");
1689
1690 static gint textview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1691                                  TextView *textview)
1692 {
1693         SummaryView *summaryview = NULL;
1694         MessageView *messageview = textview->messageview;
1695
1696         if (!event) return FALSE;
1697         if (messageview->mainwin)
1698                 summaryview = messageview->mainwin->summaryview;
1699
1700         switch (event->keyval) {
1701         case GDK_Tab:
1702         case GDK_Home:
1703         case GDK_Left:
1704         case GDK_Up:
1705         case GDK_Right:
1706         case GDK_Down:
1707         case GDK_Page_Up:
1708         case GDK_Page_Down:
1709         case GDK_End:
1710         case GDK_Control_L:
1711         case GDK_Control_R:
1712                 break;
1713         case GDK_space:
1714                 if (summaryview)
1715                         summary_pass_key_press_event(summaryview, event);
1716                 else
1717                         textview_scroll_page(textview, FALSE);
1718                 break;
1719         case GDK_BackSpace:
1720                 textview_scroll_page(textview, TRUE);
1721                 break;
1722         case GDK_Return:
1723                 textview_scroll_one_line(textview,
1724                                          (event->state & GDK_MOD1_MASK) != 0);
1725                 break;
1726         case GDK_Delete:
1727                 if (summaryview)
1728                         summary_pass_key_press_event(summaryview, event);
1729                 break;
1730         case GDK_y:
1731         case GDK_t:
1732         case GDK_l:
1733                 if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) == 0) {
1734                         KEY_PRESS_EVENT_STOP();
1735                         mimeview_pass_key_press_event(messageview->mimeview,
1736                                                       event);
1737                         break;
1738                 }
1739                 /* possible fall through */
1740         default:
1741                 if (summaryview &&
1742                     event->window != messageview->mainwin->window->window) {
1743                         GdkEventKey tmpev = *event;
1744
1745                         tmpev.window = messageview->mainwin->window->window;
1746                         KEY_PRESS_EVENT_STOP();
1747                         gtk_widget_event(messageview->mainwin->window,
1748                                          (GdkEvent *)&tmpev);
1749                 }
1750                 break;
1751         }
1752
1753         return TRUE;
1754 }
1755
1756 static gint show_url_timeout_cb(gpointer data)
1757 {
1758         TextView *textview = (TextView *)data;
1759         
1760         TEXTVIEW_STATUSBAR_POP(textview);
1761         textview->show_url_timeout_tag = 0;
1762         return FALSE;
1763 }
1764
1765 /*!
1766  *\brief    Check to see if a web URL has been disguised as a different
1767  *          URL (possible with HTML email).
1768  *
1769  *\param    uri The uri to check
1770  *
1771  *\param    textview The TextView the URL is contained in
1772  *
1773  *\return   gboolean TRUE if the URL is ok, or if the user chose to open
1774  *          it anyway, otherwise FALSE          
1775  */
1776 static gboolean uri_security_check(RemoteURI *uri, TextView *textview) 
1777 {
1778         gchar *clicked_str;
1779         gboolean retval = TRUE;
1780
1781         if (g_strncasecmp(uri->uri, "http:", 5) &&
1782             g_strncasecmp(uri->uri, "https:", 6) &&
1783             g_strncasecmp(uri->uri, "www.", 4)) 
1784                 return retval;
1785
1786         clicked_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1787                                              uri->start,
1788                                              uri->end);
1789         if (clicked_str == NULL)
1790                 return TRUE;
1791
1792         if (strcmp(clicked_str, uri->uri) &&
1793             (!g_strncasecmp(clicked_str, "http:",  5) ||
1794              !g_strncasecmp(clicked_str, "https:", 6) ||
1795              !g_strncasecmp(clicked_str, "www.",   4))) {
1796                 gchar *str;
1797                 retval = FALSE;
1798
1799                 /* allow uri->uri    == http://somewhere.com
1800                    and   clicked_str ==        somewhere.com */
1801                 str = g_strconcat("http://", clicked_str, NULL);
1802
1803                 if (!g_strcasecmp(str, uri->uri))
1804                         retval = TRUE;
1805                 g_free(str);
1806         }
1807
1808         if (retval == FALSE) {
1809                 gchar *msg = NULL;
1810                 AlertValue resp;
1811
1812                 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1813                                         "the apparent URL (%s).  \n"
1814                                         "Open it anyway?"),
1815                                         uri->uri, clicked_str);
1816                 resp = alertpanel_with_type(_("Warning"), 
1817                                   msg,
1818                                   _("Yes"), 
1819                                   _("No"),
1820                                   NULL, NULL, ALERT_WARNING);
1821                 g_free(msg);
1822                 if (resp == G_ALERTDEFAULT)
1823                         retval = TRUE;
1824         } 
1825         g_free(clicked_str);
1826         return retval;
1827 }
1828
1829 static gboolean textview_uri_button_pressed(GtkTextTag *tag, GObject *obj,
1830                                             GdkEvent *event, GtkTextIter *iter,
1831                                             TextView *textview)
1832 {
1833         GtkTextIter start_iter, end_iter;
1834         gint start_pos, end_pos;
1835         GdkEventButton *bevent;
1836         GSList *cur;
1837         gchar *trimmed_uri;
1838         if (event->type != GDK_BUTTON_PRESS && event->type != GDK_2BUTTON_PRESS
1839                 && event->type != GDK_MOTION_NOTIFY)
1840                 return FALSE;
1841
1842         bevent = (GdkEventButton *) event;
1843
1844         /* get start and end positions */
1845         start_iter = *iter;
1846         if(!gtk_text_iter_backward_to_tag_toggle(&start_iter, tag)) {
1847                 debug_print("Can't find start.");
1848                 return FALSE;
1849         }
1850         start_pos = gtk_text_iter_get_offset(&start_iter);
1851
1852         end_iter = *iter;
1853         if(!gtk_text_iter_forward_to_tag_toggle(&end_iter, tag)) {
1854                 debug_print("Can't find end");
1855                 return FALSE;
1856         }
1857         end_pos = gtk_text_iter_get_offset(&end_iter);
1858
1859         /* search current uri */
1860         for (cur = textview->uri_list; cur != NULL; cur = cur->next) {
1861                 RemoteURI *uri = (RemoteURI *)cur->data;
1862
1863                 if (start_pos != uri->start || end_pos !=  uri->end)
1864                         continue;
1865
1866                 trimmed_uri = trim_string(uri->uri, 60);
1867                 /* hover or single click: display url in statusbar */
1868
1869                 if (event->type == GDK_MOTION_NOTIFY
1870                     || (event->type == GDK_BUTTON_PRESS && bevent->button == 1)) {
1871                         if (textview->messageview->mainwin) {
1872                                 TEXTVIEW_STATUSBAR_PUSH(textview, trimmed_uri);
1873                                 textview->show_url_timeout_tag = gtk_timeout_add
1874                                         (4000, show_url_timeout_cb, textview);
1875                         }
1876                         return FALSE;
1877                 }
1878                 /* doubleclick: open compose / add address / browser */
1879                 if ((event->type == GDK_2BUTTON_PRESS && bevent->button == 1) ||
1880                         bevent->button == 2 || bevent->button == 3) {
1881                         if (!g_strncasecmp(uri->uri, "mailto:", 7)) {
1882                                 if (bevent->button == 3) {
1883                                         gchar *fromname, *fromaddress;
1884                                                 
1885                                         /* extract url */
1886                                         fromaddress = g_strdup(uri->uri + 7);
1887                                         /* Hiroyuki: please put this function in utils.c! */
1888                                         fromname = procheader_get_fromname(fromaddress);
1889                                         extract_address(fromaddress);
1890                                         g_message("adding from textview %s <%s>", fromname, fromaddress);
1891                                         /* Add to address book - Match */
1892                                         addressbook_add_contact( fromname, fromaddress, NULL );
1893                                                 
1894                                         g_free(fromaddress);
1895                                         g_free(fromname);
1896                                 } else {
1897                                         PrefsAccount *account = NULL;
1898
1899                                         if (textview->messageview && textview->messageview->msginfo &&
1900                                             textview->messageview->msginfo->folder) {
1901                                                 FolderItem   *folder_item;
1902
1903                                                 folder_item = textview->messageview->msginfo->folder;
1904                                                 if (folder_item->prefs && folder_item->prefs->enable_default_account)
1905                                                         account = account_find_from_id(folder_item->prefs->default_account);
1906                                         }
1907                                         compose_new(account, uri->uri + 7, NULL);
1908                                 }
1909                                 return TRUE;
1910                         } else {
1911                                 if (textview_uri_security_check(textview, uri) == TRUE) 
1912                                         open_uri(uri->uri,
1913                                                  prefs_common.uri_cmd);
1914                                 return TRUE;
1915                         }
1916                 }
1917                 g_free(trimmed_uri);
1918         }
1919
1920         return FALSE;
1921 }
1922
1923 /*!
1924  *\brief    Check to see if a web URL has been disguised as a different
1925  *          URL (possible with HTML email).
1926  *
1927  *\param    uri The uri to check
1928  *
1929  *\param    textview The TextView the URL is contained in
1930  *
1931  *\return   gboolean TRUE if the URL is ok, or if the user chose to open
1932  *          it anyway, otherwise FALSE          
1933  */
1934 static gboolean textview_uri_security_check(TextView *textview, RemoteURI *uri)
1935 {
1936         gchar *visible_str;
1937         gboolean retval = TRUE;
1938
1939         if (is_uri_string(uri->uri) == FALSE)
1940                 return TRUE;
1941
1942         visible_str = gtk_editable_get_chars(GTK_EDITABLE(textview->text),
1943                                              uri->start, uri->end);
1944         if (visible_str == NULL)
1945                 return TRUE;
1946
1947         if (strcmp(visible_str, uri->uri) != 0 && is_uri_string(visible_str)) {
1948                 gchar *uri_path;
1949                 gchar *visible_uri_path;
1950
1951                 uri_path = get_uri_path(uri->uri);
1952                 visible_uri_path = get_uri_path(visible_str);
1953                 if (strcmp(uri_path, visible_uri_path) != 0)
1954                         retval = FALSE;
1955         }
1956
1957         if (retval == FALSE) {
1958                 gchar *msg;
1959                 AlertValue aval;
1960
1961                 msg = g_strdup_printf(_("The real URL (%s) is different from\n"
1962                                         "the apparent URL (%s).\n"
1963                                         "Open it anyway?"),
1964                                       uri->uri, visible_str);
1965                 aval = alertpanel_with_type(_("Warning"), msg, _("Yes"), _("No"), NULL,
1966                                             NULL, ALERT_WARNING);
1967                 g_free(msg);
1968                 if (aval == G_ALERTDEFAULT)
1969                         retval = TRUE;
1970         }
1971
1972         g_free(visible_str);
1973
1974         return retval;
1975 }
1976
1977 static void textview_uri_list_remove_all(GSList *uri_list)
1978 {
1979         GSList *cur;
1980
1981         for (cur = uri_list; cur != NULL; cur = cur->next) {
1982                 if (cur->data) {
1983                         g_free(((RemoteURI *)cur->data)->uri);
1984                         g_free(cur->data);
1985                 }
1986         }
1987
1988         g_slist_free(uri_list);
1989 }