/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- * * Author: * Michael Zucchi * * Copyright 1999, 2000 Ximian, Inc. (www.ximian.com) * * 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 #endif #include #include #include #include #include #include #include "camel-imapp-stream.h" #include "camel-imapp-exception.h" #define t(x) #define io(x) x static void setup_table(void); static CamelObjectClass *parent_class = NULL; /* Returns the class for a CamelStream */ #define CS_CLASS(so) CAMEL_IMAPP_STREAM_CLASS(CAMEL_OBJECT_GET_CLASS(so)) #define CAMEL_IMAPP_STREAM_SIZE (4096) #define CAMEL_IMAPP_STREAM_TOKEN (4096) /* maximum token size */ static int stream_fill(CamelIMAPPStream *is) { int left = 0; if (is->source) { left = is->end - is->ptr; memcpy(is->buf, is->ptr, left); is->end = is->buf + left; is->ptr = is->buf; left = camel_stream_read(is->source, is->end, CAMEL_IMAPP_STREAM_SIZE - (is->end - is->buf)); if (left > 0) { is->end += left; io(printf("camel_imapp_read: buffer is '%.*s'\n", is->end - is->ptr, is->ptr)); return is->end - is->ptr; } else { io(printf("camel_imapp_read: -1\n")); return -1; } } printf("camel_imapp_read: -1\n"); return -1; } static ssize_t stream_read(CamelStream *stream, char *buffer, size_t n) { CamelIMAPPStream *is = (CamelIMAPPStream *)stream; ssize_t max; if (is->literal == 0 || n == 0) return 0; max = is->end - is->ptr; if (max > 0) { max = MIN(max, is->literal); max = MIN(max, n); memcpy(buffer, is->ptr, max); is->ptr += max; } else { max = MIN(is->literal, n); max = camel_stream_read(is->source, buffer, max); if (max <= 0) return max; } is->literal -= max; return max; } static ssize_t stream_write(CamelStream *stream, const char *buffer, size_t n) { CamelIMAPPStream *is = (CamelIMAPPStream *)stream; return camel_stream_write(is->source, buffer, n); } static int stream_close(CamelStream *stream) { /* nop? */ return 0; } static int stream_flush(CamelStream *stream) { /* nop? */ return 0; } static gboolean stream_eos(CamelStream *stream) { CamelIMAPPStream *is = (CamelIMAPPStream *)stream; return is->literal == 0; } static int stream_reset(CamelStream *stream) { /* nop? reset literal mode? */ return 0; } static void camel_imapp_stream_class_init (CamelStreamClass *camel_imapp_stream_class) { CamelStreamClass *camel_stream_class = (CamelStreamClass *)camel_imapp_stream_class; parent_class = camel_type_get_global_classfuncs( CAMEL_OBJECT_TYPE ); /* virtual method definition */ camel_stream_class->read = stream_read; camel_stream_class->write = stream_write; camel_stream_class->close = stream_close; camel_stream_class->flush = stream_flush; camel_stream_class->eos = stream_eos; camel_stream_class->reset = stream_reset; } static void camel_imapp_stream_init(CamelIMAPPStream *is, CamelIMAPPStreamClass *isclass) { /* +1 is room for appending a 0 if we need to for a token */ is->ptr = is->end = is->buf = g_malloc(CAMEL_IMAPP_STREAM_SIZE+1); is->tokenptr = is->tokenbuf = g_malloc(CAMEL_IMAPP_STREAM_SIZE+1); is->tokenend = is->tokenbuf + CAMEL_IMAPP_STREAM_SIZE; } static void camel_imapp_stream_finalise(CamelIMAPPStream *is) { g_free(is->buf); if (is->source) camel_object_unref((CamelObject *)is->source); } CamelType camel_imapp_stream_get_type (void) { static CamelType camel_imapp_stream_type = CAMEL_INVALID_TYPE; if (camel_imapp_stream_type == CAMEL_INVALID_TYPE) { setup_table(); camel_imapp_stream_type = camel_type_register( camel_stream_get_type(), "CamelIMAPPStream", sizeof( CamelIMAPPStream ), sizeof( CamelIMAPPStreamClass ), (CamelObjectClassInitFunc) camel_imapp_stream_class_init, NULL, (CamelObjectInitFunc) camel_imapp_stream_init, (CamelObjectFinalizeFunc) camel_imapp_stream_finalise ); } return camel_imapp_stream_type; } /** * camel_imapp_stream_new: * * Returns a NULL stream. A null stream is always at eof, and * always returns success for all reads and writes. * * Return value: the stream **/ CamelStream * camel_imapp_stream_new(CamelStream *source) { CamelIMAPPStream *is; is = (CamelIMAPPStream *)camel_object_new(camel_imapp_stream_get_type ()); camel_object_ref((CamelObject *)source); is->source = source; return (CamelStream *)is; } /* From rfc2060 ATOM_CHAR ::= atom_specials ::= "(" / ")" / "{" / SPACE / CTL / list_wildcards / quoted_specials CHAR ::= CTL ::= SPACE ::= list_wildcards ::= "%" / "*" quoted_specials ::= <"> / "\" */ static unsigned char imap_specials[256] = { /* 00 */0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10 */0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20 */0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, /* 30 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 40 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 50 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, /* 60 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 70 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; #define imap_is_atom(c) ((imap_specials[(c)&0xff] & 0x01) != 0) #define imap_is_simple(c) ((imap_specials[(c)&0xff] & 0x02) != 0) #define imap_not_id(c) ((imap_specials[(c)&0xff] & 0x04) != 0) /* could be pregenerated, but this is cheap */ static struct { unsigned char *chars; unsigned char mask; } is_masks[] = { { "\n*()[]+", 2 }, { " \r\n()[]+", 4 }, }; static void setup_table(void) { int i; unsigned char *p, c; for (i=0;i<(int)(sizeof(is_masks)/sizeof(is_masks[0]));i++) { p = is_masks[i].chars; while ((c = *p++)) imap_specials[c] |= is_masks[i].mask; } } #if 0 static int skip_ws(CamelIMAPPStream *is, unsigned char *pp, unsigned char *pe) { register unsigned char c, *p; unsigned char *e; p = is->ptr; e = is->end; do { while (p >= e ) { is->ptr = p; if (stream_fill(is) == IMAP_TOK_ERROR) return IMAP_TOK_ERROR; p = is->ptr; e = is->end; } c = *p++; } while (c == ' ' || c == '\r'); is->ptr = p; is->end = e; return c; } #endif /* FIXME: these should probably handle it themselves, and get rid of the token interface? */ int camel_imapp_stream_atom(CamelIMAPPStream *is, unsigned char **data, unsigned int *lenp) { unsigned char *p, c; /* this is only 'approximate' atom */ switch(camel_imapp_stream_token(is, data, lenp)) { case IMAP_TOK_TOKEN: p = *data; while ((c = *p)) *p++ = toupper(c); case IMAP_TOK_INT: return 0; case IMAP_TOK_ERROR: return IMAP_TOK_ERROR; default: camel_exception_throw(1, "expecting atom"); printf("expecting atom!\n"); return IMAP_TOK_PROTOCOL; } } /* gets an atom, a quoted_string, or a literal */ int camel_imapp_stream_astring(CamelIMAPPStream *is, unsigned char **data) { unsigned char *p, *start; unsigned int len, inlen; switch(camel_imapp_stream_token(is, data, &len)) { case IMAP_TOK_TOKEN: case IMAP_TOK_INT: case IMAP_TOK_STRING: return 0; case IMAP_TOK_LITERAL: /* FIXME: just grow buffer */ if (len >= CAMEL_IMAPP_STREAM_TOKEN) { camel_exception_throw(1, "astring: literal too long"); printf("astring too long\n"); return IMAP_TOK_PROTOCOL; } p = is->tokenptr; camel_imapp_stream_set_literal(is, len); do { len = camel_imapp_stream_getl(is, &start, &inlen); if (len < 0) return len; memcpy(p, start, inlen); p += inlen; } while (len > 0); *data = is->tokenptr; return 0; case IMAP_TOK_ERROR: /* wont get unless no exception hanlder*/ return IMAP_TOK_ERROR; default: camel_exception_throw(1, "expecting astring"); printf("expecting astring!\n"); return IMAP_TOK_PROTOCOL; } } /* check for NIL or (small) quoted_string or literal */ int camel_imapp_stream_nstring(CamelIMAPPStream *is, unsigned char **data) { unsigned char *p, *start; unsigned int len, inlen; switch(camel_imapp_stream_token(is, data, &len)) { case IMAP_TOK_STRING: return 0; case IMAP_TOK_LITERAL: /* FIXME: just grow buffer */ if (len >= CAMEL_IMAPP_STREAM_TOKEN) { camel_exception_throw(1, "nstring: literal too long"); return IMAP_TOK_PROTOCOL; } p = is->tokenptr; camel_imapp_stream_set_literal(is, len); do { len = camel_imapp_stream_getl(is, &start, &inlen); if (len < 0) return len; memcpy(p, start, inlen); p += inlen; } while (len > 0); *data = is->tokenptr; return 0; case IMAP_TOK_TOKEN: p = *data; if (toupper(p[0]) == 'N' && toupper(p[1]) == 'I' && toupper(p[2]) == 'L' && p[3] == 0) { *data = NULL; return 0; } default: camel_exception_throw(1, "expecting nstring"); return IMAP_TOK_PROTOCOL; case IMAP_TOK_ERROR: /* we'll never get this unless there are no exception handlers anyway */ return IMAP_TOK_ERROR; } } /* parse an nstring as a stream */ int camel_imapp_stream_nstring_stream(CamelIMAPPStream *is, CamelStream **stream) /* throws IO,PARSE exception */ { unsigned char *token; unsigned int len; int ret = 0; CamelStream * volatile mem = NULL; *stream = NULL; CAMEL_TRY { switch(camel_imapp_stream_token(is, &token, &len)) { case IMAP_TOK_STRING: mem = camel_stream_mem_new_with_buffer(token, len); *stream = mem; break; case IMAP_TOK_LITERAL: /* if len is big, we could automatically use a file backing */ camel_imapp_stream_set_literal(is, len); mem = camel_stream_mem_new(); if (camel_stream_write_to_stream((CamelStream *)is, mem) == -1) camel_exception_throw(1, "nstring: io error: %s", strerror(errno)); camel_stream_reset(mem); *stream = mem; break; case IMAP_TOK_TOKEN: if (toupper(token[0]) == 'N' && toupper(token[1]) == 'I' && toupper(token[2]) == 'L' && token[3] == 0) { *stream = NULL; break; } default: ret = -1; camel_exception_throw(1, "nstring: token not string"); } } CAMEL_CATCH(ex) { if (mem) camel_object_unref((CamelObject *)mem); camel_exception_throw_ex(ex); } CAMEL_DONE; /* never reaches here anyway */ return ret; } guint32 camel_imapp_stream_number(CamelIMAPPStream *is) { unsigned char *token; unsigned int len; if (camel_imapp_stream_token(is, &token, &len) != IMAP_TOK_INT) { camel_exception_throw(1, "expecting number"); return 0; } return strtoul(token, 0, 10); } int camel_imapp_stream_text(CamelIMAPPStream *is, unsigned char **text) { GByteArray *build = g_byte_array_new(); unsigned char *token; unsigned int len; int tok; CAMEL_TRY { while (is->unget > 0) { switch (is->unget_tok) { case IMAP_TOK_TOKEN: case IMAP_TOK_STRING: case IMAP_TOK_INT: g_byte_array_append(build, is->unget_token, is->unget_len); g_byte_array_append(build, " ", 1); default: /* invalid, but we'll ignore */ break; } is->unget--; } do { tok = camel_imapp_stream_gets(is, &token, &len); if (tok < 0) camel_exception_throw(1, "io error: %s", strerror(errno)); if (len) g_byte_array_append(build, token, len); } while (tok > 0); } CAMEL_CATCH(ex) { *text = NULL; g_byte_array_free(build, TRUE); camel_exception_throw_ex(ex); } CAMEL_DONE; g_byte_array_append(build, "", 1); *text = build->data; g_byte_array_free(build, FALSE); return 0; } /* Get one token from the imap stream */ camel_imapp_token_t /* throws IO,PARSE exception */ camel_imapp_stream_token(CamelIMAPPStream *is, unsigned char **data, unsigned int *len) { register unsigned char c, *p, *o, *oe; unsigned char *e; unsigned int literal; int digits; if (is->unget > 0) { is->unget--; *data = is->unget_token; *len = is->unget_len; /*printf("token UNGET '%c' %s\n", is->unget_tok, is->unget_token);*/ return is->unget_tok; } if (is->literal > 0) g_warning("stream_token called with literal %d", is->literal); p = is->ptr; e = is->end; /* skip whitespace/prefill buffer */ do { while (p >= e ) { is->ptr = p; if (stream_fill(is) == IMAP_TOK_ERROR) goto io_error; p = is->ptr; e = is->end; } c = *p++; } while (c == ' ' || c == '\r'); /*strchr("\n*()[]+", c)*/ if (imap_is_simple(c)) { is->ptr = p; t(printf("token '%c'\n", c)); return c; } else if (c == '{') { literal = 0; *data = p; while (1) { while (p < e) { c = *p++; if (isdigit(c) && literal < (UINT_MAX/10)) { literal = literal * 10 + (c - '0'); } else if (c == '}') { while (1) { while (p < e) { c = *p++; if (c == '\n') { *len = literal; is->ptr = p; is->literal = literal; t(printf("token LITERAL %d\n", literal)); return IMAP_TOK_LITERAL; } } is->ptr = p; if (stream_fill(is) == IMAP_TOK_ERROR) goto io_error; p = is->ptr; e = is->end; } } else { if (isdigit(c)) printf("Protocol error: literal too big\n"); else printf("Protocol error: literal contains invalid char %02x '%c'\n", c, isprint(c)?c:c); goto protocol_error; } } is->ptr = p; if (stream_fill(is) == IMAP_TOK_ERROR) goto io_error; p = is->ptr; e = is->end; } } else if (c == '"') { o = is->tokenptr; oe = is->tokenptr + CAMEL_IMAPP_STREAM_TOKEN - 1; while (1) { while (p < e) { c = *p++; if (c == '\\') { while (p >= e) { is->ptr = p; if (stream_fill(is) == IMAP_TOK_ERROR) goto io_error; p = is->ptr; e = is->end; } c = *p++; } else if (c == '\"') { is->ptr = p; *o = 0; *data = is->tokenbuf; *len = o - is->tokenbuf; t(printf("token STRING '%s'\n", is->tokenbuf)); return IMAP_TOK_STRING; } if (c == '\n' || c == '\r' || o>=oe) { if (o >= oe) printf("Protocol error: string too long\n"); else printf("Protocol error: truncated string\n"); goto protocol_error; } else { *o++ = c; } } is->ptr = p; if (stream_fill(is) == IMAP_TOK_ERROR) goto io_error; p = is->ptr; e = is->end; } } else { o = is->tokenptr; oe = is->tokenptr + CAMEL_IMAPP_STREAM_TOKEN - 1; digits = isdigit(c); *o++ = c; while (1) { while (p < e) { c = *p++; /*if (strchr(" \r\n*()[]+", c) != NULL) {*/ if (imap_not_id(c)) { if (c == ' ' || c == '\r') is->ptr = p; else is->ptr = p-1; *o = 0; *data = is->tokenbuf; *len = o - is->tokenbuf; t(printf("token TOKEN '%s'\n", is->tokenbuf)); return digits?IMAP_TOK_INT:IMAP_TOK_TOKEN; } else if (o < oe) { digits &= isdigit(c); *o++ = c; } else { printf("Protocol error: token too long\n"); goto protocol_error; } } is->ptr = p; if (stream_fill(is) == IMAP_TOK_ERROR) goto io_error; p = is->ptr; e = is->end; } } /* Had an i/o erorr */ io_error: printf("Got io error\n"); camel_exception_throw(1, "io error"); return IMAP_TOK_ERROR; /* Protocol error, skip until next lf? */ protocol_error: printf("Got protocol error\n"); if (c == '\n') is->ptr = p-1; else is->ptr = p; camel_exception_throw(1, "protocol error"); return IMAP_TOK_PROTOCOL; } void camel_imapp_stream_ungettoken(CamelIMAPPStream *is, camel_imapp_token_t tok, unsigned char *token, unsigned int len) { /*printf("ungettoken: '%c' '%s'\n", tok, token);*/ is->unget_tok = tok; is->unget_token = token; is->unget_len = len; is->unget++; } /* returns -1 on error, 0 if last lot of data, >0 if more remaining */ int camel_imapp_stream_gets(CamelIMAPPStream *is, unsigned char **start, unsigned int *len) { int max; unsigned char *end; *len = 0; max = is->end - is->ptr; if (max == 0) { max = stream_fill(is); if (max <= 0) return max; } *start = is->ptr; end = memchr(is->ptr, '\n', max); if (end) max = (end - is->ptr) + 1; *start = is->ptr; *len = max; is->ptr += max; return end == NULL?1:0; } void camel_imapp_stream_set_literal(CamelIMAPPStream *is, unsigned int literal) { is->literal = literal; } /* returns -1 on erorr, 0 if last data, >0 if more data left */ int camel_imapp_stream_getl(CamelIMAPPStream *is, unsigned char **start, unsigned int *len) { int max; *len = 0; if (is->literal > 0) { max = is->end - is->ptr; if (max == 0) { max = stream_fill(is); if (max <= 0) return max; } max = MIN(max, is->literal); *start = is->ptr; *len = max; is->ptr += max; is->literal -= max; } if (is->literal > 0) return 1; return 0; }