aboutsummaryrefslogblamecommitdiffstats
path: root/camel/providers/imap/camel-imap-command.c
blob: 45e5751b87059a7280666be72f33a51cc9a024f3 (plain) (tree)
1
2
3
4
5
6
7
8
9




                                                                           

                                        
  
                                     
  


                                                                   
  



                                                                    
  



                                                               


   
                    
                   
      
 
                   

                   
                  



                               
                             
                                     
                               
                                  
                                
                             
                                
 


                               



                                                                               

                                                                   

                                                                       

                                                               

   
                      



                                                                   
                                                                    
  



                                                                


                                                                   
  
                                                      
  
                                                                        


                                                                     
  







                                                                        
                   
                  
        
                                                 
        




                                                                   



                                                                                  
                                               
                                                         
                                                                     

                                                                     
        

                                                           
                                                           
                            
         
                     
        

















                                                                      
                               




                                                                        


                                                                          
                                                                  














                                                                       
        
                           
                                                           
                    
        
                                                 

                                                         
        
                
                                                           






                                                               

                         



                                                        
                

                                                                        
                                     






                                                                            
         
        
                              













                                                                                                     
















                                                                                     


   
                                   
                         
                                                    
                          
                        

                                                                     

                                                                      
   
                                                                       
  
                                                                     
                                 

                   
                                                                        
                                                                   
 
                                                    

                            

                                                                     






                                                                                     
                                                           
                            
         
        


                                              

















                                                                      
 
                                   
                      
        
                                                                  
                                                           
                                                 
         
        

                           
                                                         
                                                                        



                                                                                                                        

                                                 
                                       
                                                         
                              
                 


                                                    

                                                                  
                                                         
                                                                      







                                                                                                                                  
                








                                                        
        

                                                
                                                           
        








                                                              
        




                                                             
                                                 
        



                                                                                                                           
         
        



                                                                         
        
                                                
                                                                              

                            
        
                                   
        
                                                    
                            
                                
                                  
                                            
                                
        


                                                                     
                                                   


                                                                              

                                                                        
                                                                              

                            
        


                         
                                                         
                                                           
                                                          
                                                                      



                                                                   

                                                                      



                                                                          

                                       
                        

                              
        


                                
        
                                  
                    
        
                   



                                            
                

                                            
                              
                
                                                   
                                                                                        

                                        
                


                                                      
                                                                                 

                                           

                                                                                     
                            

                                                                                             
                                                                                      
                                                  

                                  
                                     


                                                                                      
                                                  

                                  
                                            
                







                                                                      
                   
                


                                            
                                                       










                                                             
                







                                                                       
                                                       
                

                                            
                
                                         
                                                                     
                                  
         
        


                                          



                                               



                                      
        




                                                     




                            

                                                  
  
                                                                
                                                                      

    
                                                                             
 


                                  
        

                       
        

                                                       
                


                                                                        
                                                         
                                                
                                                                 








                                                                              
        

                                                    
        


                                                

                                                                           


                                                              
                

                                                                     
        
                          
                                                   


   

                                                   


                                                                     
                                             

    

                                                                         
 


                       



                                                                     
                                                   


   
                               
                                           





                                                                    

                                                                   
                                 



                                                                         


                                                         



                                                
        

                            




                                                                        
                                                              
                
                                                   
                              
         
        

                                                    
                                                                 


                                                                              

                                                                               
         
        
                                                   




                                            
                                           




                                                                      
                                                                      
                                                         



                                                                         

                                                                      


                                                             
        
                                                           

                                          
                                                           

                              
        
                                                                      
                                                                               
                                                
                                                   

                    
 





                                                                    
                                    
                                
 
                                  
        






                                              
                






                                                                      





                                                       
                         
                         
                                                     




                                                                                                          
                                                 
                                                       
                                                    






                                                                                      

                                      


                                  






                                                                               
        
                                      
                                          




                                        
                                               

                              

                                                           
                 
                


                                                                 
                                                              
                              
                        

                                                  
                                                                 
                              
                         
                         
                                                  

                                                                         
                                
                                                                                        
                                                                                                           

                                                                                  

                                                                                 

                                                        
                         
                        

                                                
                              
                        

                                        
                 
                

                                       
        

                   





                                                                        
        


                                                              
        

                      
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* camel-imap-command.c: IMAP command sending/parsing routines */

/*
 *  Authors:
 *    Dan Winship <danw@ximian.com>
 *    Jeffrey Stedfast <fejj@ximian.com>
 *
 *  Copyright 2000, 2001 Ximian, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * 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 Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 */

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

#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include "camel-imap-command.h"
#include "camel-imap-utils.h"
#include "camel-imap-folder.h"
#include "camel-imap-store.h"
#include "camel-imap-store-summary.h"
#include "camel-imap-private.h"
#include <camel/camel-exception.h>
#include <camel/camel-private.h>
#include <camel/camel-utf8.h>
#include <camel/camel-session.h>


extern int camel_verbose_debug;

static gboolean imap_command_start (CamelImapStore *store, CamelFolder *folder,
                    const char *cmd, CamelException *ex);
CamelImapResponse *imap_read_response (CamelImapStore *store,
                       CamelException *ex);
static char *imap_read_untagged (CamelImapStore *store, char *line,
                 CamelException *ex);
static char *imap_command_strdup_vprintf (CamelImapStore *store,
                      const char *fmt, va_list ap);
static char *imap_command_strdup_printf (CamelImapStore *store,
                     const char *fmt, ...);

/**
 * camel_imap_command:
 * @store: the IMAP store
 * @folder: The folder to perform the operation in (or %NULL if not
 * relevant).
 * @ex: a CamelException
 * @fmt: a sort of printf-style format string, followed by arguments
 *
 * This function calls camel_imap_command_start() to send the
 * command, then reads the complete response to it using
 * camel_imap_command_response() and returns a CamelImapResponse
 * structure.
 *
 * As a special case, if @fmt is %NULL, it will just select @folder
 * and return the response from doing so.
 *
 * See camel_imap_command_start() for details on @fmt.
 *
 * On success, the store's connect_lock will be locked. It will be freed
 * when you call camel_imap_response_free. (The lock is recursive, so
 * callers can grab and release it themselves if they need to run
 * multiple commands atomically.)
 *
 * Return value: %NULL if an error occurred (in which case @ex will
 * be set). Otherwise, a CamelImapResponse describing the server's
 * response, which the caller must free with camel_imap_response_free().
 **/
CamelImapResponse *
camel_imap_command (CamelImapStore *store, CamelFolder *folder,
            CamelException *ex, const char *fmt, ...)
{
    va_list ap;
    char *cmd;
    
    CAMEL_SERVICE_LOCK (store, connect_lock);
    
    if (fmt) {
        va_start (ap, fmt);
        cmd = imap_command_strdup_vprintf (store, fmt, ap);
        va_end (ap);
    } else {
        if (store->current_folder) {
            camel_object_unref (CAMEL_OBJECT (store->current_folder));
            store->current_folder = NULL;
        }
        store->current_folder = folder;
        camel_object_ref (CAMEL_OBJECT (folder));
        cmd = imap_command_strdup_printf (store, "SELECT %F",
                          folder->full_name);
    }
    
    if (!imap_command_start (store, folder, cmd, ex)) {
        g_free (cmd);
        CAMEL_SERVICE_UNLOCK (store, connect_lock);
        return NULL;
    }
    g_free (cmd);
    
    return imap_read_response (store, ex);
}

/**
 * camel_imap_command_start:
 * @store: the IMAP store
 * @folder: The folder to perform the operation in (or %NULL if not
 * relevant).
 * @ex: a CamelException
 * @fmt: a sort of printf-style format string, followed by arguments
 *
 * This function makes sure that @folder (if non-%NULL) is the
 * currently-selected folder on @store and then sends the IMAP command
 * specified by @fmt and the following arguments.
 *
 * @fmt can include the following %-escapes ONLY:
 *  %s, %d, %%: as with printf
 *  %S: an IMAP "string" (quoted string or literal)
 *  %F: an IMAP folder name
 *
 * %S strings will be passed as literals if the server supports LITERAL+
 * and quoted strings otherwise. (%S does not support strings that
 * contain newlines.)
 *
 * %F will have the imap store's namespace prepended and then be processed
 * like %S.
 *
 * On success, the store's connect_lock will be locked. It will be
 * freed when %CAMEL_IMAP_RESPONSE_TAGGED or %CAMEL_IMAP_RESPONSE_ERROR
 * is returned from camel_imap_command_response(). (The lock is
 * recursive, so callers can grab and release it themselves if they
 * need to run multiple commands atomically.)
 *
 * Return value: %TRUE if the command was sent successfully, %FALSE if
 * an error occurred (in which case @ex will be set).
 **/
gboolean
camel_imap_command_start (CamelImapStore *store, CamelFolder *folder,
              CamelException *ex, const char *fmt, ...)
{
    va_list ap;
    char *cmd;
    gboolean ok;
    
    va_start (ap, fmt);
    cmd = imap_command_strdup_vprintf (store, fmt, ap);
    va_end (ap);
    
    CAMEL_SERVICE_LOCK (store, connect_lock);
    ok = imap_command_start (store, folder, cmd, ex);
    g_free (cmd);
    
    if (!ok)
        CAMEL_SERVICE_UNLOCK (store, connect_lock);
    return ok;
}

static gboolean
imap_command_start (CamelImapStore *store, CamelFolder *folder,
            const char *cmd, CamelException *ex)
{
    ssize_t nwritten;
    
    /* Check for current folder */
    if (folder && folder != store->current_folder) {
        CamelImapResponse *response;
        CamelException internal_ex;
        
        response = camel_imap_command (store, folder, ex, NULL);
        if (!response)
            return FALSE;
        camel_exception_init (&internal_ex);
        camel_imap_folder_selected (folder, response, &internal_ex);
        camel_imap_response_free (store, response);
        if (camel_exception_is_set (&internal_ex)) {
            camel_exception_xfer (ex, &internal_ex);
            return FALSE;
        }
    }
    
    /* Send the command */
    if (camel_verbose_debug) {
        const char *mask;
        
        if (!strncmp ("LOGIN \"", cmd, 7))
            mask = "LOGIN \"xxx\" xxx";
        else if (!strncmp ("LOGIN {", cmd, 7))
            mask = "LOGIN {N+}\r\nxxx {N+}\r\nxxx";
        else if (!strncmp ("LOGIN ", cmd, 6))
            mask = "LOGIN xxx xxx";
        else
            mask = cmd;
        
        fprintf (stderr, "sending : %c%.5d %s\r\n", store->tag_prefix, store->command, mask);
    }
    
    nwritten = camel_stream_printf (store->ostream, "%c%.5d %s\r\n",
                    store->tag_prefix, store->command++, cmd);
    
    if (nwritten == -1) {
        if (errno == EINTR)
            camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
                         _("Operation cancelled"));
        else
            camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
                         g_strerror (errno));
        
        camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
        return FALSE;
    }
    
    return TRUE;
}

/**
 * camel_imap_command_continuation:
 * @store: the IMAP store
 * @cmd: buffer containing the response/request data
 * @cmdlen: command length
 * @ex: a CamelException
 *
 * This method is for sending continuing responses to the IMAP server
 * after camel_imap_command() or camel_imap_command_response() returns
 * a continuation response.
 * 
 * This function assumes you have an exclusive lock on the imap stream.
 *
 * Return value: as for camel_imap_command(). On failure, the store's
 * connect_lock will be released.
 **/
CamelImapResponse *
camel_imap_command_continuation (CamelImapStore *store, const char *cmd,
                 size_t cmdlen, CamelException *ex)
{
    if (!camel_imap_store_connected (store, ex))
        return NULL;
    
    if (camel_stream_write (store->ostream, cmd, cmdlen) == -1 ||
        camel_stream_write (store->ostream, "\r\n", 2) == -1) {
        if (errno == EINTR)
            camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
                         _("Operation cancelled"));
        else
            camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
                         g_strerror (errno));
        camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
        CAMEL_SERVICE_UNLOCK (store, connect_lock);
        return NULL;
    }
    
    return imap_read_response (store, ex);
}

/**
 * camel_imap_command_response:
 * @store: the IMAP store
 * @response: a pointer to pass back the response data in
 * @ex: a CamelException
 *
 * This reads a single tagged, untagged, or continuation response from
 * @store into *@response. The caller must free the string when it is
 * done with it.
 *
 * Return value: One of %CAMEL_IMAP_RESPONSE_CONTINUATION,
 * %CAMEL_IMAP_RESPONSE_UNTAGGED, %CAMEL_IMAP_RESPONSE_TAGGED, or
 * %CAMEL_IMAP_RESPONSE_ERROR. If either of the last two, @store's
 * command lock will be unlocked.
 **/
CamelImapResponseType
camel_imap_command_response (CamelImapStore *store, char **response,
                 CamelException *ex)
{
    CamelImapResponseType type;
    char *respbuf;
    
    if (camel_imap_store_readline (store, &respbuf, ex) < 0) {
        CAMEL_SERVICE_UNLOCK (store, connect_lock);
        return CAMEL_IMAP_RESPONSE_ERROR;
    }
    
    switch (*respbuf) {
    case '*':
        if (!strncasecmp (respbuf, "* BYE", 5)) {
            /* Connection was lost, no more data to fetch */
            camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
            camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
                          _("Server unexpectedly disconnected: %s"),
                          _("Unknown error")); /* g_strerror (104));  FIXME after 1.0 is released */
            store->connected = FALSE;
            g_free (respbuf);
            respbuf = NULL;
            type = CAMEL_IMAP_RESPONSE_ERROR;
            break;
        }
        
        /* Read the rest of the response. */
        type = CAMEL_IMAP_RESPONSE_UNTAGGED;
        respbuf = imap_read_untagged (store, respbuf, ex);
        if (!respbuf)
            type = CAMEL_IMAP_RESPONSE_ERROR;
        else if (!strncasecmp (respbuf, "* OK [ALERT]", 12)) {
            char *msg;

            /* for imap ALERT codes, account user@host */
            msg = g_strdup_printf(_("Alert from IMAP server %s@%s:\n%s"),
                          ((CamelService *)store)->url->user, ((CamelService *)store)->url->host, respbuf+12);
            camel_session_alert_user(((CamelService *)store)->session, CAMEL_SESSION_ALERT_WARNING, msg, FALSE);
            g_free(msg);
        }
        
        break;
    case '+':
        type = CAMEL_IMAP_RESPONSE_CONTINUATION;
        break;
    default:
        type = CAMEL_IMAP_RESPONSE_TAGGED;
        break;
    }
    *response = respbuf;
    
    if (type == CAMEL_IMAP_RESPONSE_ERROR ||
        type == CAMEL_IMAP_RESPONSE_TAGGED)
        CAMEL_SERVICE_UNLOCK (store, connect_lock);
    
    return type;
}

CamelImapResponse *
imap_read_response (CamelImapStore *store, CamelException *ex)
{
    CamelImapResponse *response;
    CamelImapResponseType type;
    char *respbuf, *p;
    
    /* Get another lock so that when we reach the tagged
     * response and camel_imap_command_response unlocks,
     * we're still locked. This lock is owned by response
     * and gets unlocked when response is freed.
     */
    CAMEL_SERVICE_LOCK (store, connect_lock);
    
    response = g_new0 (CamelImapResponse, 1);
    if (store->current_folder && camel_disco_store_status (CAMEL_DISCO_STORE (store)) != CAMEL_DISCO_STORE_RESYNCING) {
        response->folder = store->current_folder;
        camel_object_ref (CAMEL_OBJECT (response->folder));
    }
    
    response->untagged = g_ptr_array_new ();
    while ((type = camel_imap_command_response (store, &respbuf, ex))
           == CAMEL_IMAP_RESPONSE_UNTAGGED)
        g_ptr_array_add (response->untagged, respbuf);
    
    if (type == CAMEL_IMAP_RESPONSE_ERROR) {
        camel_imap_response_free_without_processing (store, response);
        return NULL;
    }
    
    response->status = respbuf;
    
    /* Check for OK or continuation response. */
    if (*respbuf == '+')
        return response;
    p = strchr (respbuf, ' ');
    if (p && !strncasecmp (p, " OK", 3))
        return response;
    
    /* We should never get BAD, or anything else but +, OK, or NO
     * for that matter.
     */
    if (!p || strncasecmp (p, " NO", 3) != 0) {
        g_warning ("Unexpected response from IMAP server: %s",
               respbuf);
        camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
                      _("Unexpected response from IMAP "
                    "server: %s"), respbuf);
        camel_imap_response_free_without_processing (store, response);
        return NULL;
    }
    
    p += 3;
    if (!*p++)
        p = NULL;
    camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                  _("IMAP command failed: %s"),
                  p ? p : _("Unknown error"));
    camel_imap_response_free_without_processing (store, response);
    return NULL;
}

/* Given a line that is the start of an untagged response, read and
 * return the complete response, which may include an arbitrary number
 * of literals.
 */
static char *
imap_read_untagged (CamelImapStore *store, char *line, CamelException *ex)
{
    int fulllen, ldigits, nread, i;
    unsigned int length;
    GPtrArray *data;
    GString *str;
    char *end, *p, *s, *d;
    
    p = strrchr (line, '{');
    if (!p)
        return line;
    
    data = g_ptr_array_new ();
    fulllen = 0;
    
    while (1) {
        str = g_string_new (line);
        g_free (line);
        fulllen += str->len;
        g_ptr_array_add (data, str);
        
        p = strrchr (str->str, '{');
        if (!p)
            break;
        
        length = strtoul (p + 1, &end, 10);
        if (*end != '}' || *(end + 1) || end == p + 1 || length >= UINT_MAX - 2)
            break;
        ldigits = end - (p + 1);
        
        /* Read the literal */
        str = g_string_sized_new (length + 2);
        str->str[0] = '\n';
        nread = camel_stream_read (store->istream, str->str + 1, length);
        if (nread == -1) {
            if (errno == EINTR)
                camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
                             _("Operation cancelled"));
            else
                camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
                             g_strerror (errno));
            camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
            g_string_free (str, TRUE);
            goto lose;
        }
        if (nread < length) {
            camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
                         _("Server response ended too soon."));
            camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
            g_string_free (str, TRUE);
            goto lose;
        }
        str->str[length + 1] = '\0';
        
        /* Fix up the literal, turning CRLFs into LF. Also, if
         * we find any embedded NULs, strip them. This is
         * dubious, but:
         *   - The IMAP grammar says you can't have NULs here
         *     anyway, so this will not affect our behavior
         *     against any completely correct server.
         *   - WU-imapd 12.264 (at least) will cheerily pass
         *     NULs along if they are embedded in the message
         */
        
        s = d = str->str + 1;
        end = str->str + 1 + length;
        while (s < end) {
            while (s < end && *s == '\0') {
                s++;
                length--;
            }
            if (*s == '\r' && *(s + 1) == '\n') {
                s++;
                length--;
            }
            *d++ = *s++;
        }
        *d = '\0';
        str->len = length + 1;
        
        /* p points to the "{" in the line that starts the
         * literal. The length of the CR-less response must be
         * less than or equal to the length of the response
         * with CRs, therefore overwriting the old value with
         * the new value cannot cause an overrun. However, we
         * don't want it to be shorter either, because then the
         * GString's length would be off...
         */
        sprintf (p, "{%0*d}", ldigits, length);
        
        fulllen += str->len;
        g_ptr_array_add (data, str);
        
        /* Read the next line. */
        if (camel_imap_store_readline (store, &line, ex) < 0)
            goto lose;
    }
    
    /* Now reassemble the data. */
    p = line = g_malloc (fulllen + 1);
    for (i = 0; i < data->len; i++) {
        str = data->pdata[i];
        memcpy (p, str->str, str->len);
        p += str->len;
        g_string_free (str, TRUE);
    }
    *p = '\0';
    g_ptr_array_free (data, TRUE);
    return line;
    
 lose:
    for (i = 0; i < data->len; i++)
        g_string_free (data->pdata[i], TRUE);
    g_ptr_array_free (data, TRUE);
    return NULL;
}


/**
 * camel_imap_response_free:
 * @store: the CamelImapStore the response is from
 * @response: a CamelImapResponse
 *
 * Frees all of the data in @response and processes any untagged
 * EXPUNGE and EXISTS responses in it. Releases @store's connect_lock.
 **/
void
camel_imap_response_free (CamelImapStore *store, CamelImapResponse *response)
{
    int i, number, exists = 0;
    GArray *expunged = NULL;
    char *resp, *p;
    
    if (!response)
        return;
    
    for (i = 0; i < response->untagged->len; i++) {
        resp = response->untagged->pdata[i];
        
        if (response->folder) {
            /* Check if it's something we need to handle. */
            number = strtoul (resp + 2, &p, 10);
            if (!strcasecmp (p, " EXISTS")) {
                exists = number;
            } else if (!strcasecmp (p, " EXPUNGE")) {
                if (!expunged) {
                    expunged = g_array_new (FALSE, FALSE,
                                sizeof (int));
                }
                g_array_append_val (expunged, number);
            }
        }
        g_free (resp);
    }
    
    g_ptr_array_free (response->untagged, TRUE);
    g_free (response->status);
    
    if (response->folder) {
        if (exists > 0 || expunged) {
            /* Update the summary */
            camel_imap_folder_changed (response->folder,
                           exists, expunged, NULL);
            if (expunged)
                g_array_free (expunged, TRUE);
        }
        
        camel_object_unref (CAMEL_OBJECT (response->folder));
    }
    
    g_free (response);
    CAMEL_SERVICE_UNLOCK (store, connect_lock);
}

/**
 * camel_imap_response_free_without_processing:
 * @store: the CamelImapStore the response is from.
 * @response: a CamelImapResponse:
 *
 * Frees all of the data in @response without processing any untagged
 * responses. Releases @store's command lock.
 **/
void
camel_imap_response_free_without_processing (CamelImapStore *store,
                         CamelImapResponse *response)
{
    if (!response)
        return;
    
    if (response->folder) {
        camel_object_unref (CAMEL_OBJECT (response->folder));
        response->folder = NULL;
    }
    camel_imap_response_free (store, response);
}

/**
 * camel_imap_response_extract:
 * @store: the store the response came from
 * @response: the response data returned from camel_imap_command
 * @type: the response type to extract
 * @ex: a CamelException
 *
 * This checks that @response contains a single untagged response of
 * type @type and returns just that response data. If @response
 * doesn't contain the right information, the function will set @ex
 * and return %NULL. Either way, @response will be freed and the
 * store's connect_lock released.
 *
 * Return value: the desired response string, which the caller must free.
 **/
char *
camel_imap_response_extract (CamelImapStore *store,
                 CamelImapResponse *response,
                 const char *type,
                 CamelException *ex)
{
    int len = strlen (type), i;
    char *resp;
    
    len = strlen (type);
    
    for (i = 0; i < response->untagged->len; i++) {
        resp = response->untagged->pdata[i];
        /* Skip "* ", and initial sequence number, if present */
        strtoul (resp + 2, &resp, 10);
        if (*resp == ' ')
            resp = (char *) imap_next_word (resp);
        
        if (!strncasecmp (resp, type, len))
            break;
    }
    
    if (i < response->untagged->len) {
        resp = response->untagged->pdata[i];
        g_ptr_array_remove_index (response->untagged, i);
    } else {
        resp = NULL;
        camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
                      _("IMAP server response did not contain "
                    "%s information"), type);
    }
    
    camel_imap_response_free (store, response);
    return resp;
}

/**
 * camel_imap_response_extract_continuation:
 * @store: the store the response came from
 * @response: the response data returned from camel_imap_command
 * @ex: a CamelException
 *
 * This checks that @response contains a continuation response, and
 * returns just that data. If @response doesn't contain a continuation
 * response, the function will set @ex, release @store's connect_lock,
 * and return %NULL. Either way, @response will be freed.
 *
 * Return value: the desired response string, which the caller must free.
 **/
char *
camel_imap_response_extract_continuation (CamelImapStore *store,
                      CamelImapResponse *response,
                      CamelException *ex)
{
    char *status;
    
    if (response->status && *response->status == '+') {
        status = response->status;
        response->status = NULL;
        camel_imap_response_free (store, response);
        return status;
    }
    
    camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
                  _("Unexpected OK response from IMAP server: %s"),
                  response->status);
    camel_imap_response_free (store, response);
    return NULL;
}

static char *
imap_command_strdup_vprintf (CamelImapStore *store, const char *fmt,
                 va_list ap)
{
    GPtrArray *args;
    const char *p, *start;
    char *out, *outptr, *string;
    int num, len, i, arglen;

    args = g_ptr_array_new ();
    
    /* Determine the length of the data */
    len = strlen (fmt);
    p = start = fmt;
    while (*p) {
        p = strchr (start, '%');
        if (!p)
            break;
        
        switch (*++p) {
        case 'd':
            num = va_arg (ap, int);
            g_ptr_array_add (args, GINT_TO_POINTER (num));
            start = p + 1;
            len += 10;
            break;
        case 's':
            string = va_arg (ap, char *);
            g_ptr_array_add (args, string);
            start = p + 1;
            len += strlen (string);
            break;
        case 'S':
        case 'F':
            string = va_arg (ap, char *);
            if (*p == 'F') {
                /* NB: this is freed during output */
                char *s = camel_imap_store_summary_full_from_path(store->summary, string);
                string = s?s:camel_utf8_utf7(string);
            }
            arglen = strlen (string);
            g_ptr_array_add (args, string);
            if (imap_is_atom (string)) {
                len += arglen;
            } else {
                if (store->capabilities & IMAP_CAPABILITY_LITERALPLUS)
                    len += arglen + 15;
                else
                    len += arglen * 2;
            }
            start = p + 1;
            break;
        case '%':
            start = p;
            break;
        default:
            g_warning ("camel-imap-command is not printf. I don't "
                   "know what '%%%c' means.", *p);
            start = *p ? p + 1 : p;
            break;
        }
    }
    
    /* Now write out the string */
    outptr = out = g_malloc (len + 1);
    p = start = fmt;
    i = 0;
    while (*p) {
        p = strchr (start, '%');
        if (!p) {
            strcpy (outptr, start);
            break;
        } else {
            strncpy (outptr, start, p - start);
            outptr += p - start;
        }
        
        switch (*++p) {
        case 'd':
            num = GPOINTER_TO_INT (args->pdata[i++]);
            outptr += sprintf (outptr, "%d", num);
            break;
            
        case 's':
            string = args->pdata[i++];
            outptr += sprintf (outptr, "%s", string);
            break;
        case 'S':
        case 'F':
            string = args->pdata[i++];
            if (imap_is_atom (string)) {
                outptr += sprintf (outptr, "%s", string);
            } else {
                if (store->capabilities & IMAP_CAPABILITY_LITERALPLUS) {
                    outptr += sprintf (outptr, "{%d+}\r\n%s", strlen (string), string);
                } else {
                    char *quoted = imap_quote_string (string);
                    
                    outptr += sprintf (outptr, "%s", quoted);
                    g_free (quoted);
                }
            }
            
            if (*p == 'F')
                g_free (string);
            break;
        default:
            *outptr++ = '%';
            *outptr++ = *p;
        }
        
        start = *p ? p + 1 : p;
    }
    
    return out;
}

static char *
imap_command_strdup_printf (CamelImapStore *store, const char *fmt, ...)
{
    va_list ap;
    char *result;
    
    va_start (ap, fmt);
    result = imap_command_strdup_vprintf (store, fmt, ap);
    va_end (ap);
    
    return result;
}