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