/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
*
* Author:
* Michael Zucchi <notzed@ximian.com>
*
* 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 <config.h>
#endif
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <glib.h>
#include <camel/camel-stream-mem.h>
#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 ::= <any CHAR except atom_specials>
atom_specials ::= "(" / ")" / "{" / SPACE / CTL / list_wildcards /
quoted_specials
CHAR ::= <any 7-bit US-ASCII character except NUL,
0x01 - 0x7f>
CTL ::= <any ASCII control character and DEL,
0x00 - 0x1f, 0x7f>
SPACE ::= <ASCII SP, space, 0x20>
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;
}