Fix bug 2458 “Option to force header type to pre-defined-only”
[claws.git] / src / compose.c
index 03dbf485edffdcb959e65de230a7c070ca7f245c..4306e8ec2f94b40df513af8bff36561303f3cc4e 100644 (file)
@@ -1,6 +1,6 @@
 /*
- * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
- * Copyright (C) 1999-2013 Hiroyuki Yamamoto and the Claws Mail team
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2015 Hiroyuki Yamamoto and the Claws Mail team
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -14,7 +14,7 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
- * 
+ *
  */
 
 #ifdef HAVE_CONFIG_H
@@ -737,6 +737,7 @@ static GtkRadioActionEntry compose_radio_enc_entries[] =
        ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
        ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
        ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
+       ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
        ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
        ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
        ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
@@ -1306,11 +1307,15 @@ static void compose_force_encryption(Compose *compose, PrefsAccount *account,
                if (system) {
                        g_free(compose->privacy_system);
                        compose->privacy_system = NULL;
+                       g_free(compose->encdata);
+                       compose->encdata = NULL;
                }
                if (compose->privacy_system == NULL)
                        compose->privacy_system = g_strdup(privacy);
                else if (*(compose->privacy_system) == '\0') {
                        g_free(compose->privacy_system);
+                       g_free(compose->encdata);
+                       compose->encdata = NULL;
                        compose->privacy_system = g_strdup(privacy);
                }
                compose_update_privacy_system_menu_item(compose, FALSE);
@@ -1337,6 +1342,8 @@ static void compose_force_signing(Compose *compose, PrefsAccount *account, const
                if (system) {
                        g_free(compose->privacy_system);
                        compose->privacy_system = NULL;
+                       g_free(compose->encdata);
+                       compose->encdata = NULL;
                }
                if (compose->privacy_system == NULL)
                        compose->privacy_system = g_strdup(privacy);
@@ -3575,9 +3582,10 @@ static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *fi
        gint len;
        FILE *fp;
        gboolean prev_autowrap;
-       struct stat file_stat;
+       GStatBuf file_stat;
        int ret;
        GString *file_contents = NULL;
+       ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
 
        cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
 
@@ -3644,15 +3652,14 @@ static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *fi
                if (g_utf8_validate(buf, -1, NULL) == TRUE)
                        str = g_strdup(buf);
                else {
+                       codeconv_set_strict(TRUE);
                        str = conv_codeset_strdup
                                (buf, cur_encoding, CS_INTERNAL);
+                       codeconv_set_strict(FALSE);
+
                        if (!str) {
-                               alertpanel_error(_("Unable to insert the file "
-                               "because converting to the internal encoding "
-                               "failed. This may be caused by a binary file "
-                               "or a wrongly encoded text file. If you are "
-                               "sure this is the right file then try "
-                               "attaching it instead."));
+                               result = COMPOSE_INSERT_INVALID_CHARACTER;
+                               break;
                        }
                }
                if (!str) continue;
@@ -3670,20 +3677,22 @@ static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *fi
                g_free(str);
        }
 
-       gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
-       g_string_free(file_contents, TRUE);
+       if (result == COMPOSE_INSERT_SUCCESS) {
+               gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
 
-       compose_changed_cb(NULL, compose);
-       g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
-                                         G_CALLBACK(text_inserted),
-                                         compose);
-       compose->autowrap = prev_autowrap;
-       if (compose->autowrap)
-               compose_wrap_all(compose);
+               compose_changed_cb(NULL, compose);
+               g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
+                                                 G_CALLBACK(text_inserted),
+                                                 compose);
+               compose->autowrap = prev_autowrap;
+               if (compose->autowrap)
+                       compose_wrap_all(compose);
+       }
 
+       g_string_free(file_contents, TRUE);
        fclose(fp);
 
-       return COMPOSE_INSERT_SUCCESS;
+       return result;
 }
 
 static gboolean compose_attach_append(Compose *compose, const gchar *file,
@@ -3901,7 +3910,7 @@ static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
 
                outfile = procmime_get_tmp_file_name(child);
                if ((err = procmime_get_part(outfile, child)) < 0)
-                       g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
+                       g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
                else {
                        gchar *content_type;
 
@@ -5152,7 +5161,7 @@ gint compose_send(Compose *compose)
                        alertpanel_error(_("Could not queue message for sending:\n\n"
                                           "Signature failed: %s"), privacy_get_error());
                } else if (val == -2 && errno != 0) {
-                       alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
+                       alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
                } else {
                        alertpanel_error(_("Could not queue message for sending."));
                }
@@ -5509,8 +5518,8 @@ static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gbool
 {
        GtkTextBuffer *buffer;
        GtkTextIter start, end;
-       gchar *chars;
-       gchar *buf;
+       gchar *chars, *tmp_enc_file, *content;
+       gchar *buf, *msg;
        const gchar *out_codeset;
        EncodingType encoding = ENC_UNKNOWN;
        MimeInfo *mimemsg, *mimetext;
@@ -5518,6 +5527,7 @@ static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gbool
        const gchar *src_codeset = CS_INTERNAL;
        gchar *from_addr = NULL;
        gchar *from_name = NULL;
+       FolderItem *outbox;
 
        if (action == COMPOSE_WRITE_FOR_SEND)
                attach_parts = TRUE;
@@ -5598,7 +5608,6 @@ static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gbool
 
                if (!buf) {
                        AlertValue aval;
-                       gchar *msg;
 
                        msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
                                                "to the specified %s charset.\n"
@@ -5655,7 +5664,6 @@ static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gbool
            encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
            check_line_length(buf, 1000, &line) < 0) {
                AlertValue aval;
-               gchar *msg;
 
                msg = g_strdup_printf
                        (_("Line %d exceeds the line length limit (998 bytes).\n"
@@ -5727,6 +5735,54 @@ static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gbool
        }
        g_free(from_name);
        g_free(from_addr);
+
+       if (compose->use_encryption) {
+               if (compose->encdata != NULL &&
+                               strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
+
+                       /* First, write an unencrypted copy and save it to outbox, if
+                        * user wants that. */
+                       if (compose->account->save_encrypted_as_clear_text) {
+                               debug_print("saving sent message unencrypted...\n");
+                               FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
+                               if (tmpfp) {
+                                       fclose(tmpfp);
+
+                                       /* fp now points to a file with headers written,
+                                        * let's make a copy. */
+                                       rewind(fp);
+                                       content = file_read_stream_to_str(fp);
+
+                                       str_write_to_file(content, tmp_enc_file);
+                                       g_free(content);
+
+                                       /* Now write the unencrypted body. */
+                                       if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
+                                               procmime_write_mimeinfo(mimemsg, tmpfp);
+                                               fclose(tmpfp);
+
+                                               outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
+                                               if (!outbox)
+                                                       outbox = folder_get_default_outbox();
+
+                                               procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
+                                               claws_unlink(tmp_enc_file);
+                                       } else {
+                                               g_warning("Can't open file %s\n", tmp_enc_file);
+                                       }
+                               } else {
+                                       g_warning("couldn't get tempfile\n");
+                               }
+                       }
+                       if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
+                               debug_print("Couldn't encrypt mime structure: %s.\n",
+                                               privacy_get_error());
+                               alertpanel_error(_("Couldn't encrypt the email: %s"),
+                                               privacy_get_error());
+                       }
+               }
+       }
+
        procmime_write_mimeinfo(mimemsg, fp);
        
        procmime_mimeinfo_free_all(mimemsg);
@@ -5910,7 +5966,7 @@ static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
        tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
                              G_DIR_SEPARATOR, compose, (guint) rand());
        debug_print("queuing to %s\n", tmp);
-       if ((fp = g_fopen(tmp, "wb")) == NULL) {
+       if ((fp = g_fopen(tmp, "w+b")) == NULL) {
                FILE_OP_ERROR(tmp, "fopen");
                g_free(tmp);
                return -2;
@@ -5972,7 +6028,6 @@ static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
                err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
                err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
                if (compose->use_encryption) {
-                       gchar *encdata;
                        if (!compose_warn_encryption(compose)) {
                                fclose(fp);
                                claws_unlink(tmp);
@@ -5982,16 +6037,16 @@ static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
                        if (mailac && mailac->encrypt_to_self) {
                                GSList *tmp_list = g_slist_copy(compose->to_list);
                                tmp_list = g_slist_append(tmp_list, compose->account->address);
-                               encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
+                               compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
                                g_slist_free(tmp_list);
                        } else {
-                               encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
+                               compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
                        }
-                       if (encdata != NULL) {
-                               if (strcmp(encdata, "_DONT_ENCRYPT_")) {
+                       if (compose->encdata != NULL) {
+                               if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
                                        err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
                                        err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n", 
-                                               encdata) < 0);
+                                               compose->encdata) < 0);
                                } /* else we finally dont want to encrypt */
                        } else {
                                err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
@@ -6004,7 +6059,6 @@ static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
                                g_free(tmp);
                                return -5;
                        }
-                       g_free(encdata);
                }
        }
 
@@ -6123,7 +6177,7 @@ static int compose_add_attachments(Compose *compose, MimeInfo *parent)
        AttachInfo *ainfo;
        GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
        MimeInfo *mimepart;
-       struct stat statbuf;
+       GStatBuf statbuf;
        gchar *type, *subtype;
        GtkTreeModel *model;
        GtkTreeIter iter;
@@ -6909,6 +6963,10 @@ static void compose_create_header_entry(Compose *compose)
        if (header)
                gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
 
+       gtk_editable_set_editable(
+               GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
+               prefs_common.type_any_header);
+
        g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
                         G_CALLBACK(compose_grab_focus_cb), compose);
 
@@ -7083,6 +7141,8 @@ static GtkWidget *compose_create_header(Compose *compose)
        gtk_widget_show(header_table);
        gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
        gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
+       gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
+                       gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
        gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
 
        gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
@@ -7656,6 +7716,7 @@ static Compose *compose_create(PrefsAccount *account,
        MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
        MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_ISO_8859_5, "Options/Encoding/Cyrillic/"CS_ISO_8859_5, GTK_UI_MANAGER_MENUITEM)
        MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
+       MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
        MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
        MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
 
@@ -7935,6 +7996,7 @@ static Compose *compose_create(PrefsAccount *account,
        compose->use_signing    = FALSE;
        compose->use_encryption = FALSE;
        compose->privacy_system = NULL;
+       compose->encdata        = NULL;
 
        compose->modified = FALSE;
 
@@ -8239,6 +8301,8 @@ static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
        systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
        g_free(compose->privacy_system);
        compose->privacy_system = NULL;
+       g_free(compose->encdata);
+       compose->encdata = NULL;
        if (systemid != NULL) {
                compose->privacy_system = g_strdup(systemid);
 
@@ -8332,6 +8396,7 @@ static void compose_set_out_encoding(Compose *compose)
                case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
                case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
                case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
+               case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
                case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
                case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
                case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
@@ -8775,6 +8840,7 @@ static void compose_destroy(Compose *compose)
        g_free(compose->orig_charset);
 
        g_free(compose->privacy_system);
+       g_free(compose->encdata);
 
 #ifndef USE_NEW_ADDRBOOK
        if (addressbook_get_target_compose() == compose)
@@ -9684,15 +9750,35 @@ static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
        Compose *compose = (Compose *)data;
        GtkTreeSelection *attach_selection;
        gint attach_nr_selected;
+       GtkTreePath *path;
        
        if (!event) return FALSE;
 
        if (event->button == 3) {
                attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
                attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
-                       
+
+               /* If no rows, or just one row is selected, right-click should
+                * open menu relevant to the row being right-clicked on. We
+                * achieve that by selecting the clicked row first. If more
+                * than one row is selected, we shouldn't modify the selection,
+                * as user may want to remove selected rows (attachments). */
+               if (attach_nr_selected < 2) {
+                       gtk_tree_selection_unselect_all(attach_selection);
+                       attach_nr_selected = 0;
+                       gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
+                                       event->x, event->y, &path, NULL, NULL, NULL);
+                       if (path != NULL) {
+                               gtk_tree_selection_select_path(attach_selection, path);
+                               gtk_tree_path_free(path);
+                               attach_nr_selected++;
+                       }
+               }
+
                cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
-               cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
+               /* Properties menu item makes no sense with more than one row
+                * selected, the properties dialog can only edit one attachment. */
+               cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
                        
                gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
                               NULL, NULL, event->button, event->time);
@@ -9767,7 +9853,7 @@ static void compose_send_later_cb(GtkAction *action, gpointer data)
        } else if (val == -1) {
                alertpanel_error(_("Could not queue message."));
        } else if (val == -2) {
-               alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
+               alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
        } else if (val == -3) {
                if (privacy_peek_error())
                alertpanel_error(_("Could not queue message for sending:\n\n"
@@ -10000,7 +10086,7 @@ warn_err:
                compose_close(compose);
                return TRUE;
        } else {
-               struct stat s;
+               GStatBuf s;
                gchar *path;
 
                path = folder_item_fetch_msg(draft, msgnum);
@@ -11028,6 +11114,7 @@ static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn) 
 {
        g_free(compose->privacy_system);
+       g_free(compose->encdata);
 
        compose->privacy_system = g_strdup(account->default_privacy_system);
        compose_update_privacy_system_menu_item(compose, warn);
@@ -11061,11 +11148,8 @@ static void compose_attach_drag_received_cb (GtkWidget         *widget,
        GdkAtom type;
 
        type = gtk_selection_data_get_data_type(data);
-       if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
-#ifdef G_OS_WIN32
-        || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
-#endif
-          ) && gtk_drag_get_source_widget(context) != 
+       if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
+          && gtk_drag_get_source_widget(context) !=
                summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
                list = uri_list_extract_filenames(
                        (const gchar *)gtk_selection_data_get_data(data));
@@ -11148,11 +11232,7 @@ static void compose_insert_drag_received_cb (GtkWidget         *widget,
        /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
         * does not work */
        type = gtk_selection_data_get_data_type(data);
-#ifndef G_OS_WIN32
        if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
-#else
-       if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
-#endif
                AlertValue val = G_ALERTDEFAULT;
                const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);