diff options
Diffstat (limited to 'camel/providers/imap4/camel-imap4-stream.c')
-rw-r--r-- | camel/providers/imap4/camel-imap4-stream.c | 708 |
1 files changed, 708 insertions, 0 deletions
diff --git a/camel/providers/imap4/camel-imap4-stream.c b/camel/providers/imap4/camel-imap4-stream.c new file mode 100644 index 0000000000..f2b2cbeb4b --- /dev/null +++ b/camel/providers/imap4/camel-imap4-stream.c @@ -0,0 +1,708 @@ +/* -*- 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 <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> + +#include "camel-imap4-specials.h" + +#include "camel-imap4-stream.h" + +#define d(x) x + +#define IMAP4_TOKEN_LEN 128 + +static void camel_imap4_stream_class_init (CamelIMAP4StreamClass *klass); +static void camel_imap4_stream_init (CamelIMAP4Stream *stream, CamelIMAP4StreamClass *klass); +static void camel_imap4_stream_finalize (CamelObject *object); + +static ssize_t stream_read (CamelStream *stream, char *buffer, size_t n); +static ssize_t stream_write (CamelStream *stream, const char *buffer, size_t n); +static int stream_flush (CamelStream *stream); +static int stream_close (CamelStream *stream); +static gboolean stream_eos (CamelStream *stream); + + +static CamelStreamClass *parent_class = NULL; + + +CamelType +camel_imap4_stream_get_type (void) +{ + static CamelType type = 0; + + if (!type) { + type = camel_type_register (CAMEL_TYPE_IMAP4_STREAM, + "CamelIMAP4Stream", + sizeof (CamelIMAP4Stream), + sizeof (CamelIMAP4StreamClass), + (CamelObjectClassInitFunc) camel_imap4_stream_class_init, + NULL, + (CamelObjectInitFunc) camel_imap4_stream_init, + (CamelObjectFinalizeFunc) camel_imap4_stream_finalize); + } + + return type; +} + +static void +camel_imap4_stream_class_init (CamelIMAP4StreamClass *klass) +{ + CamelStreamClass *stream_class = (CamelStreamClass *) klass; + + parent_class = (CamelStreamClass *) camel_type_get_global_classfuncs (CAMEL_STREAM_TYPE); + + /* virtual method overload */ + stream_class->read = stream_read; + stream_class->write = stream_write; + stream_class->flush = stream_flush; + stream_class->close = stream_close; + stream_class->eos = stream_eos; +} + +static void +camel_imap4_stream_init (CamelIMAP4Stream *imap4, CamelIMAP4StreamClass *klass) +{ + imap4->stream = NULL; + + imap4->mode = CAMEL_IMAP4_STREAM_MODE_TOKEN; + imap4->disconnected = FALSE; + imap4->eol = FALSE; + + imap4->literal = 0; + + imap4->inbuf = imap4->realbuf + IMAP4_READ_PRELEN; + imap4->inptr = imap4->inbuf; + imap4->inend = imap4->inbuf; + + imap4->tokenbuf = g_malloc (IMAP4_TOKEN_LEN); + imap4->tokenptr = imap4->tokenbuf; + imap4->tokenleft = IMAP4_TOKEN_LEN; + + imap4->unget = NULL; +} + +static void +camel_imap4_stream_finalize (CamelObject *object) +{ + CamelIMAP4Stream *imap4 = (CamelIMAP4Stream *) object; + + if (imap4->stream) + camel_object_unref (imap4->stream); + + g_free (imap4->tokenbuf); + g_free (imap4->unget); +} + + +static ssize_t +imap4_fill (CamelIMAP4Stream *imap4) +{ + unsigned char *inbuf, *inptr, *inend; + ssize_t nread; + size_t inlen; + + if (imap4->disconnected) { + errno = EINVAL; + return -1; + } + + inbuf = imap4->inbuf; + inptr = imap4->inptr; + inend = imap4->inend; + inlen = inend - inptr; + + g_assert (inptr <= inend); + + /* attempt to align 'inend' with realbuf + SCAN_HEAD */ + if (inptr >= inbuf) { + inbuf -= inlen < IMAP4_READ_PRELEN ? inlen : IMAP4_READ_PRELEN; + memmove (inbuf, inptr, inlen); + inptr = inbuf; + inbuf += inlen; + } else if (inptr > imap4->realbuf) { + size_t shift; + + shift = MIN (inptr - imap4->realbuf, inend - inbuf); + memmove (inptr - shift, inptr, inlen); + inptr -= shift; + inbuf = inptr + inlen; + } else { + /* we can't shift... */ + inbuf = inend; + } + + imap4->inptr = inptr; + imap4->inend = inbuf; + inend = imap4->realbuf + IMAP4_READ_PRELEN + IMAP4_READ_BUFLEN - 1; + + if ((nread = camel_stream_read (imap4->stream, inbuf, inend - inbuf)) == -1) + return -1; + else if (nread == 0) + imap4->disconnected = TRUE; + + imap4->inend += nread; + + return imap4->inend - imap4->inptr; +} + +static ssize_t +stream_read (CamelStream *stream, char *buffer, size_t n) +{ + CamelIMAP4Stream *imap4 = (CamelIMAP4Stream *) stream; + ssize_t len, nread = 0; + + if (imap4->mode == CAMEL_IMAP4_STREAM_MODE_LITERAL) { + /* don't let our caller read past the end of the literal */ + n = MIN (n, imap4->literal); + } + + if (imap4->inptr < imap4->inend) { + len = MIN (n, imap4->inend - imap4->inptr); + memcpy (buffer, imap4->inptr, len); + imap4->inptr += len; + nread = len; + } + + if (nread < n) { + if ((len = camel_stream_read (imap4->stream, buffer + nread, n - nread)) == 0) + imap4->disconnected = TRUE; + else if (len == -1) + return -1; + + nread += len; + } + + if (imap4->mode == CAMEL_IMAP4_STREAM_MODE_LITERAL) { + imap4->literal -= nread; + + if (imap4->literal == 0) { + imap4->mode = CAMEL_IMAP4_STREAM_MODE_TOKEN; + imap4->eol = TRUE; + } + } + + return nread; +} + +static ssize_t +stream_write (CamelStream *stream, const char *buffer, size_t n) +{ + CamelIMAP4Stream *imap4 = (CamelIMAP4Stream *) stream; + ssize_t nwritten; + + if (imap4->disconnected) { + errno = EINVAL; + return -1; + } + + if ((nwritten = camel_stream_write (imap4->stream, buffer, n)) == 0) + imap4->disconnected = TRUE; + + return nwritten; +} + +static int +stream_flush (CamelStream *stream) +{ + CamelIMAP4Stream *imap4 = (CamelIMAP4Stream *) stream; + + return camel_stream_flush (imap4->stream); +} + +static int +stream_close (CamelStream *stream) +{ + CamelIMAP4Stream *imap4 = (CamelIMAP4Stream *) stream; + + if (camel_stream_close (imap4->stream) == -1) + return -1; + + camel_object_unref (imap4->stream); + imap4->stream = NULL; + + imap4->disconnected = TRUE; + + return 0; +} + +static gboolean +stream_eos (CamelStream *stream) +{ + CamelIMAP4Stream *imap4 = (CamelIMAP4Stream *) stream; + + if (imap4->eol) + return TRUE; + + if (imap4->disconnected && imap4->inptr == imap4->inend) + return TRUE; + + if (camel_stream_eos (imap4->stream)) + return TRUE; + + return FALSE; +} + + +/** + * camel_imap4_stream_new: + * @stream: tcp stream + * + * Returns a new imap4 stream + **/ +CamelStream * +camel_imap4_stream_new (CamelStream *stream) +{ + CamelIMAP4Stream *imap4; + + g_return_val_if_fail (CAMEL_IS_STREAM (stream), NULL); + + imap4 = (CamelIMAP4Stream *) camel_object_new (CAMEL_TYPE_IMAP4_STREAM); + camel_object_ref (stream); + imap4->stream = stream; + + return (CamelStream *) imap4; +} + + + +#define token_save(imap4, start, len) G_STMT_START { \ + if (imap4->tokenleft <= len) { \ + unsigned int tlen, toff; \ + \ + tlen = toff = imap4->tokenptr - imap4->tokenbuf; \ + tlen = tlen ? tlen : 1; \ + \ + while (tlen < toff + len) \ + tlen <<= 1; \ + \ + imap4->tokenbuf = g_realloc (imap4->tokenbuf, tlen + 1); \ + imap4->tokenptr = imap4->tokenbuf + toff; \ + imap4->tokenleft = tlen - toff; \ + } \ + \ + memcpy (imap4->tokenptr, start, len); \ + imap4->tokenptr += len; \ + imap4->tokenleft -= len; \ +} G_STMT_END + +#define token_clear(imap4) G_STMT_START { \ + imap4->tokenleft += imap4->tokenptr - imap4->tokenbuf; \ + imap4->tokenptr = imap4->tokenbuf; \ + imap4->literal = 0; \ +} G_STMT_END + + +/** + * camel_imap4_stream_next_token: + * @stream: imap4 stream + * @token: imap4 token + * + * Reads the next token from the imap4 stream and saves it in @token. + * + * Returns 0 on success or -1 on fail. + **/ +int +camel_imap4_stream_next_token (CamelIMAP4Stream *stream, camel_imap4_token_t *token) +{ + register unsigned char *inptr; + unsigned char *inend, *start, *p; + gboolean escaped = FALSE; + size_t literal = 0; + guint32 nz_number; + int ret; + + g_return_val_if_fail (CAMEL_IS_IMAP4_STREAM (stream), -1); + g_return_val_if_fail (stream->mode != CAMEL_IMAP4_STREAM_MODE_LITERAL, -1); + g_return_val_if_fail (token != NULL, -1); + + if (stream->unget) { + memcpy (token, stream->unget, sizeof (camel_imap4_token_t)); + g_free (stream->unget); + stream->unget = NULL; + return 0; + } + + token_clear (stream); + + inptr = stream->inptr; + inend = stream->inend; + *inend = '\0'; + + do { + if (inptr == inend) { + if ((ret = imap4_fill (stream)) < 0) { + token->token = CAMEL_IMAP4_TOKEN_ERROR; + return -1; + } else if (ret == 0) { + token->token = CAMEL_IMAP4_TOKEN_NO_DATA; + return 0; + } + + inptr = stream->inptr; + inend = stream->inend; + *inend = '\0'; + } + + while (*inptr == ' ' || *inptr == '\r') + inptr++; + } while (inptr == inend); + + do { + if (inptr < inend) { + if (*inptr == '"') { + /* qstring token */ + escaped = FALSE; + start = inptr; + + /* eat the beginning " */ + inptr++; + + p = inptr; + while (inptr < inend) { + if (*inptr == '"' && !escaped) + break; + + if (*inptr == '\\' && !escaped) { + token_save (stream, p, inptr - p); + escaped = TRUE; + inptr++; + p = inptr; + } else { + inptr++; + escaped = FALSE; + } + } + + token_save (stream, p, inptr - p); + + if (inptr == inend) { + stream->inptr = start; + goto refill; + } + + /* eat the ending " */ + inptr++; + + /* nul-terminate the atom token */ + token_save (stream, "", 1); + + token->token = CAMEL_IMAP4_TOKEN_QSTRING; + token->v.qstring = stream->tokenbuf; + + d(fprintf (stderr, "token: \"%s\"\n", token->v.qstring)); + + break; + } else if (strchr ("+*()[]\n", *inptr)) { + /* special character token */ + token->token = *inptr++; +#if d(!)0 + if (token->token != '\n') + fprintf (stderr, "token: %c\n", token->token); + else + fprintf (stderr, "token: \\n\n"); +#endif + break; + } else if (*inptr == '{') { + /* literal identifier token */ + if ((p = strchr (inptr, '}')) && strchr (p, '\n')) { + inptr++; + + while (isdigit ((int) *inptr) && literal < UINT_MAX / 10) + literal = (literal * 10) + (*inptr++ - '0'); + + if (*inptr != '}') { + if (isdigit ((int) *inptr)) + g_warning ("illegal literal identifier: literal too large"); + else if (*inptr != '+') + g_warning ("illegal literal identifier: garbage following size"); + + while (*inptr != '}') + inptr++; + } + + /* skip over '}' */ + inptr++; + + /* skip over any trailing whitespace */ + while (*inptr == ' ' || *inptr == '\r') + inptr++; + + if (*inptr != '\n') { + g_warning ("illegal token following literal identifier: %s", inptr); + + /* skip ahead to the eoln */ + inptr = strchr (inptr, '\n'); + } + + /* skip over '\n' */ + inptr++; + + token->token = CAMEL_IMAP4_TOKEN_LITERAL; + token->v.literal = literal; + + d(fprintf (stderr, "token: {%u}\n", literal)); + + stream->mode = CAMEL_IMAP4_STREAM_MODE_LITERAL; + stream->literal = literal; + stream->eol = FALSE; + + break; + } else { + stream->inptr = inptr; + goto refill; + } + } else if (*inptr >= '0' && *inptr <= '9') { + /* number token */ + *inend = '\0'; + nz_number = strtoul ((char *) inptr, (char **) &start, 10); + if (start == inend) + goto refill; + + if (*start == ':' || *start == ',') { + /* workaround for 'set' tokens (APPENDUID / COPYUID) */ + goto atom_token; + } + + inptr = start; + token->token = CAMEL_IMAP4_TOKEN_NUMBER; + token->v.number = nz_number; + + d(fprintf (stderr, "token: %u\n", nz_number)); + + break; + } else if (is_atom (*inptr)) { + atom_token: + /* simple atom token */ + start = inptr; + + while (inptr < inend && is_atom (*inptr)) + inptr++; + + if (inptr == inend) { + stream->inptr = start; + goto refill; + } + + token_save (stream, start, inptr - start); + + /* nul-terminate the atom token */ + token_save (stream, "", 1); + + if (!strcmp (stream->tokenbuf, "NIL")) { + /* special atom token */ + token->token = CAMEL_IMAP4_TOKEN_NIL; + d(fprintf (stderr, "token: NIL\n")); + } else { + token->token = CAMEL_IMAP4_TOKEN_ATOM; + token->v.atom = stream->tokenbuf; + d(fprintf (stderr, "token: %s\n", token->v.atom)); + } + + break; + } else if (*inptr == '\\') { + /* possible flag token ("\" atom) */ + start = inptr++; + + while (inptr < inend && is_atom (*inptr)) + inptr++; + + if (inptr == inend) { + stream->inptr = start; + goto refill; + } + + if ((inptr - start) > 1) { + token_save (stream, start, inptr - start); + + /* nul-terminate the flag token */ + token_save (stream, "", 1); + + token->token = CAMEL_IMAP4_TOKEN_FLAG; + token->v.atom = stream->tokenbuf; + d(fprintf (stderr, "token: %s\n", token->v.atom)); + } else { + token->token = '\\'; + d(fprintf (stderr, "token: %c\n", token->token)); + } + break; + } else if (is_lwsp (*inptr)) { + inptr++; + } else { + /* unknown character token? */ + token->token = *inptr++; + d(fprintf (stderr, "token: %c\n", token->token)); + break; + } + } else { + refill: + token_clear (stream); + + if (imap4_fill (stream) <= 0) { + token->token = CAMEL_IMAP4_TOKEN_ERROR; + return -1; + } + + inptr = stream->inptr; + inend = stream->inend; + *inend = '\0'; + } + } while (inptr < inend); + + stream->inptr = inptr; + + return 0; +} + + +/** + * camel_imap4_stream_unget_token: + * @stream: imap4 stream + * @token: token to 'unget' + * + * Ungets an imap4 token (as in ungetc()). + * + * Note: you may *ONLY* unget a single token. Trying to unget another + * token will fail. + * + * Returns 0 on success or -1 on fail. + **/ +int +camel_imap4_stream_unget_token (CamelIMAP4Stream *stream, camel_imap4_token_t *token) +{ + camel_imap4_token_t *unget; + + if (stream->unget) + return -1; + + if (token->token != CAMEL_IMAP4_TOKEN_NO_DATA) { + stream->unget = unget = g_new (camel_imap4_token_t, 1); + memcpy (unget, token, sizeof (camel_imap4_token_t)); + } + + return 0; +} + + +/** + * camel_imap4_stream_readline: + * @stream: imap4 stream + * @line: line pointer + * @len: line length + * + * Reads a single line from the imap4 stream and points @line at an + * internal buffer containing the line read and sets @len to the + * length of the line buffer. + * + * Returns -1 on error, 0 if the line read is complete, or 1 if the + * read is incomplete. + **/ +int +camel_imap4_stream_line (CamelIMAP4Stream *stream, unsigned char **line, size_t *len) +{ + register unsigned char *inptr; + unsigned char *inend; + + g_return_val_if_fail (CAMEL_IS_IMAP4_STREAM (stream), -1); + g_return_val_if_fail (stream->mode != CAMEL_IMAP4_STREAM_MODE_LITERAL, -1); + g_return_val_if_fail (line != NULL, -1); + g_return_val_if_fail (len != NULL, -1); + + if ((stream->inend - stream->inptr) < 3) { + /* keep our buffer full to the optimal size */ + if (imap4_fill (stream) == -1 && stream->inptr == stream->inend) + return -1; + } + + *line = stream->inptr; + inptr = stream->inptr; + inend = stream->inend; + *inend = '\n'; + + while (*inptr != '\n') + inptr++; + + *len = (inptr - stream->inptr); + if (inptr < inend) { + /* got the eoln */ + if (inptr > stream->inptr && inptr[-1] == '\r') + inptr[-1] = '\0'; + else + inptr[0] = '\0'; + + stream->inptr = inptr + 1; + *len += 1; + + return 0; + } + + stream->inptr = inptr; + + return 1; +} + + +int +camel_imap4_stream_literal (CamelIMAP4Stream *stream, unsigned char **literal, size_t *len) +{ + unsigned char *inptr, *inend; + size_t nread; + + g_return_val_if_fail (CAMEL_IS_IMAP4_STREAM (stream), -1); + g_return_val_if_fail (stream->mode == CAMEL_IMAP4_STREAM_MODE_LITERAL, -1); + g_return_val_if_fail (literal != NULL, -1); + g_return_val_if_fail (len != NULL, -1); + + if (stream->eol) { + *len = 0; + return 0; + } + + if ((stream->inend - stream->inptr) < 1) { + /* keep our buffer full to the optimal size */ + if (imap4_fill (stream) == -1 && stream->inptr == stream->inend) + return -1; + } + + *literal = inptr = stream->inptr; + inend = stream->inend; + if ((inend - inptr) > stream->literal) + inend = inptr + stream->literal; + else + inend = stream->inend; + + *len = nread = inend - inptr; + + stream->literal -= nread; + if (stream->literal == 0) { + stream->mode = CAMEL_IMAP4_STREAM_MODE_TOKEN; + stream->eol = TRUE; + return 0; + } + + return 1; +} |