aboutsummaryrefslogblamecommitdiffstats
path: root/camel/providers/imap4/camel-imap4-command.c
blob: e869bee9c37f15123046390336a0295e3d177247 (plain) (tree)






























                                                                           
                             
 



                                 
 
                                





              


                             


          
                                       





                                            
                                                            
                            
                                                            






                      
     
               
                                           







                              
                                            





                               
      

             
                                                       




                                   
                                                        








                                                                                                                 
                                        


                                                                               
                                         














                                                                                      
                              

                       
                                    
        
                                                








                             
                                                                                                                      
 

                                    
        

                                                 


                                                
                                  



                                                               

                                                                        



                                                                                                     

                                                                   

















                                                                                  

                                                                                                                     
 

                                                   











                                             

                                                   
                                               


                                       
                                  
























                                                                             



                                                                                         


                                             

                                                            
                                                                       
                                                                  
                                                                                    

                                                                       
                                                                                   







                                                                        
                                                                           












                                                                              

















                                                                                                                    


                                                                                               
                                                                                         






                                                               
                                                                              
















                                                                 
                                           


                                                                  
                                                







                                            


                                                





                                  

                                                                                                     
 
                                   


                                
                                                                          





                       
                                                                                                                    




                                                                      
                                               




                        
                                                 
 
                                           












                                                           
                                                      

                                                             
                                                               












                                                                           
                                                                

                                                                               
                                                                

                                                                                           
                                                                 

















                                                                                            
                                                                               





                                              
                                                          













                                                                                                                 
                                        



                                                                          
                                         
























                                                                                
                                             

                               
                                       

                                                    
                                     

                                                  
                                   

                                        
                                    

                                                      
                                       

                                                             
                                       








                                                                              
                                                
 



                                              


                               



                                    
                                                                                       
                                                                      
                                                             


















                                                           
                                                                    










                                                                    



                                                                                        
                               
         
        



                                                                                        
                               
         
        
                                                                       

            
                                                                                  





                                                                            
                                                                                            


                                               
                                                                                                   






















                                                                                              
                                                                                                 
                                               
                                                                                                      
                                                      
                                                                               
                        
                                                                                          

                                               
                                                                    
                                                                 
                                                                       
                                                                      
                                                                       
                                                                       
                                                                        
                                
                                                                        
                                                                                                             


                                                        
                                                                                                  



                                                                     

                                                                                                       


                                                                                 
                                                                                                        


                                                               
         


                                                                                 
      





                                                
         


                                                          
      



                                                            
                                                                                            


                                                                              
                                                                                                








                                                                          
                                                       

                                                 
                                                                  








                                            
                                               





                  
                                                 



                                                 
                                                                      

                                                 

                                                





                                        
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*  Camel
 *  Copyright (C) 1999-2004 Jeffrey Stedfast
 *
 *  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 <stdio.h>
#include <string.h>
#include <errno.h>

#include <camel/camel-stream-null.h>
#include <camel/camel-stream-filter.h>
#include <camel/camel-mime-filter-crlf.h>
#include <camel/camel-i18n.h>

#include "camel-imap4-stream.h"
#include "camel-imap4-engine.h"
#include "camel-imap4-folder.h"
#include "camel-imap4-specials.h"

#include "camel-imap4-command.h"


#define d(x) x


enum {
    IMAP4_STRING_ATOM,
    IMAP4_STRING_QSTRING,
    IMAP4_STRING_LITERAL,
};

static int
imap4_string_get_type (const char *str)
{
    int type = 0;
    
    while (*str) {
        if (!is_atom (*str)) {
            if (is_qsafe (*str))
                type = IMAP4_STRING_QSTRING;
            else
                return IMAP4_STRING_LITERAL;
        }
        str++;
    }
    
    return type;
}

#if 0
static gboolean
imap4_string_is_atom_safe (const char *str)
{
    while (is_atom (*str))
        str++;
    
    return *str == '\0';
}

static gboolean
imap4_string_is_quote_safe (const char *str)
{
    while (is_qsafe (*str))
        str++;
    
    return *str == '\0';
}
#endif

static size_t
camel_imap4_literal_length (CamelIMAP4Literal *literal)
{
    CamelStream *stream, *null;
    CamelMimeFilter *crlf;
    size_t len;
    
    if (literal->type == CAMEL_IMAP4_LITERAL_STRING)
        return strlen (literal->literal.string);
    
    null = camel_stream_null_new ();
    crlf = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_ONLY);
    stream = (CamelStream *) camel_stream_filter_new_with_stream (null);
    camel_stream_filter_add ((CamelStreamFilter *) stream, crlf);
    camel_object_unref (crlf);
    
    switch (literal->type) {
    case CAMEL_IMAP4_LITERAL_STREAM:
        camel_stream_write_to_stream (literal->literal.stream, stream);
        camel_stream_reset (literal->literal.stream);
        break;
    case CAMEL_IMAP4_LITERAL_WRAPPER:
        camel_data_wrapper_write_to_stream (literal->literal.wrapper, stream);
        break;
    default:
        g_assert_not_reached ();
        break;
    }
    
    len = ((CamelStreamNull *) null)->written;
    
    camel_object_unref (stream);
    camel_object_unref (null);
    
    return len;
}

static CamelIMAP4CommandPart *
command_part_new (void)
{
    CamelIMAP4CommandPart *part;
    
    part = g_new (CamelIMAP4CommandPart, 1);
    part->next = NULL;
    part->buffer = NULL;
    part->buflen = 0;
    part->literal = NULL;
    
    return part;
}

static void
imap4_command_append_string (CamelIMAP4Engine *engine, CamelIMAP4CommandPart **tail, GString *str, const char *string)
{
    CamelIMAP4CommandPart *part;
    CamelIMAP4Literal *literal;
    
    switch (imap4_string_get_type (string)) {
    case IMAP4_STRING_ATOM:
        /* string is safe as it is... */
        g_string_append (str, string);
        break;
    case IMAP4_STRING_QSTRING:
        /* we need to quote the string */
        /* FIXME: need to escape stuff */
        g_string_append_printf (str, "\"%s\"", string);
        break;
    case IMAP4_STRING_LITERAL:
        if (engine->capa & CAMEL_IMAP4_CAPABILITY_LITERALPLUS) {
            /* we have to send a literal, but the server supports LITERAL+ so use that */
            g_string_append_printf (str, "{%u+}\r\n%s", strlen (string), string);
        } else {
            /* we have to make it a literal */
            literal = g_new (CamelIMAP4Literal, 1);
            literal->type = CAMEL_IMAP4_LITERAL_STRING;
            literal->literal.string = g_strdup (string);
            
            g_string_append_printf (str, "{%u}\r\n", strlen (string));
            
            (*tail)->buffer = g_strdup (str->str);
            (*tail)->buflen = str->len;
            (*tail)->literal = literal;
            
            part = command_part_new ();
            (*tail)->next = part;
            (*tail) = part;
            
            g_string_truncate (str, 0);
        }
        break;
    }
}

CamelIMAP4Command *
camel_imap4_command_newv (CamelIMAP4Engine *engine, CamelIMAP4Folder *imap4_folder, const char *format, va_list args)
{
    CamelIMAP4CommandPart *parts, *part, *tail;
    CamelIMAP4Command *ic;
    const char *start;
    GString *str;
    
    tail = parts = command_part_new ();
    
    str = g_string_new ("");
    start = format;
    
    while (*format) {
        register char ch = *format++;
        
        if (ch == '%') {
            CamelIMAP4Literal *literal;
            CamelIMAP4Folder *folder;
            char *function, **strv;
            unsigned int u;
            char *string;
            size_t len;
            void *obj;
            int c, d;
            
            g_string_append_len (str, start, format - start - 1);
            
            switch (*format) {
            case '%':
                /* literal % */
                g_string_append_c (str, '%');
                break;
            case 'c':
                /* character */
                c = va_arg (args, int);
                g_string_append_c (str, c);
                break;
            case 'd':
                /* integer */
                d = va_arg (args, int);
                g_string_append_printf (str, "%d", d);
                break;
            case 'u':
                /* unsigned integer */
                u = va_arg (args, unsigned int);
                g_string_append_printf (str, "%u", u);
                break;
            case 'F':
                /* CamelIMAP4Folder */
                folder = va_arg (args, CamelIMAP4Folder *);
                string = (char *) camel_imap4_folder_utf7_name (folder);
                imap4_command_append_string (engine, &tail, str, string);
                break;
            case 'L':
                /* Literal */
                obj = va_arg (args, void *);
                
                literal = g_new (CamelIMAP4Literal, 1);
                if (CAMEL_IS_DATA_WRAPPER (obj)) {
                    literal->type = CAMEL_IMAP4_LITERAL_WRAPPER;
                    literal->literal.wrapper = obj;
                } else if (CAMEL_IS_STREAM (obj)) {
                    literal->type = CAMEL_IMAP4_LITERAL_STREAM;
                    literal->literal.stream = obj;
                } else {
                    g_assert_not_reached ();
                }
                
                camel_object_ref (obj);
                
                /* FIXME: take advantage of LITERAL+? */
                len = camel_imap4_literal_length (literal);
                g_string_append_printf (str, "{%u}\r\n", len);
                
                tail->buffer = g_strdup (str->str);
                tail->buflen = str->len;
                tail->literal = literal;
                
                part = command_part_new ();
                tail->next = part;
                tail = part;
                
                g_string_truncate (str, 0);
                
                break;
            case 'V':
                /* a string vector of arguments which may need to be quoted or made into literals */
                function = str->str + str->len - 2;
                while (*function != ' ')
                    function--;
                function++;
                
                function = g_strdup (function);
                
                strv = va_arg (args, char **);
                for (d = 0; strv[d]; d++) {
                    if (d > 0)
                        g_string_append (str, function);
                    imap4_command_append_string (engine, &tail, str, strv[d]);
                }
                
                g_free (function);
                break;
            case 'S':
                /* string which may need to be quoted or made into a literal */
                string = va_arg (args, char *);
                imap4_command_append_string (engine, &tail, str, string);
                break;
            case 's':
                /* safe atom string */
                string = va_arg (args, char *);
                g_string_append (str, string);
                break;
            default:
                g_warning ("unknown formatter %%%c", *format);
                g_string_append_c (str, '%');
                g_string_append_c (str, *format);
                break;
            }
            
            format++;
            
            start = format;
        }
    }
    
    g_string_append (str, start);
    tail->buffer = str->str;
    tail->buflen = str->len;
    tail->literal = NULL;
    g_string_free (str, FALSE);
    
    ic = g_new0 (CamelIMAP4Command, 1);
    ((EDListNode *) ic)->next = NULL;
    ((EDListNode *) ic)->prev = NULL;
    ic->untagged = g_hash_table_new (g_str_hash, g_str_equal);
    ic->status = CAMEL_IMAP4_COMMAND_QUEUED;
    ic->resp_codes = g_ptr_array_new ();
    ic->engine = engine;
    ic->ref_count = 1;
    ic->parts = parts;
    ic->part = parts;
    
    camel_exception_init (&ic->ex);
    
    if (imap4_folder) {
        camel_object_ref (imap4_folder);
        ic->folder = imap4_folder;
    } else
        ic->folder = NULL;
    
    return ic;
}

CamelIMAP4Command *
camel_imap4_command_new (CamelIMAP4Engine *engine, CamelIMAP4Folder *folder, const char *format, ...)
{
    CamelIMAP4Command *command;
    va_list args;
    
    va_start (args, format);
    command = camel_imap4_command_newv (engine, folder, format, args);
    va_end (args);
    
    return command;
}

void
camel_imap4_command_register_untagged (CamelIMAP4Command *ic, const char *atom, CamelIMAP4UntaggedCallback untagged)
{
    g_hash_table_insert (ic->untagged, g_strdup (atom), untagged);
}

void
camel_imap4_command_ref (CamelIMAP4Command *ic)
{
    ic->ref_count++;
}

void
camel_imap4_command_unref (CamelIMAP4Command *ic)
{
    CamelIMAP4CommandPart *part, *next;
    int i;
    
    if (ic == NULL)
        return;
    
    ic->ref_count--;
    if (ic->ref_count == 0) {
        if (ic->folder)
            camel_object_unref (ic->folder);
        
        g_free (ic->tag);
        
        for (i = 0; i < ic->resp_codes->len; i++) {
            CamelIMAP4RespCode *resp_code;
            
            resp_code = ic->resp_codes->pdata[i];
            camel_imap4_resp_code_free (resp_code);
        }
        g_ptr_array_free (ic->resp_codes, TRUE);
        
        g_hash_table_foreach (ic->untagged, (GHFunc) g_free, NULL);
        g_hash_table_destroy (ic->untagged);
        
        camel_exception_clear (&ic->ex);
        
        part = ic->parts;
        while (part != NULL) {
            g_free (part->buffer);
            if (part->literal) {
                switch (part->literal->type) {
                case CAMEL_IMAP4_LITERAL_STRING:
                    g_free (part->literal->literal.string);
                    break;
                case CAMEL_IMAP4_LITERAL_STREAM:
                    camel_object_unref (part->literal->literal.stream);
                    break;
                case CAMEL_IMAP4_LITERAL_WRAPPER:
                    camel_object_unref (part->literal->literal.wrapper);
                    break;
                }
                
                g_free (part->literal);
            }
            
            next = part->next;
            g_free (part);
            part = next;
        }
        
        g_free (ic);
    }
}


static int
imap4_literal_write_to_stream (CamelIMAP4Literal *literal, CamelStream *stream)
{
    CamelStream *istream, *ostream = NULL;
    CamelDataWrapper *wrapper;
    CamelMimeFilter *crlf;
    char *string;
    
    if (literal->type == CAMEL_IMAP4_LITERAL_STRING) {
        string = literal->literal.string;
        if (camel_stream_write (stream, string, strlen (string)) == -1)
            return -1;
        
        return 0;
    }
    
    crlf = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_ONLY);
    ostream = (CamelStream *) camel_stream_filter_new_with_stream (stream);
    camel_stream_filter_add ((CamelStreamFilter *) ostream, crlf);
    camel_object_unref (crlf);
    
    /* write the literal */
    switch (literal->type) {
    case CAMEL_IMAP4_LITERAL_STREAM:
        istream = literal->literal.stream;
        if (camel_stream_write_to_stream (istream, ostream) == -1)
            goto exception;
        break;
    case CAMEL_IMAP4_LITERAL_WRAPPER:
        wrapper = literal->literal.wrapper;
        if (camel_data_wrapper_write_to_stream (wrapper, ostream) == -1)
            goto exception;
        break;
    }
    
    camel_object_unref (ostream);
    ostream = NULL;
    
#if 0
    if (camel_stream_write (stream, "\r\n", 2) == -1)
        return -1;
#endif
    
    return 0;
    
 exception:
    
    camel_object_unref (ostream);
    
    return -1;
}


static void
unexpected_token (camel_imap4_token_t *token)
{
    switch (token->token) {
    case CAMEL_IMAP4_TOKEN_NO_DATA:
        fprintf (stderr, "*** NO DATA ***");
        break;
    case CAMEL_IMAP4_TOKEN_ERROR:
        fprintf (stderr, "*** ERROR ***");
        break;
    case CAMEL_IMAP4_TOKEN_NIL:
        fprintf (stderr, "NIL");
        break;
    case CAMEL_IMAP4_TOKEN_ATOM:
            fprintf (stderr, "%s", token->v.atom);
        break;
    case CAMEL_IMAP4_TOKEN_QSTRING:
            fprintf (stderr, "\"%s\"", token->v.qstring);
        break;
    case CAMEL_IMAP4_TOKEN_LITERAL:
        fprintf (stderr, "{%u}", token->v.literal);
        break;
    default:
        fprintf (stderr, "%c", (unsigned char) (token->token & 0xff));
        break;
    }
}

int
camel_imap4_command_step (CamelIMAP4Command *ic)
{
    CamelIMAP4Engine *engine = ic->engine;
    int result = CAMEL_IMAP4_RESULT_NONE;
    CamelIMAP4Literal *literal;
    camel_imap4_token_t token;
    unsigned char *linebuf;
    ssize_t nwritten;
    size_t len;
    
    g_assert (ic->part != NULL);
    
    if (ic->part == ic->parts) {
        ic->tag = g_strdup_printf ("%c%.5u", engine->tagprefix, engine->tag++);
        camel_stream_printf (engine->ostream, "%s ", ic->tag);
        d(fprintf (stderr, "sending: %s ", ic->tag));
    }
    
#if d(!)0
    {
        int sending = ic->part != ic->parts;
        unsigned char *eoln, *eob;
        
        linebuf = ic->part->buffer;
        eob = linebuf + ic->part->buflen;
        
        do {
            eoln = linebuf;
            while (eoln < eob && *eoln != '\n')
                eoln++;
            
            if (eoln < eob)
                eoln++;
            
            if (sending)
                fwrite ("sending: ", 1, 10, stderr);
            fwrite (linebuf, 1, eoln - linebuf, stderr);
            
            linebuf = eoln + 1;
            sending = 1;
        } while (linebuf < eob);
    }
#endif
    
    linebuf = ic->part->buffer;
    len = ic->part->buflen;
    
    if ((nwritten = camel_stream_write (engine->ostream, linebuf, len)) == -1) {
        camel_exception_setv (&ic->ex, CAMEL_EXCEPTION_SYSTEM,
                      _("Failed sending command to IMAP server %s: %s"),
                      engine->url->host, g_strerror (errno));
        goto exception;
    }
    
    if (camel_stream_flush (engine->ostream) == -1) {
        camel_exception_setv (&ic->ex, CAMEL_EXCEPTION_SYSTEM,
                      _("Failed sending command to IMAP server %s: %s"),
                      engine->url->host, g_strerror (errno));
        goto exception;
    }
    
    /* now we need to read the response(s) from the IMAP4 server */
    
    do {
        if (camel_imap4_engine_next_token (engine, &token, &ic->ex) == -1)
            goto exception;
        
        if (token.token == '+') {
            /* we got a continuation response from the server */
            literal = ic->part->literal;
            
            if (camel_imap4_engine_line (engine, &linebuf, &len, &ic->ex) == -1)
                goto exception;
            
            if (literal) {
                if (imap4_literal_write_to_stream (literal, engine->ostream) == -1)
                    goto exception;
                
                g_free (linebuf);
                linebuf = NULL;
                
                break;
            } else if (ic->plus) {
                /* command expected a '+' response - probably AUTHENTICATE? */
                if (ic->plus (engine, ic, linebuf, len, &ic->ex) == -1) {
                    g_free (linebuf);
                    return -1;
                }
                
                /* now we need to wait for a "<tag> OK/NO/BAD" response */
            } else {
                /* FIXME: error?? */
                g_assert_not_reached ();
            }
            
            g_free (linebuf);
            linebuf = NULL;
        } else if (token.token == '*') {
            /* we got an untagged response, let the engine handle this */
            if (camel_imap4_engine_handle_untagged_1 (engine, &token, &ic->ex) == -1)
                goto exception;
        } else if (token.token == CAMEL_IMAP4_TOKEN_ATOM && !strcmp (token.v.atom, ic->tag)) {
            /* we got "<tag> OK/NO/BAD" */
            d(fprintf (stderr, "got %s response\n", token.v.atom));
            
            if (camel_imap4_engine_next_token (engine, &token, &ic->ex) == -1)
                goto exception;
            
            if (token.token == CAMEL_IMAP4_TOKEN_ATOM) {
                if (!strcmp (token.v.atom, "OK"))
                    result = CAMEL_IMAP4_RESULT_OK;
                else if (!strcmp (token.v.atom, "NO"))
                    result = CAMEL_IMAP4_RESULT_NO;
                else if (!strcmp (token.v.atom, "BAD"))
                    result = CAMEL_IMAP4_RESULT_BAD;
                
                if (result == CAMEL_IMAP4_RESULT_NONE) {
                    d(fprintf (stderr, "expected OK/NO/BAD but got %s\n", token.v.atom));
                    goto unexpected;
                }
                
                if (camel_imap4_engine_next_token (engine, &token, &ic->ex) == -1)
                    goto exception;
                
                if (token.token == '[') {
                    /* we have a response code */
                    camel_imap4_stream_unget_token (engine->istream, &token);
                    if (camel_imap4_engine_parse_resp_code (engine, &ic->ex) == -1)
                        goto exception;
                } else if (token.token != '\n') {
                    /* just gobble up the rest of the line */
                    if (camel_imap4_engine_line (engine, NULL, NULL, &ic->ex) == -1)
                        goto exception;
                }
            } else {
#if d(!)0
                fprintf (stderr, "expected anything but this: ");
                unexpected_token (&token);
                fprintf (stderr, "\n");
#endif
                
                goto unexpected;
            }
            
            break;
        } else {
#if d(!)0
            fprintf (stderr, "wtf is this: ");
            unexpected_token (&token);
            fprintf (stderr, "\n");
#endif
            
        unexpected:
            
            /* no fucking clue what we got... */
            if (camel_imap4_engine_line (engine, &linebuf, &len, &ic->ex) == -1)
                goto exception;
            
            camel_exception_setv (&ic->ex, CAMEL_EXCEPTION_SYSTEM,
                          _("Unexpected response from IMAP4 server %s: %s"),
                          engine->url->host, linebuf);
            
            g_free (linebuf);
            
            goto exception;
        }
    } while (1);
    
    /* status should always be ACTIVE here... */
    if (ic->status == CAMEL_IMAP4_COMMAND_ACTIVE) {
        ic->part = ic->part->next;
        if (ic->part == NULL || result) {
            ic->status = CAMEL_IMAP4_COMMAND_COMPLETE;
            ic->result = result;
            return 1;
        }
    }
    
    return 0;
    
 exception:
    
    ic->status = CAMEL_IMAP4_COMMAND_ERROR;
    
    return -1;
}


void
camel_imap4_command_reset (CamelIMAP4Command *ic)
{
    int i;
    
    for (i = 0; i < ic->resp_codes->len; i++)
        camel_imap4_resp_code_free (ic->resp_codes->pdata[i]);
    g_ptr_array_set_size (ic->resp_codes, 0);
    
    ic->status = CAMEL_IMAP4_COMMAND_QUEUED;
    ic->result = CAMEL_IMAP4_RESULT_NONE;
    ic->part = ic->parts;
    g_free (ic->tag);
    ic->tag = NULL;
    
    camel_exception_clear (&ic->ex);
}