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