added SSL support for POP using OpenSSL
[claws.git] / src / messageview.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999,2000 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 #include "defs.h"
21
22 #include <glib.h>
23 #include <gdk/gdkkeysyms.h>
24 #include <gtk/gtkvbox.h>
25 #include <gtk/gtkcontainer.h>
26 #include <gtk/gtkeditable.h>
27 #include <gtk/gtkwindow.h>
28 #include <gtk/gtktext.h>
29 #include <stdio.h>
30 #include <ctype.h>
31 #include <string.h>
32
33 #include "intl.h"
34 #include "main.h"
35 #include "messageview.h"
36 #include "headerview.h"
37 #include "textview.h"
38 #include "imageview.h"
39 #include "mimeview.h"
40 #include "procmsg.h"
41 #include "procheader.h"
42 #include "procmime.h"
43 #include "prefs_common.h"
44 #include "gtkutils.h"
45 #include "utils.h"
46 #include "rfc2015.h"
47 #include "account.h"
48 #include "alertpanel.h"
49 #include "send.h"
50
51 static void messageview_change_view_type(MessageView    *messageview,
52                                          MessageType     type);
53 static void messageview_destroy_cb      (GtkWidget      *widget,
54                                          MessageView    *messageview);
55 static void messageview_size_allocate_cb(GtkWidget      *widget,
56                                          GtkAllocation  *allocation);
57 static void key_pressed                 (GtkWidget      *widget,
58                                          GdkEventKey    *event,
59                                          MessageView    *messageview);
60
61 MessageView *messageview_create(void)
62 {
63         MessageView *messageview;
64         GtkWidget *vbox;
65         HeaderView *headerview;
66         TextView *textview;
67         ImageView *imageview;
68         MimeView *mimeview;
69
70         debug_print(_("Creating message view...\n"));
71         messageview = g_new0(MessageView, 1);
72
73         messageview->type = MVIEW_TEXT;
74
75         headerview = headerview_create();
76
77         textview = textview_create();
78         textview->messageview = messageview;
79
80         imageview = imageview_create();
81         imageview->messageview = messageview;
82
83         mimeview = mimeview_create();
84         mimeview->textview = textview;
85         mimeview->imageview = imageview;
86         mimeview->messageview = messageview;
87
88         vbox = gtk_vbox_new(FALSE, 0);
89         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET_PTR(headerview),
90                            FALSE, FALSE, 0);
91         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET_PTR(textview),
92                            TRUE, TRUE, 0);
93
94         /* to remove without destroyed */
95         gtk_widget_ref(GTK_WIDGET_PTR(textview));
96         gtk_widget_ref(GTK_WIDGET_PTR(imageview));
97         gtk_widget_ref(GTK_WIDGET_PTR(mimeview));
98
99         messageview->vbox       = vbox;
100         messageview->new_window = FALSE;
101         messageview->window     = NULL;
102         messageview->headerview = headerview;
103         messageview->textview   = textview;
104         messageview->imageview  = imageview;
105         messageview->mimeview   = mimeview;
106
107         return messageview;
108 }
109
110 MessageView *messageview_create_with_new_window(void)
111 {
112         GtkWidget *window;
113         MessageView *msgview;
114
115         window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
116         gtk_window_set_title(GTK_WINDOW(window), "Message");
117         gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE);
118         gtk_widget_set_usize(window, prefs_common.msgwin_width,
119                              prefs_common.msgwin_height);
120
121         msgview = messageview_create();
122
123         gtk_signal_connect(GTK_OBJECT(window), "size_allocate",
124                            GTK_SIGNAL_FUNC(messageview_size_allocate_cb),
125                            msgview);
126         gtk_signal_connect(GTK_OBJECT(window), "destroy",
127                            GTK_SIGNAL_FUNC(messageview_destroy_cb), msgview);
128         gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
129                            GTK_SIGNAL_FUNC(key_pressed), msgview);
130
131         gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET_PTR(msgview));
132         gtk_widget_grab_focus(msgview->textview->text);
133         gtk_widget_show_all(window);
134
135         msgview->new_window = TRUE;
136         msgview->window = window;
137
138         messageview_init(msgview);
139
140         return msgview;
141 }
142
143 void messageview_init(MessageView *messageview)
144 {
145         headerview_init(messageview->headerview);
146         textview_init(messageview->textview);
147         imageview_init(messageview->imageview);
148         mimeview_init(messageview->mimeview);
149         /*messageview_set_font(messageview);*/
150 }
151
152 static void notification_convert_header(gchar *dest, gint len, gchar *src,
153                                         gint header_len)
154 {
155         g_return_if_fail(src != NULL);
156         g_return_if_fail(dest != NULL);
157
158         if (len < 1) return;
159
160         remove_return(src);
161
162         if (is_ascii_str(src)) {
163                 strncpy2(dest, src, len);
164                 dest[len - 1] = '\0';
165                 return;
166         } else
167                 conv_encode_header(dest, len, src, header_len);
168 }
169
170 static gint disposition_notification_queue(PrefsAccount * account,
171                                            gchar * to, const gchar *file)
172 {
173         FolderItem *queue;
174         gchar *tmp, *queue_path;
175         FILE *fp, *src_fp;
176         GSList *cur;
177         gchar buf[BUFFSIZE];
178         gint num;
179
180         debug_print(_("queueing message...\n"));
181         g_return_val_if_fail(account != NULL, -1);
182
183         tmp = g_strdup_printf("%s%cqueue.%d", g_get_tmp_dir(),
184                               G_DIR_SEPARATOR, (gint)file);
185         if ((fp = fopen(tmp, "w")) == NULL) {
186                 FILE_OP_ERROR(tmp, "fopen");
187                 g_free(tmp);
188                 return -1;
189         }
190         if ((src_fp = fopen(file, "r")) == NULL) {
191                 FILE_OP_ERROR(file, "fopen");
192                 fclose(fp);
193                 unlink(tmp);
194                 g_free(tmp);
195                 return -1;
196         }
197         if (change_file_mode_rw(fp, tmp) < 0) {
198                 FILE_OP_ERROR(tmp, "chmod");
199                 g_warning(_("can't change file mode\n"));
200         }
201
202         /* queueing variables */
203         fprintf(fp, "AF:\n");
204         fprintf(fp, "NF:0\n");
205         fprintf(fp, "PS:10\n");
206         fprintf(fp, "SRH:1\n");
207         fprintf(fp, "SFN:\n");
208         fprintf(fp, "DSR:\n");
209         fprintf(fp, "MID:\n");
210         fprintf(fp, "CFG:\n");
211         fprintf(fp, "PT:0\n");
212         fprintf(fp, "S:%s\n", account->address);
213         fprintf(fp, "RQ:\n");
214         if (account->smtp_server)
215                 fprintf(fp, "SSV:%s\n", account->smtp_server);
216         else
217                 fprintf(fp, "SSV:\n");
218         if (account->nntp_server)
219                 fprintf(fp, "NSV:%s\n", account->nntp_server);
220         else
221                 fprintf(fp, "NSV:\n");
222         fprintf(fp, "SSH:\n");
223         fprintf(fp, "R:<%s>", to);
224         fprintf(fp, "\n");
225         fprintf(fp, "\n");
226
227         while (fgets(buf, sizeof(buf), src_fp) != NULL) {
228                 if (fputs(buf, fp) == EOF) {
229                         FILE_OP_ERROR(tmp, "fputs");
230                         fclose(fp);
231                         fclose(src_fp);
232                         unlink(tmp);
233                         g_free(tmp);
234                         return -1;
235                 }
236         }
237
238         fclose(src_fp);
239         if (fclose(fp) == EOF) {
240                 FILE_OP_ERROR(tmp, "fclose");
241                 unlink(tmp);
242                 g_free(tmp);
243                 return -1;
244         }
245
246         queue = folder_get_default_queue();
247         folder_item_scan(queue);
248         queue_path = folder_item_get_path(queue);
249         if (!is_dir_exist(queue_path))
250                 make_dir_hier(queue_path);
251         if ((num = folder_item_add_msg(queue, tmp, TRUE)) < 0) {
252                 g_warning(_("can't queue the message\n"));
253                 unlink(tmp);
254                 g_free(tmp);
255                 g_free(queue_path);
256                 return -1;
257         }
258         g_free(tmp);
259
260         if ((fp = procmsg_open_mark_file(queue_path, TRUE)) == NULL)
261                 g_warning(_("can't open mark file\n"));
262         else {
263                 MsgInfo newmsginfo;
264
265                 newmsginfo.msgnum = num;
266                 newmsginfo.flags = 0;
267                 procmsg_write_flags(&newmsginfo, fp);
268                 fclose(fp);
269         }
270         g_free(queue_path);
271
272         folder_item_scan(queue);
273         folderview_update_item(queue, TRUE);
274
275         return 0;
276 }
277
278 static gint disposition_notification_send(MsgInfo * msginfo)
279 {
280         gchar buf[BUFFSIZE];
281         gchar tmp[MAXPATHLEN + 1];
282         FILE *fp;
283         GSList * to_list;
284         gint ok;
285         gchar * to;
286
287         if ((!msginfo->returnreceiptto) && 
288             (!msginfo->dispositionnotificationto))
289                 return -1;
290
291         /* write to temporary file */
292         g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg%d",
293                    get_rc_dir(), G_DIR_SEPARATOR, (gint)msginfo);
294
295         if ((fp = fopen(tmp, "w")) == NULL) {
296                 FILE_OP_ERROR(tmp, "fopen");
297                 return -1;
298         }
299
300         /* chmod for security */
301         if (change_file_mode_rw(fp, tmp) < 0) {
302                 FILE_OP_ERROR(tmp, "chmod");
303                 g_warning(_("can't change file mode\n"));
304         }
305
306         /* Date */
307         get_rfc822_date(buf, sizeof(buf));
308         fprintf(fp, "Date: %s\n", buf);
309
310         /* From */
311         if (cur_account->name && *cur_account->name) {
312                 notification_convert_header
313                         (buf, sizeof(buf), cur_account->name,
314                          strlen("From: "));
315                 fprintf(fp, "From: %s <%s>\n", buf, cur_account->address);
316         } else
317                 fprintf(fp, "From: %s\n", cur_account->address);
318
319         /* To */
320         if (msginfo->dispositionnotificationto)
321                 to = msginfo->dispositionnotificationto;
322         else
323                 to = msginfo->returnreceiptto;
324         fprintf(fp, "To: %s\n", to);
325
326         /* Subject */
327         notification_convert_header(buf, sizeof(buf), msginfo->subject,
328                                     strlen("Subject: "));
329         fprintf(fp, "Subject: Disposition notification: %s\n", buf);
330
331         if (fclose(fp) == EOF) {
332                 FILE_OP_ERROR(tmp, "fclose");
333                 unlink(tmp);
334                 return -1;
335         }
336
337         to_list = address_list_append(NULL, msginfo->dispositionnotificationto);
338         ok = send_message(tmp, cur_account, to_list);
339         
340         if (ok < 0) {
341                 if (prefs_common.queue_msg) {
342                         AlertValue val;
343                         
344                         val = alertpanel
345                                 (_("Queueing"),
346                                  _("Error occurred while sending the notification.\n"
347                                    "Put this notification into queue folder?"),
348                                  _("OK"), _("Cancel"), NULL);
349                         if (G_ALERTDEFAULT == val) {
350                                 ok = disposition_notification_queue(cur_account, to, tmp);
351                                 if (ok < 0)
352                                         alertpanel_error(_("Can't queue the notification."));
353                         }
354                 } else
355                         alertpanel_error(_("Error occurred while sending the notification."));
356         }
357
358         if (unlink(tmp) < 0) FILE_OP_ERROR(tmp, "unlink");
359
360         return ok;
361 }
362
363 void messageview_show(MessageView *messageview, MsgInfo *msginfo)
364 {
365         FILE *fp;
366         gchar *file;
367         MimeInfo *mimeinfo;
368         MsgInfo *tmpmsginfo;
369
370         g_return_if_fail(msginfo != NULL);
371
372 #if USE_GPGME
373         for (;;) {
374                 if ((fp = procmsg_open_message(msginfo)) == NULL) return;
375                 mimeinfo = procmime_scan_mime_header(fp);
376                 if (!mimeinfo) break;
377
378                 if (!MSG_IS_ENCRYPTED(msginfo->flags) &&
379                     rfc2015_is_encrypted(mimeinfo)) {
380                         MSG_SET_FLAGS(msginfo->flags, MSG_ENCRYPTED);
381                 }
382                 if (MSG_IS_ENCRYPTED(msginfo->flags) &&
383                     !msginfo->plaintext_file  &&
384                     !msginfo->decryption_failed) {
385                         /* This is an encrypted message but it has not yet
386                          * been decrypted and there was no unsuccessful
387                          * decryption attempt */
388                         rfc2015_decrypt_message(msginfo, mimeinfo, fp);
389                         if (msginfo->plaintext_file &&
390                             !msginfo->decryption_failed) {
391                                 fclose(fp);
392                                 continue;
393                         }
394                 }
395
396                 break;
397         }
398 #else /* !USE_GPGME */
399         if ((fp = procmsg_open_message(msginfo)) == NULL) return;
400         mimeinfo = procmime_scan_mime_header(fp);
401 #endif /* USE_GPGME */
402         fclose(fp);
403         if (!mimeinfo) return;
404
405         file = procmsg_get_message_file_path(msginfo);
406         g_return_if_fail(file != NULL);
407
408         tmpmsginfo = procheader_parse(file, msginfo->flags, TRUE);
409
410         if (prefs_common.return_receipt
411             && (tmpmsginfo->dispositionnotificationto
412                 || tmpmsginfo->returnreceiptto)
413             && (MSG_IS_UNREAD(msginfo->flags))) {
414                 gint ok;
415                 
416                 if (alertpanel(_("Return Receipt"), _("Send return receipt ?"),
417                                _("Yes"), _("No"), NULL) == G_ALERTDEFAULT) {
418                         ok = disposition_notification_send(tmpmsginfo);
419                         if (ok < 0)
420                                 alertpanel_error(_("Error occurred while sending notification."));
421                 }
422         }
423
424         headerview_show(messageview->headerview, tmpmsginfo);
425         procmsg_msginfo_free(tmpmsginfo);
426
427         if (mimeinfo->mime_type != MIME_TEXT) {
428                 messageview_change_view_type(messageview, MVIEW_MIME);
429                 mimeview_show_message(messageview->mimeview, mimeinfo, file);
430         } else {
431                 messageview_change_view_type(messageview, MVIEW_TEXT);
432                 textview_show_message(messageview->textview, mimeinfo, file);
433                 procmime_mimeinfo_free(mimeinfo);
434         }
435
436         g_free(file);
437 }
438
439 static void messageview_change_view_type(MessageView *messageview,
440                                          MessageType type)
441 {
442         TextView *textview = messageview->textview;
443         ImageView *imageview = messageview->imageview;
444         MimeView *mimeview = messageview->mimeview;
445
446         if (messageview->type == type) return;
447
448         if (type == MVIEW_MIME) {
449                 gtk_container_remove
450                         (GTK_CONTAINER(GTK_WIDGET_PTR(messageview)),
451                          GTK_WIDGET_PTR(textview));
452                 gtk_box_pack_start(GTK_BOX(messageview->vbox),
453                                    GTK_WIDGET_PTR(mimeview), TRUE, TRUE, 0);
454                 gtk_container_add(GTK_CONTAINER(mimeview->vbox),
455                                   GTK_WIDGET_PTR(textview));
456                 mimeview->type = MIMEVIEW_TEXT;
457         } else if (type == MVIEW_TEXT) {
458                 gtk_container_remove
459                         (GTK_CONTAINER(GTK_WIDGET_PTR(messageview)),
460                          GTK_WIDGET_PTR(mimeview));
461
462                 if (mimeview->vbox == GTK_WIDGET_PTR(textview)->parent) {
463                         gtk_container_remove(GTK_CONTAINER(mimeview->vbox),
464                                              GTK_WIDGET_PTR(textview));
465                 } else {
466                         gtk_container_remove(GTK_CONTAINER(mimeview->vbox),
467                                              GTK_WIDGET_PTR(imageview));
468                 }
469
470                 gtk_box_pack_start(GTK_BOX(messageview->vbox),
471                                    GTK_WIDGET_PTR(textview), TRUE, TRUE, 0);
472         } else
473                 return;
474
475         messageview->type = type;
476 }
477
478 void messageview_clear(MessageView *messageview)
479 {
480         messageview_change_view_type(messageview, MVIEW_TEXT);
481         headerview_clear(messageview->headerview);
482         textview_clear(messageview->textview);
483 }
484
485 void messageview_destroy(MessageView *messageview)
486 {
487         GtkWidget *textview  = GTK_WIDGET_PTR(messageview->textview);
488         GtkWidget *imageview = GTK_WIDGET_PTR(messageview->imageview);
489         GtkWidget *mimeview  = GTK_WIDGET_PTR(messageview->mimeview);
490
491         headerview_destroy(messageview->headerview);
492         textview_destroy(messageview->textview);
493         imageview_destroy(messageview->imageview);
494         mimeview_destroy(messageview->mimeview);
495
496         g_free(messageview);
497
498         gtk_widget_unref(textview);
499         gtk_widget_unref(imageview);
500         gtk_widget_unref(mimeview);
501 }
502
503 void messageview_quote_color_set(void)
504 {
505 }
506
507 void messageview_set_font(MessageView *messageview)
508 {
509         textview_set_font(messageview->textview, NULL);
510 }
511
512 void messageview_copy_clipboard(MessageView *messageview)
513 {
514         if (messageview->type == MVIEW_TEXT)
515                 gtk_editable_copy_clipboard
516                         (GTK_EDITABLE(messageview->textview->text));
517 }
518
519 void messageview_select_all(MessageView *messageview)
520 {
521         if (messageview->type == MVIEW_TEXT)
522                 gtk_editable_select_region
523                         (GTK_EDITABLE(messageview->textview->text), 0, -1);
524 }
525
526 GtkWidget *messageview_get_text_widget(MessageView *messageview)
527 {
528         return messageview->textview->text;
529 }
530
531 static void messageview_destroy_cb(GtkWidget *widget, MessageView *messageview)
532 {
533         messageview_destroy(messageview);
534 }
535
536 static void messageview_size_allocate_cb(GtkWidget *widget,
537                                          GtkAllocation *allocation)
538 {
539         g_return_if_fail(allocation != NULL);
540
541         prefs_common.msgwin_width  = allocation->width;
542         prefs_common.msgwin_height = allocation->height;
543 }
544
545 static void key_pressed(GtkWidget *widget, GdkEventKey *event,
546                         MessageView *messageview)
547 {
548         if (event && event->keyval == GDK_Escape && messageview->window)
549                 gtk_widget_destroy(messageview->window);
550 }