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