aboutsummaryrefslogblamecommitdiffstats
path: root/mail/upgrade-mailer.c
blob: a9c122b9d325642d87fb14f30074c072027c1b0e (plain) (tree)



































                                                                           
                   




                                               



                                 
 
                                   
                              




                        
                           


  

           
                                        
 

                                            
        

                                                      
        
                                           

                            
                   

                                              
        

                            
        








                                                       
        
                                             


           
                               
 
              


                               




                                                       


                    



















                                                               
                                





































                                                                                       



































































































































































                                                                                        

                                                  






















































































                                                                                                                         
































































                                                                   




                                                            
                     


















                                                                   
                                       

                                                                          
                                                                             
                             
                                                 
                           


                                                                                                            






                                                                         
                                                            
                                 
                                
                                                      
                         


                                                                           
                                           
                                                                              



                                                                                                                      
                                                
                                           













                                                                                                                          
                
                                  

                                
                           

                           

                                     




                                                          
        
                      

                                                
                                          


















                                                               







                                             
        
                                                      

                                                                   
                        




















































































                                                                                                                
                                                 
                            
                                   
















































































                                                                                                            
                                                         
                                    
                                           
























                                                                                                                    
                                                                                                         






                     
                                                                                                        












                                                                                                               
                                                
                              




                                                                                

                                                                
                                 
                
                            

                          
                                                                      



                                                           

                                                                

                                     
                                    








                                                                                                 
                                                                  
                       




                                 







                                                                                                                
                                         





                                                                                          



                                                                    
                             


                          



















                                                                                                              
                        








                                                                                   
                 
         
        
                       
                                                                             
                                 

                             
         



                                                                                                   
                                 



                             



                                                                                                                
                                       



                             
                                                                                                         
        
                         


                     

 
 



                                            
                         












                                                                                                   



                             












                                                                                     
                                           



                                                                                                              


                                                                                         
                                














                                                                                           
                                
                                            


                                      

                                                                                   
















                                                                                             

                                                                                       





















                                                                               
                                                                          
                                            
     







                                                                                                          
      



























                                                                                                  
                             





                                                        


                                             
                         
        
                     



















                                                                           
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 *  Authors: Jeffrey Stedfast <fejj@ximian.com>
 *
 *  Copyright 2002 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 <glib.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <ctype.h>

#include <bonobo.h>
#include <bonobo-conf/bonobo-config-database.h>
#include <gal/util/e-xml-utils.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <libxml/tree.h>

#include <camel/camel-file-utils.h>
#include <camel/camel-store.h>

struct _storeinfo {
    char *base_url;
    char *namespace;
    char dir_sep;
    GPtrArray *folders;
};



static char
find_dir_sep (const char *lsub_response)
{
    register const unsigned char *inptr;
    const unsigned char *inend;
    
    inptr = (const unsigned char *) lsub_response;
    inend = inptr + strlen (inptr);
    
    if (strncmp (inptr, "* LSUB (", 8))
        return '\0';
    
    inptr += 8;
    while (inptr < inend && *inptr != ')')
        inptr++;
    
    if (inptr >= inend)
        return '\0';
    
    inptr++;
    while (inptr < inend && isspace ((int) *inptr))
        inptr++;
    
    if (inptr >= inend)
        return '\0';
    
    if (*inptr == '\"')
        inptr++;
    
    return inptr < inend ? *inptr : '\0';
}

static void
si_free (struct _storeinfo *si)
{
    int i;
    
    g_free (si->base_url);
    g_free (si->namespace);
    if (si->folders) {
        for (i = 0; i < si->folders->len; i++)
            g_free (si->folders->pdata[i]);
        g_ptr_array_free (si->folders, TRUE);
    }
    g_free (si);
}

static unsigned char tohex[16] = {
    '0', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};

static char *
hex_encode (const char *in, size_t len)
{
    const unsigned char *inend = in + len;
    unsigned char *inptr, *outptr;
    char *outbuf;
    
    outptr = outbuf = g_malloc ((len * 3) + 1);
    
    inptr = (unsigned char *) in;
    while (inptr < inend) {
        if (*inptr > 127 || isspace ((int) *inptr)) {
            *outptr++ = '%';
            *outptr++ = tohex[(*inptr >> 4) & 0xf];
            *outptr++ = tohex[*inptr & 0xf];
            inptr++;
        } else
            *outptr++ = *inptr++;
    }
    
    *outptr = '\0';
    
    return outbuf;
}

#define HEXVAL(c) (isdigit (c) ? (c) - '0' : tolower (c) - 'a' + 10)

static char *
hex_decode (const char *in, size_t len)
{
    const unsigned char *inend = in + len;
    unsigned char *inptr, *outptr;
    char *outbuf;
    
    outptr = outbuf = g_malloc (len + 1);
    
    inptr = (unsigned char *) in;
    while (inptr < inend) {
        if (*inptr == '%') {
            if (isxdigit ((int) inptr[1]) && isxdigit ((int) inptr[2])) {
                *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]);
                inptr += 3;
            } else
                *outptr++ = *inptr++;
        } else
            *outptr++ = *inptr++;
    }
    
    *outptr = '\0';
    
    return outbuf;
}

static char *
parse_lsub (const char *lsub, char *dir_sep)
{
    const unsigned char *inptr = (const unsigned char *) lsub;
    const unsigned char *inend;
    int inlen, quoted = 0;
    
    inend = inptr + strlen (inptr);
    if (strncmp (inptr, "* LSUB (", 8))
        return NULL;
    
    inptr += 8;
    while (inptr < inend && *inptr != ')')
        inptr++;
    
    if (inptr >= inend)
        return NULL;
    
    inptr++;
    while (inptr < inend && isspace ((int) *inptr))
        inptr++;
    
    if (inptr >= inend)
        return NULL;
    
    /* skip over the dir sep */
    if (*inptr == '\"')
        inptr++;
    
    *dir_sep = (char) *inptr++;
    if (*inptr == '\"')
        inptr++;
    
    if (inptr >= inend)
        return NULL;
    
    while (inptr < inend && isspace ((int) *inptr))
        inptr++;
    
    if (inptr >= inend)
        return NULL;
    
    if (*inptr == '\"') {
        inptr++;
        quoted = 1;
    } else
        quoted = 0;
    
    inlen = strlen (inptr) - quoted;
    
    return g_strndup (inptr, inlen);
}

static void
cache_upgrade (struct _storeinfo *si, const char *folder_name)
{
    const char *old_folder_name = folder_name;
    char *oldpath, *newpath, *p;
    struct dirent *dent;
    DIR *dir = NULL;
    
    if (si->namespace && strcmp ("INBOX", folder_name)) {
        if (!strncmp (old_folder_name, si->namespace, strlen (si->namespace))) {
            old_folder_name += strlen (si->namespace);
            if (*old_folder_name == si->dir_sep)
                old_folder_name++;
        }
    }
    
    oldpath = g_strdup_printf ("%s/evolution/mail/imap/%s/%s", getenv ("HOME"),
                   si->base_url + 7, old_folder_name);
    
    newpath = g_strdup_printf ("%s/evolution/mail/imap/%s/folders/%s",
                   getenv ("HOME"), si->base_url + 7, folder_name);
    
    if (!strcmp (folder_name, "folders"))
        goto special_case_folders;
    
    if (si->dir_sep != '/') {
        p = newpath + strlen (newpath) - strlen (folder_name) - 1;
        while (*p) {
            if (*p == si->dir_sep)
                *p = '/';
            p++;
        }
    }
    
    /* make sure all parent directories exist */
    if ((p = strrchr (newpath, '/'))) {
        *p = '\0';
        camel_mkdir_hier (newpath, 0755);
        *p = '/';
    }
    
    if (rename (oldpath, newpath) == -1) {
        fprintf (stderr, "Failed to upgrade cache for imap folder %s/%s: %s\n",
             si->base_url, folder_name, g_strerror (errno));
    }
    
    g_free (oldpath);
    g_free (newpath);
    
    return;
    
 special_case_folders:
    
    /* the user had a toplevel folder named "folders" */
    if (camel_mkdir_hier (newpath, 0755) == -1) {
        /* we don't bother to check EEXIST because well, if
                   folders/folders exists then we're pretty much
                   fucked */
        goto exception;
    }
    
    if (!(dir = opendir (oldpath)))
        goto exception;
    
    while ((dent = readdir (dir))) {
        char *old_path, *new_path;
        
        if (!strcmp (dent->d_name, ".") || !strcmp (dent->d_name, ".."))
            continue;
        
        old_path = g_strdup_printf ("%s/%s", oldpath, dent->d_name);
        new_path = g_strdup_printf ("%s/%s", newpath, dent->d_name);
        
        /* make sure all parent directories exist */
        if ((p = strrchr (new_path, '/'))) {
            *p = '\0';
            camel_mkdir_hier (new_path, 0755);
            *p = '/';
        }
        
        if (rename (old_path, new_path) == -1) {
            g_free (old_path);
            g_free (new_path);
            goto exception;
        }
        
        g_free (old_path);
        g_free (new_path);
    }
    
    closedir (dir);
    
    g_free (oldpath);
    g_free (newpath);
    
    return;
    
 exception:
    
    fprintf (stderr, "Failed to upgrade cache for imap folder %s/%s: %s\n",
         si->base_url, folder_name, g_strerror (errno));
    
    if (dir)
        closedir (dir);
    
    g_free (oldpath);
    g_free (newpath);
}

static int
foldercmp (const void *f1, const void *f2)
{
    const char **folder1 = (const char **) f1;
    const char **folder2 = (const char **) f2;
    
    return strcmp (*folder1, *folder2);
}

static void
cache_upgrade_and_free (gpointer key, gpointer val, gpointer user_data)
{
    struct _storeinfo *si = val;
    GPtrArray *folders;
    char *path = NULL;
    char dir_sep;
    int i;
    
    if (si->folders) {
        path = g_strdup_printf ("%s/evolution/mail/imap/%s/folders",
                    getenv ("HOME"), si->base_url + 7);
        
        if (mkdir (path, 0755) == -1 && errno != EEXIST) {
            fprintf (stderr, "Failed to create directory %s: %s", path, g_strerror (errno));
            goto exception;
        }
        
        g_free (path);
        folders = g_ptr_array_new ();
        for (i = 0; i < si->folders->len; i++) {
            if ((path = parse_lsub (si->folders->pdata[i], &dir_sep))) {
                g_ptr_array_add (folders, path);
            }
        }
        
        /* sort the folders so that parents get created before
                   their children */
        qsort (folders->pdata, folders->len, sizeof (void *), foldercmp);
        
        for (i = 0; i < folders->len; i++) {
            cache_upgrade (si, folders->pdata[i]);
            g_free (folders->pdata[i]);
        }
    }
    
    si_free (si);
    
    return;
    
 exception:
    
    fprintf (stderr, "Could not upgrade imap cache for %s: %s\n",
         si->base_url + 7, g_strerror (errno));
    
    g_free (path);
    
    si_free (si);
}

static char *
get_base_url (const char *protocol, const char *uri)
{
    unsigned char *base_url, *p;
    
    p = (unsigned char *) uri + strlen (protocol) + 1;
    if (!strncmp (p, "//", 2))
        p += 2;
    
    base_url = p;
    p = strchr (p, '/');
    base_url = g_strdup_printf ("%s://%.*s", protocol, p ? (int) (p - base_url) : (int) strlen (base_url), base_url);
    
    return base_url;
}

static char *
imap_namespace (const char *uri)
{
    unsigned char *name, *p;
    
    if ((name = strstr (uri, ";namespace=\"")) == NULL)
        return NULL;
    
    name += strlen (";namespace=\"");
    p = name;
    while (*p && *p != '\"')
        p++;
    
    return g_strndup (name, p - name);
}

static char *
find_folder (GPtrArray *folders, const char *folder, char *dir_sep)
{
    const unsigned char *inptr, *inend;
    int inlen, len, diff, i;
    int quoted;
    
    len = strlen (folder);
    
    for (i = 0; i < folders->len; i++) {
        inptr = folders->pdata[i];
        inend = inptr + strlen (inptr);
        if (strncmp (inptr, "* LSUB (", 8))
            continue;
        
        inptr += 8;
        while (inptr < inend && *inptr != ')')
            inptr++;
        
        if (inptr >= inend)
            continue;
        
        inptr++;
        while (inptr < inend && isspace ((int) *inptr))
            inptr++;
        
        if (inptr >= inend)
            continue;
        
        /* skip over the dir sep */
        if (*inptr == '\"')
            inptr++;
        
        *dir_sep = *inptr++;
        if (*inptr == '\"')
            inptr++;
        
        if (inptr >= inend)
            continue;
        
        while (inptr < inend && isspace ((int) *inptr))
            inptr++;
        
        if (inptr >= inend)
            continue;
        
        if (*inptr == '\"') {
            inptr++;
            quoted = 1;
        } else
            quoted = 0;
        
        inlen = strlen (inptr) - quoted;
        if (len > inlen)
            continue;
        
        diff = inlen - len;
        if (!strncmp (inptr + diff, folder, len))
            return hex_encode (inptr, inlen);
    }
    
    *dir_sep = '\0';
    
    return NULL;
}

static char *
imap_url_upgrade (GHashTable *imap_sources, const char *uri)
{
    struct _storeinfo *si;
    unsigned char *base_url, *folder, *p, *new = NULL;
    char dir_sep;
    
    base_url = get_base_url ("imap", uri);
    
    fprintf (stderr, "checking for %s... ", base_url);
    if (!(si = g_hash_table_lookup (imap_sources, base_url))) {
        fprintf (stderr, "not found.\n");
        g_warning ("Unknown imap account: %s", base_url);
        g_free (base_url);
        return NULL;
    }
    
    fprintf (stderr, "found.\n");
    p = (unsigned char *) uri + strlen (base_url) + 1;
    if (!strcmp (p, "INBOX")) {
        new = g_strdup_printf ("%s/INBOX", base_url);
        g_free (base_url);
        return new;
    }
    
    p = hex_decode (p, strlen (p));
    
    fprintf (stderr, "checking for folder %s on %s... ", p, base_url);
    folder = si->folders ? find_folder (si->folders, p, &dir_sep) : NULL;
    if (folder == NULL) {
        fprintf (stderr, "not found.\n");
        folder = p;
        if (si->namespace) {
            if (!si->dir_sep) {
                fprintf (stderr, "checking for directory separator in namespace param... ");
                if (*si->namespace == '/') {
                    dir_sep = '/';
                } else {
                    p = si->namespace;
                    while (*p && !ispunct ((int) *p))
                        p++;
                    
                    dir_sep = (char) *p;
                }
            } else {
                dir_sep = si->dir_sep;
            }
            
            if (dir_sep) {
                fprintf (stderr, "found: '%c'\n", dir_sep);
                p = folder;
                folder = hex_encode (folder, strlen (folder));
                if (si->namespace[strlen (si->namespace) - 1] == dir_sep)
                    new = g_strdup_printf ("%s/%s%s", base_url, si->namespace, folder);
                else
                    new = g_strdup_printf ("%s/%s%c%s", base_url, si->namespace, dir_sep, folder);
                g_free (folder);
                folder = p;
                
                p = new + strlen (base_url) + 1;
                while (*p) {
                    if (*p == dir_sep)
                        *p = '/';
                    p++;
                }
            } else {
                fprintf (stderr, "not found.");
                g_warning ("Cannot update settings for imap folder %s: unknown directory separator", uri);
            }
        } else {
            g_warning ("Cannot update settings for imap folder %s: unknown namespace", uri);
        }
        
        g_free (base_url);
        g_free (folder);
        
        return new;
    } else
        g_free (p);
    
    fprintf (stderr, "found.\n");
    new = g_strdup_printf ("%s/%s", base_url, folder);
    g_free (folder);
    
    if (!si->dir_sep)
        si->dir_sep = dir_sep;
    
    if (dir_sep) {
        p = new + strlen (base_url) + 1;
        while (*p) {
            if (*p == dir_sep)
                *p = '/';
            p++;
        }
    }
    
    g_free (base_url);
    
    return new;
}

static char *
exchange_url_upgrade (const char *uri)
{
    unsigned char *base_url, *folder;
    char *url;
    
    base_url = get_base_url ("exchange", uri);
    folder = (unsigned char *) uri + strlen (base_url) + 1;
    
    if (strncmp (folder, "exchange/", 9))
        return g_strdup (uri);
    
    folder += 9;
    while (*folder && *folder != '/')
        folder++;
    if (*folder == '/')
        folder++;
    
    folder = hex_decode (folder, strlen (folder));
    url = g_strdup_printf ("%s/personal/%s", base_url, folder);
    g_free (base_url);
    g_free (folder);
    
    return url;
}

static int
mailer_upgrade_account_info (Bonobo_ConfigDatabase db, const char *key, int num, GHashTable *imap_sources)
{
    char *path, *uri, *new;
    int i;
    
    for (i = 0;  i < num; i++) {
        path = g_strdup_printf ("/Mail/Accounts/account_%s_folder_uri_%d", key, i);
        uri = bonobo_config_get_string (db, path, NULL);
        if (uri) {
            if (!strncmp (uri, "imap:", 5)) {
                new = imap_url_upgrade (imap_sources, uri);
                if (new) {
                    bonobo_config_set_string (db, path, new, NULL);
                    g_free (new);
                }
            } else if (!strncmp (uri, "exchange:", 9)) {
                new = exchange_url_upgrade (uri);
                bonobo_config_set_string (db, path, new, NULL);
                g_free (new);
            }
        }
        
        g_free (uri);
        g_free (path);
    }
    
    return 0;
}

static int
mailer_upgrade_xml_file (GHashTable *imap_sources, const char *filename)
{
    unsigned char *buffer, *inptr, *start, *uri, *new;
    ssize_t nread = 0, nwritten, n;
    gboolean url_need_upgrade;
    struct stat st;
    size_t len;
    char *bak;
    int fd;
    
    bak = g_strdup_printf ("%s.bak-1.0", filename);
    if (stat (bak, &st) != -1) {
        /* seems we have already converted this file? */
        fprintf (stderr, "\n%s already exists, assuming %s has already been upgraded\n", bak, filename);
        g_free (bak);
        return 0;
    }
    
    if (stat (filename, &st) == -1 || (fd = open (filename, O_RDONLY)) == -1) {
        /* file doesn't exist? I guess nothing to upgrade here */
        fprintf (stderr, "\nCould not open %s: %s\n", filename, strerror (errno));
        g_free (bak);
        return 0;
    }
    
    start = buffer = g_malloc (st.st_size + 1);
    do {
        do {
            n = read (fd, buffer + nread, st.st_size - nread);
        } while (n == -1 && errno == EINTR);
        
        if (n > 0)
            nread += n;
    } while (n != -1 && nread < st.st_size);
    buffer[nread] = '\0';
    
    if (nread < st.st_size) {
        /* failed to load the entire file? */
        fprintf (stderr, "\nFailed to load %s: %s\n", filename, strerror (errno));
        g_free (buffer);
        g_free (bak);
        close (fd);
        return -1;
    }
    
    close (fd);
    
    inptr = buffer;
    url_need_upgrade = FALSE;
    do {
        inptr = strstr (inptr, "uri=\"");
        if (inptr) {
            inptr += 5;
            url_need_upgrade = !strncmp (inptr, "imap:", 5) || !strncmp (inptr, "exchange:", 9);
        }
    } while (inptr && !url_need_upgrade);
    
    if (inptr == NULL) {
        /* no imap urls in this xml file, so no need to "upgrade" it */
        fprintf (stdout, "\nNo updates required for %s\n", filename);
        g_free (buffer);
        g_free (bak);
        return 0;
    }
    
    if (rename (filename, bak) == -1) {
        /* failed to backup xml file */
        fprintf (stderr, "\nFailed to create backup file %s: %s\n", bak, strerror (errno));
        g_free (buffer);
        g_free (bak);
        return -1;
    }
    
    if ((fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC, 0644)) == -1) {
        /* failed to create new xml file */
        fprintf (stderr, "\nFailed to create new %s: %s\n", filename, strerror (errno));
        rename (bak, filename);
        g_free (buffer);
        g_free (bak);
        return -1;
    }
    
    while (inptr != NULL) {
        len = inptr - start;
        nwritten = 0;
        do {
            do {
                n = write (fd, start + nwritten, len - nwritten);
            } while (n == -1 && errno == EINTR);
            
            if (n > 0)
                nwritten += n;
        } while (n != -1 && nwritten < len);
        
        if (nwritten < len)
            goto exception;
        
        start = inptr;
        while (*start && *start != '"')
            start++;
        
        uri = g_strndup (inptr, start - inptr);
        if (!strncmp (uri, "imap:", 5)) {
            if ((new = imap_url_upgrade (imap_sources, uri)) == NULL) {
                new = uri;
                uri = NULL;
            }
        } else if (!strncmp (uri, "exchange:", 9)) {
            new = exchange_url_upgrade (uri);
        } else {
            new = uri;
            uri = NULL;
        }
        g_free (uri);
        
        nwritten = 0;
        len = strlen (new);
        do {
            do {
                n = write (fd, new + nwritten, len - nwritten);
            } while (n == -1 && errno == EINTR);
            
            if (n > 0)
                nwritten += n;
        } while (n != -1 && nwritten < len);
        
        g_free (new);
        
        if (nwritten < len)
            goto exception;
        
        inptr = start;
        url_need_upgrade = FALSE;
        do {
            inptr = strstr (inptr, "uri=\"");
            if (inptr) {
                inptr += 5;
                url_need_upgrade = !strncmp (inptr, "imap:", 5) || !strncmp (inptr, "exchange:", 9);
            }
        } while (inptr && !url_need_upgrade);
    }
    
    nwritten = 0;
    len = strlen (start);
    do {
        do {
            n = write (fd, start + nwritten, len - nwritten);
        } while (n == -1 && errno == EINTR);
        
        if (n > 0)
            nwritten += n;
    } while (n != -1 && nwritten < len);
    
    if (nwritten < len)
        goto exception;
    
    if (fsync (fd) == -1)
        goto exception;
    
    close (fd);
    g_free (buffer);
    
    fprintf (stdout, "\nSuccessfully upgraded %s\nPrevious settings saved in %s\n\n", filename, bak);
    
    g_free (bak);
    
    return 0;
    
 exception:
    
    fprintf (stderr, "\nFailed to save updated settings to %s: %s\n\n", filename, strerror (errno));
    
    close (fd);
    g_free (buffer);
    unlink (filename);
    rename (bak, filename);
    g_free (bak);
    
    return -1;
}

static char *
shortcuts_upgrade_uri (GHashTable *accounts, GHashTable *imap_sources, const char *account, const char *folder)
{
    char *url, *name, *decoded, *new = NULL;
    struct _storeinfo *si;
    int type;
    
    type = GPOINTER_TO_INT ((si = g_hash_table_lookup (accounts, account)));
    if (type == 1) {
        /* exchange */
        decoded = hex_decode (folder, strlen (folder));
        name = g_strdup_printf ("personal/%s", decoded);
        g_free (decoded);
        
        return name;
    } else {
        /* imap */
        url = g_strdup_printf ("%s/%s", si->base_url, folder);
        new = imap_url_upgrade (imap_sources, url);
        g_free (url);
        
        if (new) {
            name = new + strlen (si->base_url) + 1;
            name = hex_decode (name, strlen (name));
            g_free (new);
            
            return name;
        }
    }
    
    return NULL;
}

static int
shortcuts_upgrade_xml_file (GHashTable *accounts, GHashTable *imap_sources, const char *filename)
{
    char *bak, *uri, *account, *folder, *new, *new_uri, *type;
    struct stat st;
    xmlDoc *doc;
    xmlNode *group, *item;
    int account_len;
    gboolean changed = FALSE;

    bak = g_strdup_printf ("%s.bak-1.0", filename);
    if (stat (bak, &st) != -1) {
        /* seems we have already converted this file? */
        fprintf (stderr, "\n%s already exists, assuming %s has already been upgraded\n", bak, filename);
        g_free (bak);
        return 0;
    }
    
    if (stat (filename, &st) == -1) {
        /* file doesn't exist? I guess nothing to upgrade here */
        fprintf (stderr, "\nCould not open %s: %s\n", filename, strerror (errno));
        g_free (bak);
        return 0;
    }
    
    doc = xmlParseFile (filename);
    if (!doc || !doc->xmlRootNode) {
        /* failed to load/parse the file? */
        fprintf (stderr, "\nFailed to load %s\n", filename);
        g_free (bak);
        return -1;
    }
    
    for (group = doc->xmlRootNode->xmlChildrenNode; group; group = group->next) {
        for (item = group->xmlChildrenNode; item; item = item->next) {
            /* Fix IMAP/Exchange URIs */
            uri = xmlNodeGetContent (item);
            if (!strncmp (uri, "evolution:/", 11)) {
                account_len = strcspn (uri + 11, "/");
                account = g_strndup (uri + 11, account_len);
                if (g_hash_table_lookup (accounts, account)) {
                    folder = uri + 11 + account_len;
                    if (*folder)
                        folder++;
                    new = shortcuts_upgrade_uri (accounts, imap_sources, account, folder);
                    new_uri = g_strdup_printf ("evolution:/%s/%s", account, new);
                    xmlNodeSetContent (item, new_uri);
                    changed = TRUE;
                    g_free (new_uri);
                }
                g_free (account);
            }
            xmlFree (uri);
            
            /* Fix LDAP shortcuts */
            type = xmlGetProp (item, "type");
            if (type) {
                if (!strcmp (type, "ldap-contacts")) {
                    xmlSetProp (item, "type", "contacts/ldap");
                    changed = TRUE;
                }
                xmlFree (type);
            }
        }
    }
    
    if (!changed) {
        fprintf (stdout, "\nNo updates required for %s\n", filename);
        xmlFreeDoc (doc);
        g_free (bak);
        return 0;
    }
    
    if (rename (filename, bak) == -1) {
        /* failed to backup xml file */
        fprintf (stderr, "\nFailed to create backup file %s: %s\n", bak, strerror (errno));
        xmlFreeDoc (doc);
        g_free (bak);
        return -1;
    }
    
    if (e_xml_save_file (filename, doc) == -1) {
        fprintf (stderr, "\nFailed to save updated settings to %s: %s\n\n", filename, strerror (errno));
        xmlFreeDoc (doc);
        unlink (filename);
        rename (bak, filename);
        g_free (bak);
        return -1;
    }
    
    fprintf (stdout, "\nSuccessfully upgraded %s\nPrevious settings saved in %s\n\n", filename, bak);
    
    xmlFreeDoc (doc);
    g_free (bak);
    
    return 0;
}


static int
mailer_upgrade (Bonobo_ConfigDatabase db)
{
    GHashTable *imap_sources, *accounts;
    char *path, *uri;
    char *account;
    int num, i;
    
    if ((num = bonobo_config_get_long_with_default (db, "/Mail/Accounts/num", 0, NULL)) == 0) {
        /* nothing to upgrade */
        return 0;
    }
    
    accounts = g_hash_table_new (g_str_hash, g_str_equal);
    imap_sources = g_hash_table_new (g_str_hash, g_str_equal);
    for (i = 0; i < num; i++) {
        struct _storeinfo *si;
        struct stat st;
        char *string;
        guint32 tmp;
        FILE *fp;
        int j;
        
        path = g_strdup_printf ("/Mail/Accounts/source_url_%d", i);
        uri = bonobo_config_get_string (db, path, NULL);
        g_free (path);
        if (uri && !strncmp (uri, "imap:", 5)) {
            path = g_strdup_printf ("/Mail/Accounts/account_name_%d", i);
            account = bonobo_config_get_string (db, path, NULL);
            g_free (path);
            
            si = g_new (struct _storeinfo, 1);
            si->base_url = get_base_url ("imap", uri);
            si->namespace = imap_namespace (uri);
            si->dir_sep = '\0';
            si->folders = NULL;
            
            path = si->base_url + 7;
            
            path = g_strdup_printf ("%s/evolution/mail/imap/%s/storeinfo", getenv ("HOME"), path);
            if (stat (path, &st) != -1 && (fp = fopen (path, "r")) != NULL) {
                camel_file_util_decode_uint32 (fp, &tmp);
                camel_file_util_decode_uint32 (fp, &tmp);
                
                j = 0;
                si->folders = g_ptr_array_new ();
                while (camel_file_util_decode_string (fp, &string) != -1) {
                    if (j++ > 0) {
                        g_ptr_array_add (si->folders, string);
                    } else {
                        if (!si->namespace)
                            si->namespace = string;
                        else
                            g_free (string);
                        
                        camel_file_util_decode_uint32 (fp, &tmp);
                        si->dir_sep = (char) tmp & 0xff;
                    }
                }
                
                fclose (fp);
            }
            g_free (path);
            
            if (si->folders && si->folders->len > 0)
                si->dir_sep = find_dir_sep (si->folders->pdata[0]);
            
            g_hash_table_insert (imap_sources, si->base_url, si);
            
            if (account)
                g_hash_table_insert (accounts, account, si);
        } else if (uri && !strncmp (uri, "exchange:", 9)) {
            path = g_strdup_printf ("/Mail/Accounts/account_name_%d", i);
            account = bonobo_config_get_string (db, path, NULL);
            g_free (path);
            
            if (account)
                g_hash_table_insert (accounts, account, GINT_TO_POINTER (1));
        }
        
        g_free (uri);
    }
    
    if (g_hash_table_size (accounts) == 0) {
        /* user doesn't have any imap/exchange accounts - nothing to upgrade */
        g_hash_table_destroy (imap_sources);
        return 0;
    }
    
    /* upgrade user's account info (bug #29135) */
    mailer_upgrade_account_info (db, "drafts", num, imap_sources);
    mailer_upgrade_account_info (db, "sent", num, imap_sources);
    
    /* upgrade user's filters/vfolders (bug #24451) */
    path = g_strdup_printf ("%s/evolution/filters.xml", getenv ("HOME"));
    mailer_upgrade_xml_file (imap_sources, path);
    g_free (path);
    
    path = g_strdup_printf ("%s/evolution/vfolders.xml", getenv ("HOME"));
    mailer_upgrade_xml_file (imap_sources, path);
    g_free (path);
    
    /* upgrade user's shortcuts (there's no bug # for this one) */
    path = g_strdup_printf ("%s/evolution/shortcuts.xml", getenv ("HOME"));
    shortcuts_upgrade_xml_file (accounts, imap_sources, path);
    g_free (path);
    
    g_hash_table_foreach (imap_sources, cache_upgrade_and_free, NULL);
    g_hash_table_destroy (imap_sources);
#if 0
    path = g_strdup_printf ("%s/evolution/mail/imap", getenv ("HOME"));
    bak = g_strdup_printf ("%s.bak-1.0", path);
    
    if (rename (path, bak) == -1)
        fprintf (stderr, "\nFailed to backup Evolution 1.0's IMAP cache: %s\n", strerror (errno));
    
    g_free (path);
    g_free (bak);
#endif
    
    return 0;
}

static Bonobo_ConfigDatabase
get_config_db (void)
{
    Bonobo_ConfigDatabase db;
    CORBA_Environment ev;
    
    CORBA_exception_init (&ev);
    
    db = bonobo_get_object ("wombat:", "Bonobo/ConfigDatabase", &ev);
    if (BONOBO_EX (&ev) || db == CORBA_OBJECT_NIL) {
        fprintf (stderr, "get_config_db(): Could not get the config database object '%s'",
             bonobo_exception_get_text (&ev));
        db = CORBA_OBJECT_NIL;
    }
    
    CORBA_exception_free (&ev);
    
    return db;
}

static int
upgrade (void)
{
    Bonobo_ConfigDatabase db;
    CORBA_Environment ev;
    
    if ((db = get_config_db ()) == CORBA_OBJECT_NIL)
        g_error ("Could not get config db");
    
    mailer_upgrade (db);
    
    CORBA_exception_init (&ev);
    Bonobo_ConfigDatabase_sync (db, &ev);
    
    gtk_main_quit ();
    
    return FALSE;
}

int main (int argc, char **argv)
{
    CORBA_ORB orb;
    
    gnome_init ("evolution-upgrade", "1.0", argc, argv);
    
    if ((orb = oaf_init (argc, argv)) == NULL)
        g_error ("Cannot init oaf");
    
    if (bonobo_init (orb, CORBA_OBJECT_NIL, CORBA_OBJECT_NIL) == FALSE)
        g_error ("Cannot init bonobo");
    
    gtk_idle_add ((GtkFunction) upgrade, NULL);
    
    bonobo_main ();
    
    return 0;
}