#include #include #include #include #include #include #include #include "camel-imapp-folder.h" #include "camel-imapp-stream.h" #include "camel-imapp-utils.h" #include "camel-imapp-exception.h" #include "camel-imapp-engine.h" #include "e-util/e-memory.h" /* high-level parser state */ #define p(x) /* debug */ #define d(x) /* ANSI-C code produced by gperf version 2.7 */ /* Command-line: gperf -H imap_hash -N imap_tokenise -L ANSI-C -o -t -k1,$ imap-tokens.txt */ struct _imap_keyword { char *name; enum _imap_id id; }; /* gperf input file best hash generated using: gperf -o -s-2 -k1,'$' -t -H imap_hash -N imap_tokenise -L ANSI-C */ #define TOTAL_KEYWORDS 23 #define MIN_WORD_LENGTH 2 #define MAX_WORD_LENGTH 14 #define MIN_HASH_VALUE 2 #define MAX_HASH_VALUE 38 /* maximum key range = 37, duplicates = 0 */ #ifdef __GNUC__ __inline #endif static unsigned int imap_hash (register const char *str, register unsigned int len) { static unsigned char asso_values[] = { 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 10, 15, 39, 20, 0, 0, 39, 0, 10, 39, 0, 39, 39, 10, 0, 0, 39, 0, 10, 5, 10, 39, 39, 39, 0, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39 }; return len + asso_values[(unsigned char)str[len - 1]] + asso_values[(unsigned char)str[0]]; } #ifdef __GNUC__ __inline #endif enum _imap_id imap_tokenise (register const char *str, register unsigned int len) { static struct _imap_keyword wordlist[] = { {""}, {""}, {"OK", IMAP_OK}, {""}, {""}, {"PARSE", IMAP_PARSE}, {""}, {"PREAUTH", IMAP_PREAUTH}, {"ENVELOPE", IMAP_ENVELOPE}, {"READ-ONLY", IMAP_READ_ONLY}, {"READ-WRITE", IMAP_READ_WRITE}, {"RFC822.SIZE", IMAP_RFC822_SIZE}, {"NO", IMAP_NO}, {"RFC822.HEADER", IMAP_RFC822_HEADER}, {"TRYCREATE", IMAP_TRYCREATE}, {"FLAGS", IMAP_FLAGS}, {"RFC822.TEXT", IMAP_RFC822_TEXT}, {"NEWNAME", IMAP_NEWNAME}, {"BYE", IMAP_BYE}, {"BODY", IMAP_BODY}, {"ALERT", IMAP_ALERT}, {"UIDVALIDITY", IMAP_UIDVALIDITY}, {"INTERNALDATE", IMAP_INTERNALDATE}, {""}, {"PERMANENTFLAGS", IMAP_PERMANENTFLAGS}, {""}, {"UNSEEN", IMAP_UNSEEN}, {""}, {"BODYSTRUCTURE", IMAP_BODYSTRUCTURE}, {""}, {""}, {""}, {""}, {"UID", IMAP_UID}, {""}, {""}, {""}, {""}, {"BAD", IMAP_BAD} }; if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) { register int key = imap_hash (str, len); if (key <= MAX_HASH_VALUE && key >= 0) { register const char *s = wordlist[key].name; if (*str == *s && !strcmp (str + 1, s + 1)) return wordlist[key].id; } } return 0; } /* flag table */ static struct { char *name; guint32 flag; } flag_table[] = { { "\\ANSWERED", CAMEL_MESSAGE_ANSWERED }, { "\\DELETED", CAMEL_MESSAGE_DELETED }, { "\\DRAFT", CAMEL_MESSAGE_DRAFT }, { "\\FLAGGED", CAMEL_MESSAGE_FLAGGED }, { "\\SEEN", CAMEL_MESSAGE_SEEN }, { "\\RECENT", CAMEL_IMAPP_MESSAGE_RECENT }, { "\\*", CAMEL_MESSAGE_USER }, }; /* utility functions shoudl this be part of imapp-driver? */ /* mabye this should be a stream op? */ void imap_parse_flags(CamelIMAPPStream *stream, guint32 *flagsp) /* throws IO,PARSE exception */ { int tok, len, i; unsigned char *token, *p, c; guint32 flags = 0; *flagsp = flags; tok = camel_imapp_stream_token(stream, &token, &len); if (tok == '(') { do { tok = camel_imapp_stream_token(stream, &token, &len); if (tok == IMAP_TOK_TOKEN) { p = token; while ((c=*p)) *p++ = toupper(c); for (i=0;i<(int)(sizeof(flag_table)/sizeof(flag_table[0]));i++) if (!strcmp(token, flag_table[i].name)) flags |= flag_table[i].flag; } else if (tok != ')') { camel_exception_throw(1, "expecting flag"); } } while (tok != ')'); } else { camel_exception_throw(1, "expecting flag list"); } *flagsp = flags; } void imap_write_flags(CamelStream *stream, guint32 flags) /* throws IO exception */ { int i; /* all this ugly exception throwing goes away once camel streams throw their own? */ if (camel_stream_write(stream, "(", 1) == -1) camel_exception_throw(1, "io error: %s", strerror(errno)); for (i=0;flags!=0 && i<(int)(sizeof(flag_table)/sizeof(flag_table[0]));i++) { if (flag_table[i].flag & flags) { if (camel_stream_write(stream, flag_table[i].name, strlen(flag_table[i].name)) == -1) camel_exception_throw(1, "io error: %s", strerror(errno)); flags &= ~flag_table[i].flag; if (flags != 0) if (camel_stream_write(stream, " ", 1) == -1) camel_exception_throw(1, "io error: %s", strerror(errno)); } } if (camel_stream_write(stream, ")", 1) == -1) camel_exception_throw(1, "io error: %s", strerror(errno)); } /* body ::= "(" body_type_1part / body_type_mpart ")" body_extension ::= nstring / number / "(" 1#body_extension ")" ;; Future expansion. Client implementations ;; MUST accept body_extension fields. Server ;; implementations MUST NOT generate ;; body_extension fields except as defined by ;; future standard or standards-track ;; revisions of this specification. body_ext_1part ::= body_fld_md5 [SPACE body_fld_dsp [SPACE body_fld_lang [SPACE 1#body_extension]]] ;; MUST NOT be returned on non-extensible ;; "BODY" fetch body_ext_mpart ::= body_fld_param [SPACE body_fld_dsp SPACE body_fld_lang [SPACE 1#body_extension]] ;; MUST NOT be returned on non-extensible ;; "BODY" fetch body_fields ::= body_fld_param SPACE body_fld_id SPACE body_fld_desc SPACE body_fld_enc SPACE body_fld_octets body_fld_desc ::= nstring body_fld_dsp ::= "(" string SPACE body_fld_param ")" / nil body_fld_enc ::= (<"> ("7BIT" / "8BIT" / "BINARY" / "BASE64"/ "QUOTED-PRINTABLE") <">) / string body_fld_id ::= nstring body_fld_lang ::= nstring / "(" 1#string ")" body_fld_lines ::= number body_fld_md5 ::= nstring body_fld_octets ::= number body_fld_param ::= "(" 1#(string SPACE string) ")" / nil body_type_1part ::= (body_type_basic / body_type_msg / body_type_text) [SPACE body_ext_1part] body_type_basic ::= media_basic SPACE body_fields ;; MESSAGE subtype MUST NOT be "RFC822" body_type_mpart ::= 1*body SPACE media_subtype [SPACE body_ext_mpart] body_type_msg ::= media_message SPACE body_fields SPACE envelope SPACE body SPACE body_fld_lines body_type_text ::= media_text SPACE body_fields SPACE body_fld_lines envelope ::= "(" env_date SPACE env_subject SPACE env_from SPACE env_sender SPACE env_reply_to SPACE env_to SPACE env_cc SPACE env_bcc SPACE env_in_reply_to SPACE env_message_id ")" env_bcc ::= "(" 1*address ")" / nil env_cc ::= "(" 1*address ")" / nil env_date ::= nstring env_from ::= "(" 1*address ")" / nil env_in_reply_to ::= nstring env_message_id ::= nstring env_reply_to ::= "(" 1*address ")" / nil env_sender ::= "(" 1*address ")" / nil env_subject ::= nstring env_to ::= "(" 1*address ")" / nil media_basic ::= (<"> ("APPLICATION" / "AUDIO" / "IMAGE" / "MESSAGE" / "VIDEO") <">) / string) SPACE media_subtype ;; Defined in [MIME-IMT] media_message ::= <"> "MESSAGE" <"> SPACE <"> "RFC822" <"> ;; Defined in [MIME-IMT] media_subtype ::= string ;; Defined in [MIME-IMT] media_text ::= <"> "TEXT" <"> SPACE media_subtype ;; Defined in [MIME-IMT] ( "type" "subtype" body_fields [envelope body body_fld_lines] [body_fld_lines] (("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 1152 23)("TEXT" "PLAIN" ("CHARSET" "US-ASCII" "NAME" "cc.diff") "<960723163407.20117h@cac.washington.edu>" "Compiler diff" "BASE64" 4554 73) "MIXED")) */ /* struct _body_fields { CamelContentType *ct; char *msgid, *desc; CamelTransferEncoding encoding; guint32 size; };*/ void imap_free_body(struct _CamelMessageContentInfo *cinfo) { struct _CamelMessageContentInfo *list, *next; list = cinfo->childs; while (list) { next = list->next; imap_free_body(list); list = next; } if (cinfo->type) camel_content_type_unref(cinfo->type); g_free(cinfo->id); g_free(cinfo->description); g_free(cinfo->encoding); g_free(cinfo); } void imap_parse_param_list(CamelIMAPPStream *is, struct _camel_header_param **plist) { int tok, len; unsigned char *token, *param; p(printf("body_fld_param\n")); /* body_fld_param ::= "(" 1#(string SPACE string) ")" / nil */ tok = camel_imapp_stream_token(is, &token, &len); if (tok == '(') { while (1) { tok = camel_imapp_stream_token(is, &token, &len); if (tok == ')') break; camel_imapp_stream_ungettoken(is, tok, token, len); camel_imapp_stream_astring(is, &token); param = alloca(strlen(token)+1); strcpy(param, token); camel_imapp_stream_astring(is, &token); camel_header_set_param(plist, param, token); } } /* else check nil? no need */ } struct _CamelContentDisposition * imap_parse_ext_optional(CamelIMAPPStream *is) { int tok, len; unsigned char *token; struct _CamelContentDisposition * volatile dinfo = NULL; /* this parses both extension types, from the body_fld_dsp onwards */ /* although the grammars are different, they can be parsed the same way */ /* body_ext_1part ::= body_fld_md5 [SPACE body_fld_dsp [SPACE body_fld_lang [SPACE 1#body_extension]]] ;; MUST NOT be returned on non-extensible ;; "BODY" fetch */ /* body_ext_mpart ::= body_fld_param [SPACE body_fld_dsp SPACE body_fld_lang [SPACE 1#body_extension]] ;; MUST NOT be returned on non-extensible ;; "BODY" fetch */ CAMEL_TRY { /* body_fld_dsp ::= "(" string SPACE body_fld_param ")" / nil */ tok = camel_imapp_stream_token(is, &token, &len); switch (tok) { case '(': dinfo = g_malloc0(sizeof(*dinfo)); dinfo->refcount = 1; /* should be string */ camel_imapp_stream_astring(is, &token); dinfo->disposition = g_strdup(token); imap_parse_param_list(is, &dinfo->params); case IMAP_TOK_TOKEN: d(printf("body_fld_dsp: NIL\n")); break; default: camel_exception_throw(1, "body_fld_disp: expecting nil or list"); } p(printf("body_fld_lang\n")); /* body_fld_lang ::= nstring / "(" 1#string ")" */ /* we just drop the lang string/list, save it somewhere? */ tok = camel_imapp_stream_token(is, &token, &len); switch (tok) { case '(': while (1) { tok = camel_imapp_stream_token(is, &token, &len); if (tok == ')') { break; } else if (tok != IMAP_TOK_STRING) { camel_exception_throw(1, "expecting string"); } } break; case IMAP_TOK_TOKEN: d(printf("body_fld_lang = nil\n")); /* treat as 'nil' */ break; case IMAP_TOK_STRING: /* we have a string */ break; case IMAP_TOK_LITERAL: /* we have a literal string */ camel_imapp_stream_set_literal(is, len); while ((tok = camel_imapp_stream_getl(is, &token, &len)) > 0) { d(printf("Skip literal data '%.*s'\n", (int)len, token)); } break; } } CAMEL_CATCH(ex) { if (dinfo) camel_content_disposition_unref(dinfo); camel_exception_throw_ex(ex); } CAMEL_DONE; return dinfo; } struct _CamelMessageContentInfo * imap_parse_body_fields(CamelIMAPPStream *is) { unsigned char *token, *type; struct _CamelMessageContentInfo *cinfo; /* body_fields ::= body_fld_param SPACE body_fld_id SPACE body_fld_desc SPACE body_fld_enc SPACE body_fld_octets */ p(printf("body_fields\n")); cinfo = g_malloc0(sizeof(*cinfo)); CAMEL_TRY { /* this should be string not astring */ camel_imapp_stream_astring(is, &token); type = alloca(strlen(token)+1); strcpy(type, token); camel_imapp_stream_astring(is, &token); cinfo->type = camel_content_type_new(type, token); imap_parse_param_list(is, &cinfo->type->params); /* body_fld_id ::= nstring */ camel_imapp_stream_nstring(is, &token); cinfo->id = g_strdup(token); /* body_fld_desc ::= nstring */ camel_imapp_stream_nstring(is, &token); cinfo->description = g_strdup(token); /* body_fld_enc ::= (<"> ("7BIT" / "8BIT" / "BINARY" / "BASE64"/ "QUOTED-PRINTABLE") <">) / string */ camel_imapp_stream_astring(is, &token); cinfo->encoding = g_strdup(token); /* body_fld_octets ::= number */ cinfo->size = camel_imapp_stream_number(is); } CAMEL_CATCH(ex) { imap_free_body(cinfo); camel_exception_throw_ex(ex); } CAMEL_DONE; return cinfo; } struct _camel_header_address * imap_parse_address_list(CamelIMAPPStream *is) /* throws PARSE,IO exception */ { int tok, len; unsigned char *token, *host, *mbox; struct _camel_header_address *list = NULL; /* "(" 1*address ")" / nil */ CAMEL_TRY { tok = camel_imapp_stream_token(is, &token, &len); if (tok == '(') { while (1) { struct _camel_header_address *addr, *group = NULL; /* address ::= "(" addr_name SPACE addr_adl SPACE addr_mailbox SPACE addr_host ")" */ tok = camel_imapp_stream_token(is, &token, &len); if (tok == ')') break; if (tok != '(') camel_exception_throw(1, "missing '(' for address"); addr = camel_header_address_new(); addr->type = CAMEL_HEADER_ADDRESS_NAME; tok = camel_imapp_stream_nstring(is, &token); addr->name = g_strdup(token); /* we ignore the route, nobody uses it in the real world */ tok = camel_imapp_stream_nstring(is, &token); /* [RFC-822] group syntax is indicated by a special form of address structure in which the host name field is NIL. If the mailbox name field is also NIL, this is an end of group marker (semi-colon in RFC 822 syntax). If the mailbox name field is non-NIL, this is a start of group marker, and the mailbox name field holds the group name phrase. */ tok = camel_imapp_stream_nstring(is, &mbox); mbox = g_strdup(mbox); tok = camel_imapp_stream_nstring(is, &host); if (host == NULL) { if (mbox == NULL) { group = NULL; } else { d(printf("adding group '%s'\n", mbox)); g_free(addr->name); addr->name = mbox; addr->type = CAMEL_HEADER_ADDRESS_GROUP; camel_header_address_list_append(&list, addr); group = addr; } } else { addr->v.addr = g_strdup_printf("%s%s%s", mbox?(char *)mbox:"", host?"@":"", host?(char *)host:""); g_free(mbox); d(printf("adding address '%s'\n", addr->v.addr)); if (group != NULL) camel_header_address_add_member(group, addr); else camel_header_address_list_append(&list, addr); } do { tok = camel_imapp_stream_token(is, &token, &len); } while (tok != ')'); } } else { d(printf("empty, nil '%s'\n", token)); } } CAMEL_CATCH(ex) { camel_header_address_list_clear(&list); camel_exception_throw_ex(ex); } CAMEL_DONE; return list; } struct _CamelMessageInfo * imap_parse_envelope(CamelIMAPPStream *is) { int tok, len; unsigned char *token; struct _camel_header_address *addr, *addr_from; char *addrstr; struct _CamelMessageInfoBase *minfo; /* envelope ::= "(" env_date SPACE env_subject SPACE env_from SPACE env_sender SPACE env_reply_to SPACE env_to SPACE env_cc SPACE env_bcc SPACE env_in_reply_to SPACE env_message_id ")" */ p(printf("envelope\n")); minfo = (CamelMessageInfoBase *)camel_message_info_new(NULL); CAMEL_TRY { tok = camel_imapp_stream_token(is, &token, &len); if (tok != '(') camel_exception_throw(1, "envelope: expecting '('"); /* env_date ::= nstring */ camel_imapp_stream_nstring(is, &token); minfo->date_sent = camel_header_decode_date(token, NULL); /* env_subject ::= nstring */ tok = camel_imapp_stream_nstring(is, &token); minfo->subject = camel_pstring_strdup(token); /* we merge from/sender into from, append should probably merge more smartly? */ /* env_from ::= "(" 1*address ")" / nil */ addr_from = imap_parse_address_list(is); /* env_sender ::= "(" 1*address ")" / nil */ addr = imap_parse_address_list(is); if (addr_from) { camel_header_address_list_clear(&addr); #if 0 if (addr) camel_header_address_list_append_list(&addr_from, &addr); #endif } else { if (addr) addr_from = addr; } if (addr_from) { addrstr = camel_header_address_list_format(addr_from); minfo->from = camel_pstring_strdup(addrstr); g_free(addrstr); camel_header_address_list_clear(&addr_from); } /* we dont keep reply_to */ /* env_reply_to ::= "(" 1*address ")" / nil */ addr = imap_parse_address_list(is); camel_header_address_list_clear(&addr); /* env_to ::= "(" 1*address ")" / nil */ addr = imap_parse_address_list(is); if (addr) { addrstr = camel_header_address_list_format(addr); minfo->to = camel_pstring_strdup(addrstr); g_free(addrstr); camel_header_address_list_clear(&addr); } /* env_cc ::= "(" 1*address ")" / nil */ addr = imap_parse_address_list(is); if (addr) { addrstr = camel_header_address_list_format(addr); minfo->cc = camel_pstring_strdup(addrstr); g_free(addrstr); camel_header_address_list_clear(&addr); } /* we dont keep bcc either */ /* env_bcc ::= "(" 1*address ")" / nil */ addr = imap_parse_address_list(is); camel_header_address_list_clear(&addr); /* FIXME: need to put in-reply-to into references hash list */ /* env_in_reply_to ::= nstring */ tok = camel_imapp_stream_nstring(is, &token); /* FIXME: need to put message-id into message-id hash */ /* env_message_id ::= nstring */ tok = camel_imapp_stream_nstring(is, &token); tok = camel_imapp_stream_token(is, &token, &len); if (tok != ')') camel_exception_throw(1, "expecting ')'"); } CAMEL_CATCH(ex) { camel_message_info_free(minfo); camel_exception_throw_ex(ex); } CAMEL_DONE; return (CamelMessageInfo *)minfo; } struct _CamelMessageContentInfo * imap_parse_body(CamelIMAPPStream *is) { int tok, len; unsigned char *token; struct _CamelMessageContentInfo * volatile cinfo = NULL; struct _CamelMessageContentInfo *subinfo, *last; struct _CamelContentDisposition * volatile dinfo = NULL; struct _CamelMessageInfo * volatile minfo = NULL; /* body ::= "(" body_type_1part / body_type_mpart ")" */ p(printf("body\n")); CAMEL_TRY { tok = camel_imapp_stream_token(is, &token, &len); if (tok != '(') camel_exception_throw(1, "body: expecting '('"); /* 1*body (optional for multiparts) */ tok = camel_imapp_stream_token(is, &token, &len); camel_imapp_stream_ungettoken(is, tok, token, len); if (tok == '(') { /* body_type_mpart ::= 1*body SPACE media_subtype [SPACE body_ext_mpart] */ cinfo = g_malloc0(sizeof(*cinfo)); last = (struct _CamelMessageContentInfo *)&cinfo->childs; do { subinfo = imap_parse_body(is); last->next = subinfo; last = subinfo; subinfo->parent = cinfo; tok = camel_imapp_stream_token(is, &token, &len); camel_imapp_stream_ungettoken(is, tok, token, len); } while (tok == '('); d(printf("media_subtype\n")); camel_imapp_stream_astring(is, &token); cinfo->type = camel_content_type_new("multipart", token); /* body_ext_mpart ::= body_fld_param [SPACE body_fld_dsp SPACE body_fld_lang [SPACE 1#body_extension]] ;; MUST NOT be returned on non-extensible ;; "BODY" fetch */ d(printf("body_ext_mpart\n")); tok = camel_imapp_stream_token(is, &token, &len); camel_imapp_stream_ungettoken(is, tok, token, len); if (tok == '(') { imap_parse_param_list(is, &cinfo->type->params); /* body_fld_dsp ::= "(" string SPACE body_fld_param ")" / nil */ tok = camel_imapp_stream_token(is, &token, &len); camel_imapp_stream_ungettoken(is, tok, token, len); if (tok == '(' || tok == IMAP_TOK_TOKEN) { dinfo = imap_parse_ext_optional(is); /* other extension fields?, soaked up below */ } else { camel_imapp_stream_ungettoken(is, tok, token, len); } } } else { /* body_type_1part ::= (body_type_basic / body_type_msg / body_type_text) [SPACE body_ext_1part] body_type_basic ::= media_basic SPACE body_fields body_type_text ::= media_text SPACE body_fields SPACE body_fld_lines body_type_msg ::= media_message SPACE body_fields SPACE envelope SPACE body SPACE body_fld_lines */ d(printf("Single part body\n")); cinfo = imap_parse_body_fields(is); d(printf("envelope?\n")); /* do we have an envelope following */ tok = camel_imapp_stream_token(is, &token, &len); camel_imapp_stream_ungettoken(is, tok, token, len); if (tok == '(') { /* what do we do with the envelope?? */ minfo = imap_parse_envelope(is); /* what do we do with the message content info?? */ ((CamelMessageInfoBase *)minfo)->content = imap_parse_body(is); camel_message_info_free(minfo); minfo = NULL; d(printf("Scanned envelope - what do i do with it?\n")); } d(printf("fld_lines?\n")); /* do we have fld_lines following? */ tok = camel_imapp_stream_token(is, &token, &len); if (tok == IMAP_TOK_INT) { d(printf("field lines: %s\n", token)); tok = camel_imapp_stream_token(is, &token, &len); } camel_imapp_stream_ungettoken(is, tok, token, len); /* body_ext_1part ::= body_fld_md5 [SPACE body_fld_dsp [SPACE body_fld_lang [SPACE 1#body_extension]]] ;; MUST NOT be returned on non-extensible ;; "BODY" fetch */ d(printf("extension data?\n")); if (tok != ')') { camel_imapp_stream_nstring(is, &token); d(printf("md5: %s\n", token?(char *)token:"NIL")); /* body_fld_dsp ::= "(" string SPACE body_fld_param ")" / nil */ tok = camel_imapp_stream_token(is, &token, &len); camel_imapp_stream_ungettoken(is, tok, token, len); if (tok == '(' || tok == IMAP_TOK_TOKEN) { dinfo = imap_parse_ext_optional(is); /* then other extension fields, soaked up below */ } } } /* soak up any other extension fields that may be present */ /* there should only be simple tokens, no lists */ do { tok = camel_imapp_stream_token(is, &token, &len); if (tok != ')') d(printf("Dropping extension data '%s'\n", token)); } while (tok != ')'); } CAMEL_CATCH(ex) { if (cinfo) imap_free_body(cinfo); if (dinfo) camel_content_disposition_unref(dinfo); if (minfo) camel_message_info_free(minfo); camel_exception_throw_ex(ex); } CAMEL_DONE; /* FIXME: do something with the disposition, currently we have no way to pass it out? */ if (dinfo) camel_content_disposition_unref(dinfo); return cinfo; } char * imap_parse_section(CamelIMAPPStream *is) { int tok, len; unsigned char *token; char * volatile section = NULL; /* currently we only return the part within the [section] specifier any header fields are parsed, but dropped */ /* section ::= "[" [section_text / (nz_number *["." nz_number] ["." (section_text / "MIME")])] "]" section_text ::= "HEADER" / "HEADER.FIELDS" [".NOT"] SPACE header_list / "TEXT" */ CAMEL_TRY { tok = camel_imapp_stream_token(is, &token, &len); if (tok != '[') camel_exception_throw(1, "section: expecting '['"); tok = camel_imapp_stream_token(is, &token, &len); if (tok == IMAP_TOK_INT || tok == IMAP_TOK_TOKEN) section = g_strdup(token); else if (tok == ']') { section = g_strdup(""); camel_imapp_stream_ungettoken(is, tok, token, len); } else camel_exception_throw(1, "section: expecting token"); /* header_list ::= "(" 1#header_fld_name ")" header_fld_name ::= astring */ /* we dont need the header specifiers */ tok = camel_imapp_stream_token(is, &token, &len); if (tok == '(') { do { tok = camel_imapp_stream_token(is, &token, &len); if (tok == IMAP_TOK_STRING || tok == IMAP_TOK_TOKEN || tok == IMAP_TOK_INT) { /* ?do something? */ } else if (tok != ')') camel_exception_throw(1, "section: header fields: expecting string"); } while (tok != ')'); tok = camel_imapp_stream_token(is, &token, &len); } if (tok != ']') camel_exception_throw(1, "section: expecting ']'"); } CAMEL_CATCH(ex) { g_free(section); camel_exception_throw_ex(ex); } CAMEL_DONE; return section; } void imap_free_fetch(struct _fetch_info *finfo) { if (finfo == NULL) return; if (finfo->body) camel_object_unref((CamelObject *)finfo->body); if (finfo->text) camel_object_unref((CamelObject *)finfo->text); if (finfo->header) camel_object_unref((CamelObject *)finfo->header); if (finfo->minfo) camel_message_info_free(finfo->minfo); if (finfo->cinfo) imap_free_body(finfo->cinfo); g_free(finfo->date); g_free(finfo->section); g_free(finfo->uid); g_free(finfo); } extern void camel_content_info_dump(CamelMessageContentInfo *ci, int depth); extern void camel_message_info_dump(CamelMessageInfo *mi); #include /* debug, dump one out */ void imap_dump_fetch(struct _fetch_info *finfo) { CamelStream *sout; int fd; printf("Fetch info:\n"); if (finfo == NULL) { printf("Empty\n"); return; } fd = dup(1); sout = camel_stream_fs_new_with_fd(fd); if (finfo->body) { camel_stream_printf(sout, "Body content:\n"); camel_stream_write_to_stream(finfo->body, sout); } if (finfo->text) { camel_stream_printf(sout, "Text content:\n"); camel_stream_write_to_stream(finfo->text, sout); } if (finfo->header) { camel_stream_printf(sout, "Header content:\n"); camel_stream_write_to_stream(finfo->header, sout); } if (finfo->minfo) { camel_stream_printf(sout, "Message Info:\n"); camel_message_info_dump(finfo->minfo); } if (finfo->cinfo) { camel_stream_printf(sout, "Content Info:\n"); camel_content_info_dump(finfo->cinfo, 0); } if (finfo->got & FETCH_SIZE) camel_stream_printf(sout, "Size: %d\n", (int)finfo->size); if (finfo->got & FETCH_BODY) camel_stream_printf(sout, "Offset: %d\n", (int)finfo->offset); if (finfo->got & FETCH_FLAGS) camel_stream_printf(sout, "Flags: %08x\n", (int)finfo->flags); if (finfo->date) camel_stream_printf(sout, "Date: '%s'\n", finfo->date); if (finfo->section) camel_stream_printf(sout, "Section: '%s'\n", finfo->section); if (finfo->date) camel_stream_printf(sout, "UID: '%s'\n", finfo->uid); camel_object_unref((CamelObject *)sout); } struct _fetch_info * imap_parse_fetch(CamelIMAPPStream *is) { int tok, len; unsigned char *token, *p, c; struct _fetch_info *finfo; finfo = g_malloc0(sizeof(*finfo)); CAMEL_TRY { tok = camel_imapp_stream_token(is, &token, &len); if (tok != '(') camel_exception_throw(1, "fetch: expecting '('"); while ( (tok = camel_imapp_stream_token(is, &token, &len)) == IMAP_TOK_TOKEN ) { p = token; while ((c=*p)) *p++ = toupper(c); switch(imap_tokenise(token, len)) { case IMAP_ENVELOPE: finfo->minfo = imap_parse_envelope(is); finfo->got |= FETCH_MINFO; break; case IMAP_FLAGS: imap_parse_flags(is, &finfo->flags); finfo->got |= FETCH_FLAGS; break; case IMAP_INTERNALDATE: camel_imapp_stream_nstring(is, &token); /* TODO: convert to camel format? */ finfo->date = g_strdup(token); finfo->got |= FETCH_DATE; break; case IMAP_RFC822_HEADER: camel_imapp_stream_nstring_stream(is, &finfo->header); finfo->got |= FETCH_HEADER; break; case IMAP_RFC822_TEXT: camel_imapp_stream_nstring_stream(is, &finfo->text); finfo->got |= FETCH_TEXT; break; case IMAP_RFC822_SIZE: finfo->size = camel_imapp_stream_number(is); finfo->got |= FETCH_SIZE; break; case IMAP_BODYSTRUCTURE: finfo->cinfo = imap_parse_body(is); finfo->got |= FETCH_CINFO; break; case IMAP_BODY: tok = camel_imapp_stream_token(is, &token, &len); camel_imapp_stream_ungettoken(is, tok, token, len); if (tok == '(') { finfo->cinfo = imap_parse_body(is); finfo->got |= FETCH_CINFO; } else if (tok == '[') { finfo->section = imap_parse_section(is); finfo->got |= FETCH_SECTION; tok = camel_imapp_stream_token(is, &token, &len); if (token[0] == '<') { finfo->offset = strtoul(token+1, NULL, 10); } else { camel_imapp_stream_ungettoken(is, tok, token, len); } camel_imapp_stream_nstring_stream(is, &finfo->body); finfo->got |= FETCH_BODY; } else { camel_exception_throw(1, "unknown body response"); } break; case IMAP_UID: tok = camel_imapp_stream_token(is, &token, &len); if (tok != IMAP_TOK_INT) camel_exception_throw(1, "uid not integer"); finfo->uid = g_strdup(token); finfo->got |= FETCH_UID; break; default: camel_exception_throw(1, "unknown body response"); } } if (tok != ')') camel_exception_throw(1, "missing closing ')' on fetch response"); } CAMEL_CATCH(ex) { imap_free_fetch(finfo); camel_exception_throw_ex(ex); } CAMEL_DONE; return finfo; } /* rfc 2060 section 7.1 Status Responses */ /* shoudl this start after [ or before the [? token_unget anyone? */ struct _status_info * imap_parse_status(CamelIMAPPStream *is) { int tok, len; unsigned char *token; struct _status_info *sinfo; sinfo = g_malloc0(sizeof(*sinfo)); CAMEL_TRY { camel_imapp_stream_atom(is, &token, &len); /* resp_cond_auth ::= ("OK" / "PREAUTH") SPACE resp_text ;; Authentication condition resp_cond_bye ::= "BYE" SPACE resp_text resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text ;; Status condition */ sinfo->result = imap_tokenise(token, len); switch (sinfo->result) { case IMAP_OK: case IMAP_NO: case IMAP_BAD: case IMAP_PREAUTH: case IMAP_BYE: break; default: camel_exception_throw(1, "expecting OK/NO/BAD"); } tok = camel_imapp_stream_token(is, &token, &len); if (tok == '[') { camel_imapp_stream_atom(is, &token, &len); sinfo->condition = imap_tokenise(token, len); /* parse any details */ switch (sinfo->condition) { case IMAP_READ_ONLY: case IMAP_READ_WRITE: case IMAP_ALERT: case IMAP_PARSE: case IMAP_TRYCREATE: break; case IMAP_NEWNAME: /* the rfc doesn't specify the bnf for this */ camel_imapp_stream_astring(is, &token); sinfo->u.newname.oldname = g_strdup(token); camel_imapp_stream_astring(is, &token); sinfo->u.newname.newname = g_strdup(token); break; case IMAP_PERMANENTFLAGS: imap_parse_flags(is, &sinfo->u.permanentflags); break; case IMAP_UIDVALIDITY: sinfo->u.uidvalidity = camel_imapp_stream_number(is); break; case IMAP_UNSEEN: sinfo->u.unseen = camel_imapp_stream_number(is); break; default: sinfo->condition = IMAP_UNKNOWN; printf("Got unknown response code: %s: ignored\n", token); } /* ignore anything we dont know about */ do { tok = camel_imapp_stream_token(is, &token, &len); if (tok == '\n') camel_exception_throw(1, "server response truncated"); } while (tok != ']'); } else { camel_imapp_stream_ungettoken(is, tok, token, len); } /* and take the human readable response */ camel_imapp_stream_text(is, (unsigned char **)&sinfo->text); } CAMEL_CATCH(ex) { imap_free_status(sinfo); camel_exception_throw_ex(ex); } CAMEL_DONE; return sinfo; } void imap_free_status(struct _status_info *sinfo) { if (sinfo == NULL) return; switch (sinfo->condition) { case IMAP_NEWNAME: g_free(sinfo->u.newname.oldname); g_free(sinfo->u.newname.newname); default: break; } g_free(sinfo->text); g_free(sinfo); } /* FIXME: use tokeniser? */ /* FIXME: real flags */ static struct { char *name; guint32 flag; } list_flag_table[] = { { "\\NOINFERIORS", CAMEL_FOLDER_NOINFERIORS }, { "\\NOSELECT", CAMEL_FOLDER_NOSELECT }, { "\\MARKED", 1<<8 }, { "\\UNMARKED", 1<<9 }, }; struct _list_info * imap_parse_list(CamelIMAPPStream *is) /* throws io, parse */ { int tok, len, i; unsigned char *token, *p, c; struct _list_info * volatile linfo; linfo = g_malloc0(sizeof(*linfo)); CAMEL_TRY { /* mailbox_list ::= "(" #("\Marked" / "\Noinferiors" / "\Noselect" / "\Unmarked" / flag_extension) ")" SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox */ tok = camel_imapp_stream_token(is, &token, &len); if (tok != '(') camel_exception_throw(1, "list: expecting '('"); while ( (tok = camel_imapp_stream_token(is, &token, &len)) != ')' ) { if (tok == IMAP_TOK_STRING || tok == IMAP_TOK_TOKEN) { p = token; while ((c=*p)) *p++ = toupper(c); for (i=0;i<(int)(sizeof(list_flag_table)/sizeof(list_flag_table[0]));i++) if (!strcmp(token, list_flag_table[i].name)) linfo->flags |= list_flag_table[i].flag; } else { camel_exception_throw(1, "list: expecting flag or ')'"); } } camel_imapp_stream_nstring(is, &token); linfo->separator = token?*token:0; camel_imapp_stream_astring(is, &token); linfo->name = g_strdup(token); } CAMEL_CATCH(ex) { imap_free_list(linfo); camel_exception_throw_ex(ex); } CAMEL_DONE; return linfo; } char * imapp_list_get_path(struct _list_info *li) { char *path, *p; int c; const char *f; if (li->separator != 0 && li->separator != '/') { p = path = alloca(strlen(li->name)*3+1); f = li->name; while ( (c = *f++ & 0xff) ) { if (c == li->separator) *p++ = '/'; else if (c == '/' || c == '%') p += sprintf(p, "%%%02X", c); else *p++ = c; } *p = 0; } else path = li->name; return camel_utf7_utf8(path); } void imap_free_list(struct _list_info *linfo) { if (linfo) { g_free(linfo->name); g_free(linfo); } } /* ********************************************************************** */ /* utility functions */ /* should the rest of imapp-utils go into imapp-parse? */ /* this creates a uid (or sequence number) set directly into the command, optionally breaking it into smaller chunks */ void imapp_uidset_init(struct _uidset_state *ss, CamelIMAPPEngine *ie) { ss->ie = ie; ss->len = 0; ss->start = 0; ss->last = 0; } int imapp_uidset_done(struct _uidset_state *ss, CamelIMAPPCommand *ic) { int ret = 0; if (ss->last != 0 && ss->last != ss->start) { camel_imapp_engine_command_add(ss->ie, ic, ":%d", ss->last); printf(":%d", ss->last); } ret = ss->last != 0; ss->start = 0; ss->last = 0; ss->len = 0; return ret; } int imapp_uidset_add(struct _uidset_state *ss, CamelIMAPPCommand *ic, const char *uid) { guint32 uidn; uidn = strtoul(uid, NULL, 10); if (uidn == 0) return -1; if (ss->last == 0) { camel_imapp_engine_command_add(ss->ie, ic, "%d", uidn); printf("%d", uidn); ss->len ++; ss->start = uidn; } else { if (ss->last != uidn-1) { if (ss->last == ss->start) { camel_imapp_engine_command_add(ss->ie, ic, ",%d", uidn); printf(",%d", uidn); ss->len ++; } else { camel_imapp_engine_command_add(ss->ie, ic, ":%d,%d", ss->last, uidn); printf(":%d,%d", ss->last, uidn); ss->len+=2; } ss->start = uidn; } } ss->last = uidn; if (ss->len > 10) { imapp_uidset_done(ss, ic); return 1; } return 0; }