aboutsummaryrefslogblamecommitdiffstats
path: root/mail/em-composer-utils.c
blob: 6625d45c0981c0b3589f3701ae7c735bf4b535c5 (plain) (tree)
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459





















                                                                           




                    

                          

                            





                           
                           
 
                                 


                              








                                                                                






























































                                                           
                                                                                      
 
                     


                     
                               
                                                 
                                                                        

                                         
                                                                                



                                                                        
 



                                                                                                      





                                                      

                                                                                                        




                                                                            





                                                                               

                                                                                                                      
 




































































                                                                                                              
                                                                                                                     

                                         
                                                    






















                                                                                      
                                                                                     





                                                                                        

                                                                                                    











                                                             
                                                                                         









                                                                                        
                                                     




                                                                




                                                      
                                                                                            

                                      


















                                                                                                         
                                                                                      





























                                                                                                                   






                                                                                              

         


                       
                                                 

















                                                                      


                                  


                                                  
                        







                                                                                                                

         
                                                                                    



                                          
        
                                                                                                      

                       


























                                                                                             


                                                                               






















                                                                                                                        

         
                                     

















                                                                                            
        

































































                                                                                                                         

                                                                                                                  














                                                                                                                  
                                                                                                                    



























                                                                   


























                                                                                                           



                           
                                                              

                               

                                 

                                         




                                                                                                     
                                                             
 









                                                                      
                                                  

                            





                                                                   











                                                                      
                                                                               

                               
                                 






                                                             


                                                                          
                                                                                                        


                                                   






























                                                                                                              
                                                                                                                                     









































                                                                                                              
 

























                                                                                      
                                                       







                                                                                       



                                                                              






                                              
                                                                



                                                  
                                                                               



                                                    
                                                                                   


                            
           
                                                                                                                    


                               
                                                          







                                                   







                                                                                                                   



                                                 
                            







                                                                      
                                                                                     



                                                    
                                                                                     


           
                                                                          

                                  


















                                                                                                           
                                                                          
                        
















                                                                                           

                                                                                       





                                                 
                                    



                                                                               
                                                                                   



                                                    
                                                                            




                                                                                           

                                                                                       





                                                 
                            




                                                                      
                                                                                   



                                                    
                                                                            





                                    
                            



                                                             
                                                                         



















                                                                                               
                                                                          



                                          
                                                                                     

                                        
                                                                                     














                                                                
                                                                                     









                                                                                               
                                                                  

                                        
                                                                

                                        
                                                                
















































































































































































































































































































































































































































                                                                                                                                






                                                                                                                   



                                         


















                                                                                             
           
                                                                         

                                           
                                








































































































































































                                                                                                                              

                            















                                                                                                
                                                                    

























































































































































































































                                                                                                        
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 *  Authors: Jeffrey Stedfast <fejj@ximian.com>
 *
 *  Copyright 2003 Ximian, Inc. (www.ximian.com)
 *
 *  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
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <gtk/gtkdialog.h>

#include <gal/util/e-util.h>

#include "mail-mt.h"
#include "mail-ops.h"
#include "mail-tools.h"
#include "mail-config.h"
#include "mail-session.h"
#include "mail-send-recv.h"
#include "mail-component.h"

#include "widgets/misc/e-error.h"

#include "em-utils.h"
#include "em-composer-utils.h"
#include "composer/e-msg-composer.h"
#include "em-format-html.h"
#include "em-format-quote.h"

#include "e-util/e-account-list.h"

#include <camel/camel-string-utils.h>

static EAccount *guess_account (CamelMimeMessage *message, CamelFolder *folder);

struct emcs_t {
    unsigned int ref_count;
    
    CamelFolder *drafts_folder;
    char *drafts_uid;
    
    CamelFolder *folder;
    guint32 flags, set;
    char *uid;
};

static struct emcs_t *
emcs_new (void)
{
    struct emcs_t *emcs;
    
    emcs = g_new (struct emcs_t, 1);
    emcs->ref_count = 1;
    emcs->drafts_folder = NULL;
    emcs->drafts_uid = NULL;
    emcs->folder = NULL;
    emcs->flags = 0;
    emcs->set = 0;
    emcs->uid = NULL;
    
    return emcs;
}

static void
free_emcs (struct emcs_t *emcs)
{
    if (emcs->drafts_folder)
        camel_object_unref (emcs->drafts_folder);
    g_free (emcs->drafts_uid);
    
    if (emcs->folder)
        camel_object_unref (emcs->folder);
    g_free (emcs->uid);
    g_free (emcs);
}

static void
emcs_ref (struct emcs_t *emcs)
{
    emcs->ref_count++;
}

static void
emcs_unref (struct emcs_t *emcs)
{
    emcs->ref_count--;
    if (emcs->ref_count == 0)
        free_emcs (emcs);
}

static void
composer_destroy_cb (gpointer user_data, GObject *deadbeef)
{
    emcs_unref (user_data);
}

static gboolean
ask_confirm_for_unwanted_html_mail (EMsgComposer *composer, EDestination **recipients)
{
    gboolean res;
    GString *str;
    int i;
    
    str = g_string_new("");
    for (i = 0; recipients[i] != NULL; ++i) {
        if (!e_destination_get_html_mail_pref (recipients[i])) {
            const char *name;
            
            name = e_destination_get_textrep (recipients[i], FALSE);
            
            g_string_append_printf (str, "     %s\n", name);
        }
    }

    res = em_utils_prompt_user((GtkWindow *)composer,"/apps/evolution/mail/prompts/unwanted_html",
                   "mail:ask-send-html", str->str, NULL);
    g_string_free(str, TRUE);

    return res;
}

static gboolean
ask_confirm_for_empty_subject (EMsgComposer *composer)
{
    return em_utils_prompt_user((GtkWindow *)composer, "/apps/evolution/mail/prompts/empty_subject",
                    "mail:ask-send-no-subject", NULL);
}

static gboolean
ask_confirm_for_only_bcc (EMsgComposer *composer, gboolean hidden_list_case)
{
    /* If the user is mailing a hidden contact list, it is possible for
       them to create a message with only Bcc recipients without really
       realizing it.  To try to avoid being totally confusing, I've changed
       this dialog to provide slightly different text in that case, to
       better explain what the hell is going on. */
    
    return em_utils_prompt_user((GtkWindow *)composer, "/apps/evolution/mail/prompts/only_bcc",
                    hidden_list_case?"mail:ask-send-only-bcc-contact":"mail:ask-send-only-bcc", NULL);
}

struct _send_data {
    struct emcs_t *emcs;
    EMsgComposer *composer;
    gboolean send;
};

static void
composer_send_queued_cb (CamelFolder *folder, CamelMimeMessage *msg, CamelMessageInfo *info,
             int queued, const char *appended_uid, void *data)
{
    struct emcs_t *emcs;
    struct _send_data *send = data;
    
    emcs = send->emcs;
    
    if (queued) {
        if (emcs && emcs->drafts_folder) {
            /* delete the old draft message */
            camel_folder_set_message_flags (emcs->drafts_folder, emcs->drafts_uid,
                            CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN,
                            CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN);
            camel_object_unref (emcs->drafts_folder);
            emcs->drafts_folder = NULL;
            g_free (emcs->drafts_uid);
            emcs->drafts_uid = NULL;
        }
        
        if (emcs && emcs->folder) {
            /* set any replied flags etc */
            camel_folder_set_message_flags (emcs->folder, emcs->uid, emcs->flags, emcs->set);
            camel_object_unref (emcs->folder);
            emcs->folder = NULL;
            g_free (emcs->uid);
            emcs->uid = NULL;
        }
        
        gtk_widget_destroy (GTK_WIDGET (send->composer));
        
        if (send->send && camel_session_is_online (session)) {
            /* queue a message send */
            mail_send ();
        }
    } else {
        if (!emcs) {
            /* disconnect the previous signal handlers */
            g_signal_handlers_disconnect_matched (send->composer, G_SIGNAL_MATCH_FUNC, 0,
                                  0, NULL, em_utils_composer_send_cb, NULL);
            g_signal_handlers_disconnect_matched (send->composer, G_SIGNAL_MATCH_FUNC, 0,
                                  0, NULL, em_utils_composer_save_draft_cb, NULL);
            
            /* reconnect to the signals using a non-NULL emcs for the callback data */
            em_composer_utils_setup_default_callbacks (send->composer);
        }
        
        e_msg_composer_set_enable_autosave (send->composer, TRUE);
        gtk_widget_show (GTK_WIDGET (send->composer));
    }
    
    camel_message_info_free (info);
    
    if (send->emcs)
        emcs_unref (send->emcs);
    
    g_object_unref (send->composer);
    g_free (send);
}

static CamelMimeMessage *
composer_get_message (EMsgComposer *composer, gboolean post, gboolean save_html_object_data, gboolean *no_recipients)
{
    CamelMimeMessage *message = NULL;
    EDestination **recipients, **recipients_bcc;
    gboolean send_html, confirm_html;
    CamelInternetAddress *cia;
    int hidden = 0, shown = 0;
    int num = 0, num_bcc = 0;
    const char *subject;
    GConfClient *gconf;
    EAccount *account;
    int i;
    
    gconf = mail_config_get_gconf_client ();
    
    /* We should do all of the validity checks based on the composer, and not on
       the created message, as extra interaction may occur when we get the message
       (e.g. to get a passphrase to sign a message) */
    
    /* get the message recipients */
    recipients = e_msg_composer_get_recipients (composer);
    
    cia = camel_internet_address_new ();
    
    /* see which ones are visible/present, etc */
    if (recipients) {
        for (i = 0; recipients[i] != NULL; i++) {
            const char *addr = e_destination_get_address (recipients[i]);
            
            if (addr && addr[0]) {
                camel_address_decode ((CamelAddress *) cia, addr);
                if (camel_address_length ((CamelAddress *) cia) > 0) {
                    camel_address_remove ((CamelAddress *) cia, -1);
                    num++;
                    if (e_destination_is_evolution_list (recipients[i])
                        && !e_destination_list_show_addresses (recipients[i])) {
                        hidden++;
                    } else {
                        shown++;
                    }
                }
            }
        }
    }
    
    recipients_bcc = e_msg_composer_get_bcc (composer);
    if (recipients_bcc) {
        for (i = 0; recipients_bcc[i] != NULL; i++) {
            const char *addr = e_destination_get_address (recipients_bcc[i]);
            
            if (addr && addr[0]) {
                camel_address_decode ((CamelAddress *) cia, addr);
                if (camel_address_length ((CamelAddress *) cia) > 0) {
                    camel_address_remove ((CamelAddress *) cia, -1);
                    num_bcc++;
                }
            }
        }
        
        e_destination_freev (recipients_bcc);
    }
    
    camel_object_unref (cia);
    
    /* I'm sensing a lack of love, er, I mean recipients. */
    if (num == 0) {
        if (post) {
            if (no_recipients)
                *no_recipients = TRUE;
        } else {
            e_error_run((GtkWindow *)composer, "mail:send-no-recipients", NULL);
            goto finished;
        }
    }
    
    if (num > 0 && (num == num_bcc || shown == 0)) {
        /* this means that the only recipients are Bcc's */     
        if (!ask_confirm_for_only_bcc (composer, shown == 0))
            goto finished;
    }
    
    send_html = gconf_client_get_bool (gconf, "/apps/evolution/mail/composer/send_html", NULL);
    confirm_html = gconf_client_get_bool (gconf, "/apps/evolution/mail/prompts/unwanted_html", NULL);
    
    /* Only show this warning if our default is to send html.  If it isn't, we've
       manually switched into html mode in the composer and (presumably) had a good
       reason for doing this. */
    if (e_msg_composer_get_send_html (composer) && send_html && confirm_html) {
        gboolean html_problem = FALSE;
        
        if (recipients) {
            for (i = 0; recipients[i] != NULL && !html_problem; i++) {
                if (!e_destination_get_html_mail_pref (recipients[i]))
                    html_problem = TRUE;
            }
        }
        
        if (html_problem) {
            html_problem = !ask_confirm_for_unwanted_html_mail (composer, recipients);
            if (html_problem)
                goto finished;
        }
    }
    
    /* Check for no subject */
    subject = e_msg_composer_get_subject (composer);
    if (subject == NULL || subject[0] == '\0') {
        if (!ask_confirm_for_empty_subject (composer))
            goto finished;
    }
    
    /* actually get the message now, this will sign/encrypt etc */
    message = e_msg_composer_get_message (composer, save_html_object_data);
    if (message == NULL)
        goto finished;
    
    /* Add info about the sending account */
    account = e_msg_composer_get_preferred_account (composer);
    
    if (account) {
        camel_medium_set_header (CAMEL_MEDIUM (message), "X-Evolution-Account", account->name);
        camel_medium_set_header (CAMEL_MEDIUM (message), "X-Evolution-Transport", account->transport->url);
        camel_medium_set_header (CAMEL_MEDIUM (message), "X-Evolution-Fcc", account->sent_folder_uri);
        if (account->id->organization && *account->id->organization) {
            char *org;
            
            org = camel_header_encode_string (account->id->organization);
            camel_medium_set_header (CAMEL_MEDIUM (message), "Organization", org);
            g_free (org);
        }
    }
    
 finished:
    
    if (recipients)
        e_destination_freev (recipients);
    
    return message;
}

static void
got_post_folder (char *uri, CamelFolder *folder, void *data)
{
    CamelFolder **fp = data;
    
    *fp = folder;
    
    if (folder)
        camel_object_ref (folder);
}

void
em_utils_composer_send_cb (EMsgComposer *composer, gpointer user_data)
{
    CamelMimeMessage *message;
    CamelMessageInfo *info;
    struct _send_data *send;
    gboolean no_recipients = FALSE;
    CamelFolder *mail_folder = NULL, *tmpfldr;
    GList *post_folders = NULL, *post_ptr;
    XEvolution *xev;
    GList *postlist;
    
    postlist = e_msg_composer_hdrs_get_post_to ((EMsgComposerHdrs *) composer->hdrs);
    while (postlist) {
        mail_msg_wait (mail_get_folder (postlist->data, 0, got_post_folder, &tmpfldr, mail_thread_new));
        if (tmpfldr)
            post_folders = g_list_append (post_folders, tmpfldr);
        postlist = g_list_next (postlist);
    }
    
    mail_folder = mail_component_get_folder(NULL, MAIL_COMPONENT_FOLDER_OUTBOX);
    camel_object_ref (mail_folder);
    
    if (!post_folders && !mail_folder)
        return;
    
    if (!(message = composer_get_message (composer, post_folders != NULL, FALSE, &no_recipients)))
        return;
    
    if (no_recipients) {
        /* we're doing a post with no recipients */
        camel_object_unref (mail_folder);
        mail_folder = NULL;
    }
    
    if (mail_folder) {
        /* mail the message */
        info = camel_message_info_new ();
        info->flags = CAMEL_MESSAGE_SEEN;
        
        send = g_malloc (sizeof (*send));
        send->emcs = user_data;
        if (send->emcs)
            emcs_ref (send->emcs);
        send->send = TRUE;
        send->composer = composer;
        g_object_ref (composer);
        gtk_widget_hide (GTK_WIDGET (composer));
        
        e_msg_composer_set_enable_autosave (composer, FALSE);
        
        mail_append_mail (mail_folder, message, info, composer_send_queued_cb, send);
        camel_object_unref (mail_folder);
    }
    
    if (post_folders) {
        /* Remove the X-Evolution* headers if we are in Post-To mode */
        xev = mail_tool_remove_xevolution_headers (message);
        mail_tool_destroy_xevolution (xev);
        
        /* mail the message */
        info = camel_message_info_new ();
        info->flags = CAMEL_MESSAGE_SEEN;
        
        post_ptr = post_folders;
        while (post_ptr) {
            send = g_malloc (sizeof (*send));
            send->emcs = user_data;
            if (send->emcs)
                emcs_ref (send->emcs);
            send->send = FALSE;
            send->composer = composer;
            g_object_ref (composer);
            gtk_widget_hide (GTK_WIDGET (composer));
            
            e_msg_composer_set_enable_autosave (composer, FALSE);
            
            mail_append_mail ((CamelFolder *) post_ptr->data, message, info, composer_send_queued_cb, send);
            camel_object_unref ((CamelFolder *) post_ptr->data);
            
            post_ptr = g_list_next (post_ptr);
        }
    }
    
    camel_object_unref (message);
}

struct _save_draft_info {
    struct emcs_t *emcs;
    EMsgComposer *composer;
    int quit;
};

static void
save_draft_done (CamelFolder *folder, CamelMimeMessage *msg, CamelMessageInfo *info, int ok,
         const char *appended_uid, void *user_data)
{
    struct _save_draft_info *sdi = user_data;
    struct emcs_t *emcs;
    CORBA_Environment ev;
    
    if (!ok)
        goto done;
    
    CORBA_exception_init (&ev);
    GNOME_GtkHTML_Editor_Engine_runCommand (sdi->composer->editor_engine, "saved", &ev);
    CORBA_exception_free (&ev);
    
    if ((emcs = sdi->emcs) == NULL) {
        emcs = emcs_new ();
        
        /* disconnect the previous signal handlers */
        g_signal_handlers_disconnect_by_func (sdi->composer, G_CALLBACK (em_utils_composer_send_cb), NULL);
        g_signal_handlers_disconnect_by_func (sdi->composer, G_CALLBACK (em_utils_composer_save_draft_cb), NULL);
        
        /* reconnect to the signals using a non-NULL emcs for the callback data */
        em_composer_utils_setup_default_callbacks (sdi->composer);
    }
    
    if (emcs->drafts_folder) {
        /* delete the original draft message */
        camel_folder_set_message_flags (emcs->drafts_folder, emcs->drafts_uid,
                        CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN,
                        CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN);
        camel_object_unref (emcs->drafts_folder);
        emcs->drafts_folder = NULL;
        g_free (emcs->drafts_uid);
        emcs->drafts_uid = NULL;
    }
    
    if (emcs->folder) {
        /* set the replied flags etc */
        camel_folder_set_message_flags (emcs->folder, emcs->uid, emcs->flags, emcs->set);
        camel_object_unref (emcs->folder);
        emcs->folder = NULL;
        g_free (emcs->uid);
        emcs->uid = NULL;
    }
    
    if (appended_uid) {
        camel_object_ref (folder);
        emcs->drafts_folder = folder;
        emcs->drafts_uid = g_strdup (appended_uid);
    }
    
    if (sdi->quit)
        gtk_widget_destroy (GTK_WIDGET (sdi->composer));
    
 done:
    g_object_unref (sdi->composer);
    if (sdi->emcs)
        emcs_unref (sdi->emcs);
    g_free (info);
    g_free (sdi);
}

static void
save_draft_folder (char *uri, CamelFolder *folder, gpointer data)
{
    CamelFolder **save = data;
    
    if (folder) {
        *save = folder;
        camel_object_ref (folder);
    }
}

void
em_utils_composer_save_draft_cb (EMsgComposer *composer, int quit, gpointer user_data)
{
    const char *default_drafts_folder_uri = mail_component_get_folder_uri(NULL, MAIL_COMPONENT_FOLDER_DRAFTS);
    CamelFolder *drafts_folder = mail_component_get_folder(NULL, MAIL_COMPONENT_FOLDER_DRAFTS);
    struct _save_draft_info *sdi;
    CamelFolder *folder = NULL;
    CamelMimeMessage *msg;
    CamelMessageInfo *info;
    EAccount *account;
    
    account = e_msg_composer_get_preferred_account (composer);
    if (account && account->drafts_folder_uri &&
        strcmp (account->drafts_folder_uri, default_drafts_folder_uri) != 0) {
        int id;
        
        id = mail_get_folder (account->drafts_folder_uri, 0, save_draft_folder, &folder, mail_thread_new);
        mail_msg_wait (id);
        
        if (!folder) {
            if (e_error_run((GtkWindow *)composer, "mail:ask-default-drafts", NULL) != GTK_RESPONSE_YES)
                return;
            
            folder = drafts_folder;
            camel_object_ref (drafts_folder);
        }
    } else {
        folder = drafts_folder;
        camel_object_ref (folder);
    }
    
    msg = e_msg_composer_get_message_draft (composer);
    
    info = g_new0 (CamelMessageInfo, 1);
    info->flags = CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_SEEN;
    
    sdi = g_malloc (sizeof (struct _save_draft_info));
    sdi->composer = composer;
    g_object_ref (composer);
    sdi->emcs = user_data;
    if (sdi->emcs)
        emcs_ref (sdi->emcs);
    sdi->quit = quit;
    
    mail_append_mail (folder, msg, info, save_draft_done, sdi);
    camel_object_unref (folder);
    camel_object_unref (msg);
}

void
em_composer_utils_setup_callbacks (EMsgComposer *composer, CamelFolder *folder, const char *uid,
                   guint32 flags, guint32 set, CamelFolder *drafts, const char *drafts_uid)
{
    struct emcs_t *emcs;
    
    emcs = emcs_new ();
    
    if (folder && uid) {
        camel_object_ref (folder);
        emcs->folder = folder;
        emcs->uid = g_strdup (uid);
        emcs->flags = flags;
        emcs->set = set;
    }
    
    if (drafts && drafts_uid) {
        camel_object_ref (drafts);
        emcs->drafts_folder = drafts;
        emcs->drafts_uid = g_strdup (drafts_uid);
    }
    
    g_signal_connect (composer, "send", G_CALLBACK (em_utils_composer_send_cb), emcs);
    g_signal_connect (composer, "save-draft", G_CALLBACK (em_utils_composer_save_draft_cb), emcs);
    
    g_object_weak_ref ((GObject *) composer, (GWeakNotify) composer_destroy_cb, emcs);
}

/* Composing messages... */

static EMsgComposer *
create_new_composer (const char *subject, const char *fromuri)
{
    EMsgComposer *composer;
    EAccount *account = NULL;

    composer = e_msg_composer_new ();
    
    if (fromuri)
        account = mail_config_get_account_by_source_url(fromuri);

    e_msg_composer_set_headers (composer, account?account->name:NULL, NULL, NULL, NULL, subject);

    em_composer_utils_setup_default_callbacks (composer);

    return composer;
}

/**
 * em_utils_compose_new_message:
 *
 * Opens a new composer window as a child window of @parent's toplevel
 * window.
 **/
void
em_utils_compose_new_message (const char *fromuri)
{
    GtkWidget *composer;

    composer = (GtkWidget *) create_new_composer ("", fromuri);

    e_msg_composer_unset_changed ((EMsgComposer *)composer);
    e_msg_composer_drop_editor_undo ((EMsgComposer *)composer);

    gtk_widget_show (composer);
}

/**
 * em_utils_compose_new_message_with_mailto:
 * @url: mailto url
 *
 * Opens a new composer window as a child window of @parent's toplevel
 * window. If @url is non-NULL, the composer fields will be filled in
 * according to the values in the mailto url.
 **/
void
em_utils_compose_new_message_with_mailto (const char *url, const char *fromuri)
{
    EMsgComposer *composer;
    EAccount *account = NULL;
    
    if (url != NULL)
        composer = e_msg_composer_new_from_url (url);
    else
        composer = e_msg_composer_new ();
    
    em_composer_utils_setup_default_callbacks (composer);

    if (fromuri
        && (account = mail_config_get_account_by_source_url(fromuri)))
        e_msg_composer_hdrs_set_from_account((EMsgComposerHdrs *)composer->hdrs, account->name);

    e_msg_composer_unset_changed (composer);
    e_msg_composer_drop_editor_undo (composer);
    
    gtk_widget_show ((GtkWidget *) composer);
}

/**
 * em_utils_post_to_folder:
 * @folder: folder
 *
 * Opens a new composer window as a child window of @parent's toplevel
 * window. If @folder is non-NULL, the composer will default to posting
 * mail to the folder specified by @folder.
 **/
void
em_utils_post_to_folder (CamelFolder *folder)
{
    EMsgComposer *composer;
    EAccount *account;
    
    composer = e_msg_composer_new_with_type (E_MSG_COMPOSER_POST);
    
    if (folder != NULL) {
        char *url = mail_tools_folder_to_url (folder);
        
        e_msg_composer_hdrs_set_post_to ((EMsgComposerHdrs *) ((EMsgComposer *) composer)->hdrs, url);
        g_free (url);
        
        url = camel_url_to_string (CAMEL_SERVICE (folder->parent_store)->url, CAMEL_URL_HIDE_ALL);
        account = mail_config_get_account_by_source_url (url);
        g_free (url);
        
        if (account)
            e_msg_composer_hdrs_set_from_account ((EMsgComposerHdrs *) ((EMsgComposer *) composer)->hdrs, account->name);
    }
    
    em_composer_utils_setup_default_callbacks (composer);
    
    e_msg_composer_unset_changed (composer);
    e_msg_composer_drop_editor_undo (composer);
    
    gtk_widget_show ((GtkWidget *) composer);
}

/**
 * em_utils_post_to_url:
 * @url: mailto url
 *
 * Opens a new composer window as a child window of @parent's toplevel
 * window. If @url is non-NULL, the composer will default to posting
 * mail to the folder specified by @url.
 **/
void
em_utils_post_to_url (const char *url)
{
    EMsgComposer *composer;
    
    composer = e_msg_composer_new_with_type (E_MSG_COMPOSER_POST);
    
    if (url != NULL)
        e_msg_composer_hdrs_set_post_to ((EMsgComposerHdrs *) ((EMsgComposer *) composer)->hdrs, url);
    
    em_composer_utils_setup_default_callbacks (composer);
    
    e_msg_composer_unset_changed (composer);
    e_msg_composer_drop_editor_undo (composer);
    
    gtk_widget_show ((GtkWidget *) composer);
}

/* Editing messages... */

static void
edit_message (CamelMimeMessage *message, CamelFolder *drafts, const char *uid)
{
    EMsgComposer *composer;

    composer = e_msg_composer_new_with_message (message);
    em_composer_utils_setup_callbacks (composer, NULL, NULL, 0, 0, drafts, uid);
    e_msg_composer_unset_changed (composer);
    e_msg_composer_drop_editor_undo (composer);
    
    gtk_widget_show (GTK_WIDGET (composer));
}

/**
 * em_utils_edit_message:
 * @message: message to edit
 *
 * Opens a composer filled in with the headers/mime-parts/etc of
 * @message.
 **/
void
em_utils_edit_message (CamelMimeMessage *message)
{
    g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
    
    edit_message (message, NULL, NULL);
}

static void
edit_messages (CamelFolder *folder, GPtrArray *uids, GPtrArray *msgs, void *user_data)
{
    gboolean replace = GPOINTER_TO_INT (user_data);
    int i;
    
    if (msgs == NULL)
        return;
    
    for (i = 0; i < msgs->len; i++) {
        camel_medium_remove_header (CAMEL_MEDIUM (msgs->pdata[i]), "X-Mailer");
        
        if (replace)
            edit_message (msgs->pdata[i], folder, uids->pdata[i]);
        else
            edit_message (msgs->pdata[i], NULL, NULL);
    }
}

/**
 * em_utils_edit_messages:
 * @folder: folder containing messages to edit
 * @uids: uids of messages to edit
 * @replace: replace the existing message(s) when sent or saved.
 *
 * Opens a composer for each message to be edited.
 **/
void
em_utils_edit_messages (CamelFolder *folder, GPtrArray *uids, gboolean replace)
{
    g_return_if_fail (CAMEL_IS_FOLDER (folder));
    g_return_if_fail (uids != NULL);
    
    mail_get_messages (folder, uids, edit_messages, GINT_TO_POINTER (replace));
}

/* Forwarding messages... */
static void
forward_attached (CamelFolder *folder, GPtrArray *messages, CamelMimePart *part, char *subject, const char *fromuri)
{
    EMsgComposer *composer;
    
    composer = create_new_composer (subject, fromuri);
    e_msg_composer_attach (composer, part);
    
    e_msg_composer_unset_changed (composer);
    e_msg_composer_drop_editor_undo (composer);
    
    gtk_widget_show (GTK_WIDGET (composer));
}

static void
forward_attached_cb (CamelFolder *folder, GPtrArray *messages, CamelMimePart *part, char *subject, void *user_data)
{
    if (part)
        forward_attached(folder, messages, part, subject, (char *)user_data);
    g_free(user_data);
}

/**
 * em_utils_forward_attached:
 * @folder: folder containing messages to forward
 * @uids: uids of messages to forward
 * @fromuri: from folder uri
 *
 * If there is more than a single message in @uids, a multipart/digest
 * will be constructed and attached to a new composer window preset
 * with the appropriate header defaults for forwarding the first
 * message in the list. If only one message is to be forwarded, it is
 * forwarded as a simple message/rfc822 attachment.
 **/
void
em_utils_forward_attached (CamelFolder *folder, GPtrArray *uids, const char *fromuri)
{
    g_return_if_fail (CAMEL_IS_FOLDER (folder));
    g_return_if_fail (uids != NULL);
    
    mail_build_attachment (folder, uids, forward_attached_cb, g_strdup(fromuri));
}

static void
forward_non_attached (GPtrArray *messages, int style, const char *fromuri)
{
    CamelMimeMessage *message;
    EMsgComposer *composer;
    char *subject, *text;
    int i;
    guint32 flags;

    if (messages->len == 0)
        return;

    flags = EM_FORMAT_QUOTE_HEADERS;
    if (style == MAIL_CONFIG_FORWARD_QUOTED)
        flags |= EM_FORMAT_QUOTE_CITE;

    for (i = 0; i < messages->len; i++) {
        message = messages->pdata[i];
        subject = mail_tool_generate_forward_subject (message);
        
        text = em_utils_message_to_html (message, _("-------- Forwarded Message --------"), flags);
        
        if (text) {
            composer = create_new_composer (subject, fromuri);
            
            e_msg_composer_set_body_text (composer, text);
                        
            e_msg_composer_unset_changed (composer);
            e_msg_composer_drop_editor_undo (composer);
            
            gtk_widget_show (GTK_WIDGET (composer));
            
            g_free (text);
        }
        
        g_free (subject);
    }
}

static void
forward_inline (CamelFolder *folder, GPtrArray *uids, GPtrArray *messages, void *user_data)
{
    forward_non_attached (messages, MAIL_CONFIG_FORWARD_INLINE, (char *)user_data);
    g_free(user_data);
}

/**
 * em_utils_forward_inline:
 * @folder: folder containing messages to forward
 * @uids: uids of messages to forward
 * @fromuri: from folder/account uri
 *
 * Forwards each message in the 'inline' form, each in its own composer window.
 **/
void
em_utils_forward_inline (CamelFolder *folder, GPtrArray *uids, const char *fromuri)
{
    g_return_if_fail (CAMEL_IS_FOLDER (folder));
    g_return_if_fail (uids != NULL);
    
    mail_get_messages (folder, uids, forward_inline, g_strdup(fromuri));
}

static void
forward_quoted (CamelFolder *folder, GPtrArray *uids, GPtrArray *messages, void *user_data)
{
    forward_non_attached (messages, MAIL_CONFIG_FORWARD_QUOTED, (char *)user_data);
    g_free(user_data);
}

/**
 * em_utils_forward_quoted:
 * @folder: folder containing messages to forward
 * @uids: uids of messages to forward
 * @fromuri: from folder uri
 *
 * Forwards each message in the 'quoted' form (each line starting with
 * a "> "), each in its own composer window.
 **/
void
em_utils_forward_quoted (CamelFolder *folder, GPtrArray *uids, const char *fromuri)
{
    g_return_if_fail (CAMEL_IS_FOLDER (folder));
    g_return_if_fail (uids != NULL);
    
    mail_get_messages (folder, uids, forward_quoted, g_strdup(fromuri));
}

/**
 * em_utils_forward_message:
 * @parent: parent window
 * @message: message to be forwarded
 * @fromuri: from folder uri
 *
 * Forwards a message in the user's configured default style.
 **/
void
em_utils_forward_message (CamelMimeMessage *message, const char *fromuri)
{
    GPtrArray *messages;
    CamelMimePart *part;
    GConfClient *gconf;
    char *subject;
    int mode;
    
    messages = g_ptr_array_new ();
    g_ptr_array_add (messages, message);
    
    gconf = mail_config_get_gconf_client ();
    mode = gconf_client_get_int (gconf, "/apps/evolution/mail/format/forward_style", NULL);
    
    switch (mode) {
    case MAIL_CONFIG_FORWARD_ATTACHED:
    default:
        part = mail_tool_make_message_attachment (message);
        
        subject = mail_tool_generate_forward_subject (message);
        
        forward_attached (NULL, messages, part, subject, fromuri);
        camel_object_unref (part);
        g_free (subject);
        break;
    case MAIL_CONFIG_FORWARD_INLINE:
        forward_non_attached (messages, MAIL_CONFIG_FORWARD_INLINE, fromuri);
        break;
    case MAIL_CONFIG_FORWARD_QUOTED:
        forward_non_attached (messages, MAIL_CONFIG_FORWARD_QUOTED, fromuri);
        break;
    }
    
    g_ptr_array_free (messages, TRUE);
}

/**
 * em_utils_forward_messages:
 * @folder: folder containing messages to forward
 * @uids: uids of messages to forward
 *
 * Forwards a group of messages in the user's configured default
 * style.
 **/
void
em_utils_forward_messages (CamelFolder *folder, GPtrArray *uids, const char *fromuri)
{
    GConfClient *gconf;
    int mode;
    
    gconf = mail_config_get_gconf_client ();
    mode = gconf_client_get_int (gconf, "/apps/evolution/mail/format/forward_style", NULL);
    
    switch (mode) {
    case MAIL_CONFIG_FORWARD_ATTACHED:
    default:
        em_utils_forward_attached (folder, uids, fromuri);
        break;
    case MAIL_CONFIG_FORWARD_INLINE:
        em_utils_forward_inline (folder, uids, fromuri);
        break;
    case MAIL_CONFIG_FORWARD_QUOTED:
        em_utils_forward_quoted (folder, uids, fromuri);
        break;
    }
}

/* Redirecting messages... */

static EMsgComposer *
redirect_get_composer (CamelMimeMessage *message)
{
    EMsgComposer *composer;
    EAccount *account;
    
    /* QMail will refuse to send a message if it finds one of
       it's Delivered-To headers in the message, so remove all
       Delivered-To headers. Fixes bug #23635. */
    while (camel_medium_get_header (CAMEL_MEDIUM (message), "Delivered-To"))
        camel_medium_remove_header (CAMEL_MEDIUM (message), "Delivered-To");
    
    account = guess_account (message, NULL);
    
    composer = e_msg_composer_new_redirect (message, account ? account->name : NULL);
    
    em_composer_utils_setup_default_callbacks (composer);
    
    return composer;
}

/**
 * em_utils_redirect_message:
 * @message: message to redirect
 *
 * Opens a composer to redirect @message (Note: only headers will be
 * editable). Adds Resent-From/Resent-To/etc headers.
 **/
void
em_utils_redirect_message (CamelMimeMessage *message)
{
    EMsgComposer *composer;
    CamelDataWrapper *wrapper;
    
    g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
    
    composer = redirect_get_composer (message);
    
    wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (message));
    
    gtk_widget_show (GTK_WIDGET (composer));
    e_msg_composer_unset_changed (composer);
    e_msg_composer_drop_editor_undo (composer);
}

static void
redirect_msg (CamelFolder *folder, const char *uid, CamelMimeMessage *message, void *user_data)
{
    if (message == NULL)
        return;
    
    em_utils_redirect_message (message);
}

/**
 * em_utils_redirect_message_by_uid:
 * @folder: folder containing message to be redirected
 * @uid: uid of message to be redirected
 *
 * Opens a composer to redirect the message (Note: only headers will
 * be editable). Adds Resent-From/Resent-To/etc headers.
 **/
void
em_utils_redirect_message_by_uid (CamelFolder *folder, const char *uid)
{
    g_return_if_fail (CAMEL_IS_FOLDER (folder));
    g_return_if_fail (uid != NULL);
    
    mail_get_message (folder, uid, redirect_msg, NULL, mail_thread_new);
}

/* Replying to messages... */

static GHashTable *
generate_account_hash (void)
{
    GHashTable *account_hash;
    EAccount *account, *def;
    EAccountList *accounts;
    EIterator *iter;
    
    accounts = mail_config_get_accounts ();
    account_hash = g_hash_table_new (camel_strcase_hash, camel_strcase_equal);
    
    /* add the default account to the hash first */
    if ((def = mail_config_get_default_account ())) {
        if (def->id->address)
            g_hash_table_insert (account_hash, (char *) def->id->address, (void *) def);
    }
    
    iter = e_list_get_iterator ((EList *) accounts);
    while (e_iterator_is_valid (iter)) {
        account = (EAccount *) e_iterator_get (iter);
        
        if (account->id->address) {
            EAccount *acnt;
            
            /* Accounts with identical email addresses that are enabled
             * take precedence over the accounts that aren't. If all
             * accounts with matching email addresses are disabled, then
             * the first one in the list takes precedence. The default
             * account always takes precedence no matter what.
             */
            acnt = g_hash_table_lookup (account_hash, account->id->address);
            if (acnt && acnt != def && !acnt->enabled && account->enabled) {
                g_hash_table_remove (account_hash, acnt->id->address);
                acnt = NULL;
            }
            
            if (!acnt)
                g_hash_table_insert (account_hash, (char *) account->id->address, (void *) account);
        }
        
        e_iterator_next (iter);
    }
    
    g_object_unref (iter);
    
    return account_hash;
}

static EDestination **
em_utils_camel_address_to_destination (CamelInternetAddress *iaddr)
{
    EDestination *dest, **destv;
    int n, i, j;
    
    if (iaddr == NULL)
        return NULL;
    
    if ((n = camel_address_length ((CamelAddress *) iaddr)) == 0)
        return NULL;
    
    destv = g_malloc (sizeof (EDestination *) * (n + 1));
    for (i = 0, j = 0; i < n; i++) {
        const char *name, *addr;
        
        if (camel_internet_address_get (iaddr, i, &name, &addr)) {
            dest = e_destination_new ();
            e_destination_set_name (dest, name);
            e_destination_set_email (dest, addr);
            
            destv[j++] = dest;
        }
    }
    
    if (j == 0) {
        g_free (destv);
        return NULL;
    }
    
    destv[j] = NULL;
    
    return destv;
}

static EMsgComposer *
reply_get_composer (CamelMimeMessage *message, EAccount *account,
            CamelInternetAddress *to, CamelInternetAddress *cc,
            CamelFolder *folder, const char *postto)
{
    const char *message_id, *references;
    EDestination **tov, **ccv;
    EMsgComposer *composer;
    char *subject;
    
    g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
    g_return_val_if_fail (to == NULL || CAMEL_IS_INTERNET_ADDRESS (to), NULL);
    g_return_val_if_fail (cc == NULL || CAMEL_IS_INTERNET_ADDRESS (cc), NULL);

    /* construct the tov/ccv */
    tov = em_utils_camel_address_to_destination (to);
    ccv = em_utils_camel_address_to_destination (cc);

    if (tov || ccv) {
        if (postto)
            composer = e_msg_composer_new_with_type (E_MSG_COMPOSER_MAIL_POST);
        else
            composer = e_msg_composer_new_with_type (E_MSG_COMPOSER_MAIL);
    } else
        composer = e_msg_composer_new_with_type (E_MSG_COMPOSER_POST);

    /* Set the subject of the new message. */
    if ((subject = (char *) camel_mime_message_get_subject (message))) {
        if (strncasecmp (subject, "Re: ", 4) != 0)
            subject = g_strdup_printf ("Re: %s", subject);
        else
            subject = g_strdup (subject);
    } else {
        subject = g_strdup ("");
    }

    e_msg_composer_set_headers (composer, account ? account->name : NULL, tov, ccv, NULL, subject);
    
    g_free (subject);
    
    /* add post-to, if nessecary */
    if (postto) {
        char *store_url = NULL;
        
        if (folder) {
            store_url = camel_url_to_string (CAMEL_SERVICE (folder->parent_store)->url, CAMEL_URL_HIDE_ALL);
            if (store_url[strlen (store_url) - 1] == '/')
                store_url[strlen (store_url)-1] = '\0';
        }
        
        e_msg_composer_hdrs_set_post_to_base (E_MSG_COMPOSER_HDRS (composer->hdrs), store_url ? store_url : "", postto);
        g_free (store_url);
    }
    
    /* Add In-Reply-To and References. */
    message_id = camel_medium_get_header (CAMEL_MEDIUM (message), "Message-Id");
    references = camel_medium_get_header (CAMEL_MEDIUM (message), "References");
    if (message_id) {
        char *reply_refs;
        
        e_msg_composer_add_header (composer, "In-Reply-To", message_id);
        
        if (references)
            reply_refs = g_strdup_printf ("%s %s", references, message_id);
        else
            reply_refs = g_strdup (message_id);
        
        e_msg_composer_add_header (composer, "References", reply_refs);
        g_free (reply_refs);
    } else if (references) {
        e_msg_composer_add_header (composer, "References", references);
    }
    
    e_msg_composer_drop_editor_undo (composer);
    
    return composer;
}

static EAccount *
guess_account_folder(CamelFolder *folder)
{
    EAccount *account;
    char *tmp;

    tmp = camel_url_to_string(CAMEL_SERVICE(folder->parent_store)->url, CAMEL_URL_HIDE_ALL);
    account = mail_config_get_account_by_source_url(tmp);
    g_free(tmp);

    return account;
}

static EAccount *
guess_account (CamelMimeMessage *message, CamelFolder *folder)
{
    GHashTable *account_hash = NULL;
    EAccount *account = NULL;
    const char *tmp;
    int i, j;
    char *types[2] = { CAMEL_RECIPIENT_TYPE_TO, CAMEL_RECIPIENT_TYPE_CC };

    /* check for newsgroup header */
    if (folder
        && camel_medium_get_header((CamelMedium *)message, "Newsgroups")
        && (account = guess_account_folder(folder)))
        return account;

    /* then recipient (to/cc) in account table */
    account_hash = generate_account_hash ();
    for (j=0;account == NULL && j<2;j++) {
        const CamelInternetAddress *to;
        
        to = camel_mime_message_get_recipients(message, types[j]);
        if (to) {
            for (i = 0; camel_internet_address_get(to, i, NULL, &tmp); i++) {
                account = g_hash_table_lookup(account_hash, tmp);
                if (account)
                    break;
            }
        }
    }
    g_hash_table_destroy(account_hash);

    /* then message source */
    if (account == NULL
        && (tmp = camel_mime_message_get_source(message)))
        account = mail_config_get_account_by_source_url(tmp);

    /* and finally, source folder */
    if (account == NULL
        && folder)
        account = guess_account_folder(folder);

    return account;
}

static void
get_reply_sender (CamelMimeMessage *message, CamelInternetAddress **to, const char **postto)
{
    const CamelInternetAddress *reply_to;
    const char *name, *addr, *posthdr;
    int i;
    
    /* check whether there is a 'Newsgroups: ' header in there */
    posthdr = camel_medium_get_header (CAMEL_MEDIUM (message), "Newsgroups");
    if (posthdr && postto) {
        *postto = posthdr;
        while (**postto == ' ')
            (*postto)++;
        return;
    }
    
    reply_to = camel_mime_message_get_reply_to (message);
    if (!reply_to)
        reply_to = camel_mime_message_get_from (message);
    
    if (reply_to) {
        *to = camel_internet_address_new ();
        
        for (i = 0; camel_internet_address_get (reply_to, i, &name, &addr); i++)
            camel_internet_address_add (*to, name, addr);
    }
}

static gboolean
get_reply_list (CamelMimeMessage *message, CamelInternetAddress **to)
{
    const char *header, *p;
    char *addr;
    
    /* Examples:
     * 
     * List-Post: <mailto:list@host.com>
     * List-Post: <mailto:moderator@host.com?subject=list%20posting>
     * List-Post: NO (posting not allowed on this list)
     */
    if (!(header = camel_medium_get_header ((CamelMedium *) message, "List-Post")))
        return FALSE;
    
    while (*header == ' ' || *header == '\t')
        header++;
    
    /* check for NO */
    if (!strncasecmp (header, "NO", 2))
        return FALSE;
    
    /* Search for the first mailto angle-bracket enclosed URL.
     * (See rfc2369, Section 2, paragraph 3 for details) */
    if (!(header = camel_strstrcase (header, "<mailto:")))
        return FALSE;
    
    header += 8;
    
    p = header;
    while (*p && !strchr ("?>", *p))
        p++;
    
    addr = g_strndup (header, p - header);
    
    *to = camel_internet_address_new ();
    camel_internet_address_add (*to, NULL, addr);
    
    g_free (addr);
    
    return TRUE;
}

static void
concat_unique_addrs (CamelInternetAddress *dest, const CamelInternetAddress *src, GHashTable *rcpt_hash)
{
    const char *name, *addr;
    int i;
    
    for (i = 0; camel_internet_address_get (src, i, &name, &addr); i++) {
        if (!g_hash_table_lookup (rcpt_hash, addr)) {
            camel_internet_address_add (dest, name, addr);
            g_hash_table_insert (rcpt_hash, (char *) addr, GINT_TO_POINTER (1));
        }
    }
}

static void
get_reply_all (CamelMimeMessage *message, CamelInternetAddress **to, CamelInternetAddress **cc, const char **postto)
{
    const CamelInternetAddress *reply_to, *to_addrs, *cc_addrs;
    const char *name, *addr, *posthdr;
    GHashTable *rcpt_hash;
    int i;
    
    /* check whether there is a 'Newsgroups: ' header in there */
    posthdr = camel_medium_get_header (CAMEL_MEDIUM(message), "Newsgroups");
    if (posthdr && postto) {
        *postto = posthdr;
        while (**postto == ' ')
            (*postto)++;
    }
    
    rcpt_hash = generate_account_hash ();
    
    reply_to = camel_mime_message_get_reply_to (message);
    if (!reply_to)
        reply_to = camel_mime_message_get_from (message);
    
    to_addrs = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO);
    cc_addrs = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC);
    
    *to = camel_internet_address_new ();
    *cc = camel_internet_address_new ();
    
    if (reply_to) {
        for (i = 0; camel_internet_address_get (reply_to, i, &name, &addr); i++) {
            /* ignore references to the Reply-To address in the To and Cc lists */
            if (addr && !g_hash_table_lookup (rcpt_hash, addr)) {
                /* In the case that we are doing a Reply-To-All, we do not want
                   to include the user's email address because replying to oneself
                   is kinda silly. */
                
                camel_internet_address_add (*to, name, addr);
                g_hash_table_insert (rcpt_hash, (char *) addr, GINT_TO_POINTER (1));
            }
        }
    }
    
    concat_unique_addrs (*cc, to_addrs, rcpt_hash);
    concat_unique_addrs (*cc, cc_addrs, rcpt_hash);
    
    /* promote the first Cc: address to To: if To: is empty */
    if (camel_address_length ((CamelAddress *) *to) == 0 && camel_address_length ((CamelAddress *) *cc) > 0) {
        camel_internet_address_get (*cc, 0, &name, &addr);
        camel_internet_address_add (*to, name, addr);
        camel_address_remove ((CamelAddress *) *cc, 0);
    }

    /* if To: is still empty, may we removed duplicates (i.e. ourself), so add the original To if it was set */
    if (camel_address_length((CamelAddress *)*to) == 0
        && (camel_internet_address_get(to_addrs, 0, &name, &addr)
        || camel_internet_address_get(cc_addrs, 0, &name, &addr))) {
        camel_internet_address_add(*to, name, addr);
    }
    
    g_hash_table_destroy (rcpt_hash);
}

enum {
    ATTRIB_UNKNOWN,
    ATTRIB_CUSTOM,
    ATTRIB_TIMEZONE,
    ATTRIB_STRFTIME,
    ATTRIB_TM_SEC,
    ATTRIB_TM_MIN,
    ATTRIB_TM_24HOUR,
    ATTRIB_TM_12HOUR,
    ATTRIB_TM_MDAY,
    ATTRIB_TM_MON,
    ATTRIB_TM_YEAR,
    ATTRIB_TM_2YEAR,
    ATTRIB_TM_WDAY, /* not actually used */
    ATTRIB_TM_YDAY,
};

typedef void (* AttribFormatter) (GString *str, const char *attr, CamelMimeMessage *message);

static void
format_sender (GString *str, const char *attr, CamelMimeMessage *message)
{
    const CamelInternetAddress *sender;
    const char *name, *addr;
    
    sender = camel_mime_message_get_from (message);
    if (sender != NULL && camel_address_length (CAMEL_ADDRESS (sender)) > 0) {
        camel_internet_address_get (sender, 0, &name, &addr);
    } else {
        name = _("an unknown sender");
    }
    
    if (name && !strcmp (attr, "{SenderName}")) {
        g_string_append (str, name);
    } else if (addr && !strcmp (attr, "{SenderEMail}")) {
        g_string_append (str, addr);
    } else if (name) {
        g_string_append (str, name);
    } else if (addr) {
        g_string_append (str, addr);
    }
}

static struct {
    const char *name;
    int type;
    struct {
        const char *format;         /* strftime or printf format */
        AttribFormatter formatter;  /* custom formatter */
    } v;
} attribvars[] = {
    { "{Sender}",            ATTRIB_CUSTOM,    { NULL,    format_sender  } },
    { "{SenderName}",        ATTRIB_CUSTOM,    { NULL,    format_sender  } },
    { "{SenderEMail}",       ATTRIB_CUSTOM,    { NULL,    format_sender  } },
    { "{AbbrevWeekdayName}", ATTRIB_STRFTIME,  { "%a",    NULL           } },
    { "{WeekdayName}",       ATTRIB_STRFTIME,  { "%A",    NULL           } },
    { "{AbbrevMonthName}",   ATTRIB_STRFTIME,  { "%b",    NULL           } },
    { "{MonthName}",         ATTRIB_STRFTIME,  { "%B",    NULL           } },
    { "{AmPmUpper}",         ATTRIB_STRFTIME,  { "%p",    NULL           } },
    { "{AmPmLower}",         ATTRIB_STRFTIME,  { "%P",    NULL           } },
    { "{Day}",               ATTRIB_TM_MDAY,   { "%02d",  NULL           } },  /* %d  01-31 */
    { "{ Day}",              ATTRIB_TM_MDAY,   { "% 2d",  NULL           } },  /* %e   1-31 */
    { "{24Hour}",            ATTRIB_TM_24HOUR, { "%02d",  NULL           } },  /* %H  00-23 */
    { "{12Hour}",            ATTRIB_TM_12HOUR, { "%02d",  NULL           } },  /* %I  00-12 */
    { "{DayOfYear}",         ATTRIB_TM_YDAY,   { "%d",    NULL           } },  /* %j  1-366 */
    { "{Month}",             ATTRIB_TM_MON,    { "%02d",  NULL           } },  /* %m  01-12 */
    { "{Minute}",            ATTRIB_TM_MIN,    { "%02d",  NULL           } },  /* %M  00-59 */
    { "{Seconds}",           ATTRIB_TM_SEC,    { "%02d",  NULL           } },  /* %S  00-61 */
    { "{2DigitYear}",        ATTRIB_TM_2YEAR,  { "%02d",  NULL           } },  /* %y */
    { "{Year}",              ATTRIB_TM_YEAR,   { "%04d",  NULL           } },  /* %Y */
    { "{TimeZone}",          ATTRIB_TIMEZONE,  { "%+05d", NULL           } }
};

/* Note to translators: this is the attribution string used when quoting messages.
 * each ${Variable} gets replaced with a value. To see a full list of available
 * variables, see em-composer-utils.c:1514 */
#define ATTRIBUTION _("On ${AbbrevWeekdayName}, ${Year}-${Month}-${Day} at ${24Hour}:${Minute} ${TimeZone}, ${Sender} wrote:")

static char *
attribution_format (const char *format, CamelMimeMessage *message)
{
    register const char *inptr;
    const char *start;
    int tzone, len, i;
    char buf[64], *s;
    GString *str;
    struct tm tm;
    time_t date;
    int type;
    
    str = g_string_new ("");
    
    date = camel_mime_message_get_date (message, &tzone);
    /* Convert to UTC */
    date += (tzone / 100) * 60 * 60;
    date += (tzone % 100) * 60;
    
#ifdef HAVE_GMTIME_R
    gmtime_r (&date, &tm);
#else
    memcpy (&tm, gmtime (&date), sizeof (struct tm));
#endif
    
    start = inptr = format;
    while (*inptr != '\0') {
        start = inptr;
        while (*inptr && strncmp (inptr, "${", 2) != 0)
            inptr++;
        
        g_string_append_len (str, start, inptr - start);
        
        if (*inptr == '\0')
            break;
        
        start = ++inptr;
        while (*inptr && *inptr != '}')
            inptr++;
        
        if (*inptr != '}') {
            /* broken translation */
            g_string_append_len (str, "${", 2);
            inptr = start + 1;
            continue;
        }
        
        inptr++;
        len = inptr - start;
        type = ATTRIB_UNKNOWN;
        for (i = 0; i < G_N_ELEMENTS (attribvars); i++) {
            if (!strncmp (attribvars[i].name, start, len)) {
                type = attribvars[i].type;
                break;
            }
        }
        
        switch (type) {
        case ATTRIB_CUSTOM:
            attribvars[i].v.formatter (str, attribvars[i].name, message);
            break;
        case ATTRIB_TIMEZONE:
            g_string_append_printf (str, attribvars[i].v.format, tzone);
            break;
        case ATTRIB_STRFTIME:
            e_utf8_strftime (buf, sizeof (buf), attribvars[i].v.format, &tm);
            g_string_append (str, buf);
            break;
        case ATTRIB_TM_SEC:
            g_string_append_printf (str, attribvars[i].v.format, tm.tm_sec);
            break;
        case ATTRIB_TM_MIN:
            g_string_append_printf (str, attribvars[i].v.format, tm.tm_min);
            break;
        case ATTRIB_TM_24HOUR:
            g_string_append_printf (str, attribvars[i].v.format, tm.tm_hour);
            break;
        case ATTRIB_TM_12HOUR:
            g_string_append_printf (str, attribvars[i].v.format, (tm.tm_hour + 1) % 13);
            break;
        case ATTRIB_TM_MDAY:
            g_string_append_printf (str, attribvars[i].v.format, tm.tm_mday);
            break;
        case ATTRIB_TM_MON:
            g_string_append_printf (str, attribvars[i].v.format, tm.tm_mon + 1);
            break;
        case ATTRIB_TM_YEAR:
            g_string_append_printf (str, attribvars[i].v.format, tm.tm_year + 1900);
            break;
        case ATTRIB_TM_2YEAR:
            g_string_append_printf (str, attribvars[i].v.format, tm.tm_year % 100);
            break;
        case ATTRIB_TM_WDAY:
            /* not actually used */
            g_string_append_printf (str, attribvars[i].v.format, tm.tm_wday);
            break;
        case ATTRIB_TM_YDAY:
            g_string_append_printf (str, attribvars[i].v.format, tm.tm_yday + 1);
            break;
        default:
            /* mis-spelled variable? drop the format argument and continue */
            break;
        }
    }
    
    s = str->str;
    g_string_free (str, FALSE);
    
    return s;
}

static void
composer_set_body (EMsgComposer *composer, CamelMimeMessage *message)
{
    char *text, *credits;
    CamelMimePart *part;
    GConfClient *gconf;
    
    gconf = mail_config_get_gconf_client ();
    
    switch (gconf_client_get_int (gconf, "/apps/evolution/mail/format/reply_style", NULL)) {
    case MAIL_CONFIG_REPLY_DO_NOT_QUOTE:
        /* do nothing */
        break;
    case MAIL_CONFIG_REPLY_ATTACH:
        /* attach the original message as an attachment */
        part = mail_tool_make_message_attachment (message);
        e_msg_composer_attach (composer, part);
        camel_object_unref (part);
        break;
    case MAIL_CONFIG_REPLY_QUOTED:
    default:
        /* do what any sane user would want when replying... */
        credits = attribution_format (ATTRIBUTION, message);
        text = em_utils_message_to_html(message, credits, EM_FORMAT_QUOTE_CITE);
        g_free (credits);
        e_msg_composer_set_body_text(composer, text);
        g_free (text);
        break;
    }
    
    e_msg_composer_drop_editor_undo (composer);
}

/**
 * em_utils_reply_to_message:
 * @message: message to reply to
 * @mode: reply mode
 *
 * Creates a new composer ready to reply to @message.
 **/
void
em_utils_reply_to_message (CamelMimeMessage *message, int mode)
{
    CamelInternetAddress *to = NULL, *cc = NULL;
    EMsgComposer *composer;
    EAccount *account;
    
    account = guess_account (message, NULL);
    
    switch (mode) {
    case REPLY_MODE_SENDER:
        get_reply_sender (message, &to, NULL);
        break;
    case REPLY_MODE_LIST:
        if (get_reply_list (message, &to))
            break;
    case REPLY_MODE_ALL:
        get_reply_all (message, &to, &cc, NULL);
        break;
    }
    
    composer = reply_get_composer (message, account, to, cc, NULL, NULL);
    e_msg_composer_add_message_attachments (composer, message, TRUE);
    
    if (to != NULL)
        camel_object_unref (to);
    
    if (cc != NULL)
        camel_object_unref (cc);
    
    composer_set_body (composer, message);
    
    em_composer_utils_setup_default_callbacks (composer);
    
    gtk_widget_show (GTK_WIDGET (composer));
    e_msg_composer_unset_changed (composer);
}

static void
reply_to_message (CamelFolder *folder, const char *uid, CamelMimeMessage *message, void *user_data)
{
    CamelInternetAddress *to = NULL, *cc = NULL;
    const char *postto = NULL;
    EMsgComposer *composer;
    EAccount *account;
    guint32 flags;
    int mode;
    
    if (message == NULL)
        return;
    
    mode = GPOINTER_TO_INT (user_data);

    account = guess_account (message, folder);
    flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_SEEN;
    
    switch (mode) {
    case REPLY_MODE_SENDER:
        get_reply_sender (message, &to, &postto);
        break;
    case REPLY_MODE_LIST:
        flags |= CAMEL_MESSAGE_ANSWERED_ALL;
        if (get_reply_list (message, &to))
            break;
    case REPLY_MODE_ALL:
        flags |= CAMEL_MESSAGE_ANSWERED_ALL;
        get_reply_all (message, &to, &cc, &postto);
        break;
    }
    
    composer = reply_get_composer (message, account, to, cc, folder, postto);
    e_msg_composer_add_message_attachments (composer, message, TRUE);
    
    if (to != NULL)
        camel_object_unref (to);
    
    if (cc != NULL)
        camel_object_unref (cc);
    
    composer_set_body (composer, message);
    
    em_composer_utils_setup_callbacks (composer, folder, uid, flags, flags, NULL, NULL);
    
    gtk_widget_show (GTK_WIDGET (composer));
    e_msg_composer_unset_changed (composer);
}

/**
 * em_utils_reply_to_message_by_uid:
 * @folder: folder containing message to reply to
 * @uid: message uid
 * @mode: reply mode
 *
 * Creates a new composer ready to reply to the message referenced by
 * @folder and @uid.
 **/
void
em_utils_reply_to_message_by_uid (CamelFolder *folder, const char *uid, int mode)
{
    g_return_if_fail (CAMEL_IS_FOLDER (folder));
    g_return_if_fail (uid != NULL);
    
    mail_get_message (folder, uid, reply_to_message, GINT_TO_POINTER (mode), mail_thread_new);
}

/* Posting replies... */

static void
post_reply_to_message (CamelFolder *folder, const char *uid, CamelMimeMessage *message, void *user_data)
{
    /* FIXME: would be nice if this shared more code with reply_get_composer() */
    const char *message_id, *references;
    CamelInternetAddress *to = NULL;
    EDestination **tov = NULL;
    EMsgComposer *composer;
    char *subject, *url;
    EAccount *account;
    guint32 flags;
    
    if (message == NULL)
        return;
    
    account = guess_account (message, folder);
    flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_SEEN;
    
    get_reply_sender (message, &to, NULL);
    
    composer = e_msg_composer_new_with_type (E_MSG_COMPOSER_MAIL_POST);
    
    /* construct the tov/ccv */
    tov = em_utils_camel_address_to_destination (to);
    
    /* Set the subject of the new message. */
    if ((subject = (char *) camel_mime_message_get_subject (message))) {
        if (strncasecmp (subject, "Re: ", 4) != 0)
            subject = g_strdup_printf ("Re: %s", subject);
        else
            subject = g_strdup (subject);
    } else {
        subject = g_strdup ("");
    }
    
    e_msg_composer_set_headers (composer, account ? account->name : NULL, tov, NULL, NULL, subject);
    
    g_free (subject);
    
    url = mail_tools_folder_to_url (folder);
    e_msg_composer_hdrs_set_post_to ((EMsgComposerHdrs *) composer->hdrs, url);
    g_free (url);
    
    /* Add In-Reply-To and References. */
    message_id = camel_medium_get_header (CAMEL_MEDIUM (message), "Message-Id");
    references = camel_medium_get_header (CAMEL_MEDIUM (message), "References");
    if (message_id) {
        char *reply_refs;
        
        e_msg_composer_add_header (composer, "In-Reply-To", message_id);
        
        if (references)
            reply_refs = g_strdup_printf ("%s %s", references, message_id);
        else
            reply_refs = g_strdup (message_id);
        
        e_msg_composer_add_header (composer, "References", reply_refs);
        g_free (reply_refs);
    } else if (references) {
        e_msg_composer_add_header (composer, "References", references);
    }
    
    e_msg_composer_drop_editor_undo (composer);
    
    e_msg_composer_add_message_attachments (composer, message, TRUE);
    
    if (to != NULL)
        camel_object_unref (to);
    
    composer_set_body (composer, message);
    
    em_composer_utils_setup_callbacks (composer, folder, uid, flags, flags, NULL, NULL);
    
    gtk_widget_show (GTK_WIDGET (composer));    
    e_msg_composer_unset_changed (composer);
}

/**
 * em_utils_post_reply_to_message_by_uid:
 * @folder: folder containing message to reply to
 * @uid: message uid
 * @mode: reply mode
 *
 * Creates a new composer (post mode) ready to reply to the message
 * referenced by @folder and @uid.
 **/
void
em_utils_post_reply_to_message_by_uid (CamelFolder *folder, const char *uid)
{
    g_return_if_fail (CAMEL_IS_FOLDER (folder));
    g_return_if_fail (uid != NULL);
    
    mail_get_message (folder, uid, post_reply_to_message, NULL, mail_thread_new);
}