1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
4 * Michael Zucchi <notzed@ximian.com>
6 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of version 2 of the GNU Lesser General Public
10 * License as published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
32 #include <glib/gi18n-lib.h>
34 #include <camel/camel-stream-mem.h>
36 #include "camel-imapx-utils.h"
37 #include "camel-imapx-stream.h"
39 #define t(...) camel_imapx_debug(token, __VA_ARGS__)
40 #define io(...) camel_imapx_debug(io, __VA_ARGS__)
42 G_DEFINE_TYPE (CamelIMAPXStream, camel_imapx_stream, CAMEL_TYPE_STREAM)
45 imapx_stream_fill (CamelIMAPXStream *is,
46 GCancellable *cancellable,
52 left = is->end - is->ptr;
53 memcpy (is->buf, is->ptr, left);
54 is->end = is->buf + left;
56 left = camel_stream_read (
57 is->source, (gchar *) is->end,
58 is->bufsize - (is->end - is->buf),
62 io(is->tagprefix, "camel_imapx_read: buffer is '%.*s'\n", (gint)(is->end - is->ptr), is->ptr);
63 return is->end - is->ptr;
65 io(is->tagprefix, "camel_imapx_read: -1\n");
66 /* If returning zero, camel_stream_read() doesn't consider
67 * that to be an error. But we do -- we should only be here
68 * if we *know* there are data to receive. So set the error
71 g_set_error (error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
72 _("Source stream returned no data"));
77 io(is->tagprefix, "camel_imapx_read: -1\n");
80 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
81 _("Source stream unavailable"));
87 imapx_stream_dispose (GObject *object)
89 CamelIMAPXStream *stream = CAMEL_IMAPX_STREAM (object);
91 if (stream->source != NULL) {
92 g_object_unref (stream->source);
93 stream->source = NULL;
96 /* Chain up to parent's dispose() method. */
97 G_OBJECT_CLASS (camel_imapx_stream_parent_class)->dispose (object);
101 imapx_stream_finalize (GObject *object)
103 CamelIMAPXStream *stream = CAMEL_IMAPX_STREAM (object);
105 g_free (stream->buf);
106 g_free (stream->tokenbuf);
108 /* Chain up to parent's finalize() method. */
109 G_OBJECT_CLASS (camel_imapx_stream_parent_class)->finalize (object);
113 imapx_stream_read (CamelStream *stream,
116 GCancellable *cancellable,
119 CamelIMAPXStream *is = (CamelIMAPXStream *) stream;
122 if (is->literal == 0 || n == 0)
125 max = is->end - is->ptr;
127 max = MIN (max, is->literal);
129 memcpy (buffer, is->ptr, max);
132 max = MIN (is->literal, n);
133 max = camel_stream_read (is->source, buffer, max, cancellable, error);
138 io(is->tagprefix, "camel_imapx_read(literal): '%.*s'\n", (gint)max, buffer);
146 imapx_stream_write (CamelStream *stream,
149 GCancellable *cancellable,
152 CamelIMAPXStream *is = (CamelIMAPXStream *) stream;
154 if (g_strstr_len (buffer, n, "LOGIN")) {
155 io(is->tagprefix, "camel_imapx_write: 'LOGIN...'\n");
157 io(is->tagprefix, "camel_imapx_write: '%.*s'\n", (gint)n, buffer);
160 return camel_stream_write (is->source, buffer, n, cancellable, error);
164 imapx_stream_close (CamelStream *stream,
165 GCancellable *cancellable,
173 imapx_stream_flush (CamelStream *stream,
174 GCancellable *cancellable,
182 imapx_stream_eos (CamelStream *stream)
184 CamelIMAPXStream *is = (CamelIMAPXStream *) stream;
186 return is->literal == 0;
190 camel_imapx_stream_class_init (CamelIMAPXStreamClass *class)
192 GObjectClass *object_class;
193 CamelStreamClass *stream_class;
195 object_class = G_OBJECT_CLASS (class);
196 object_class->dispose = imapx_stream_dispose;
197 object_class->finalize = imapx_stream_finalize;
199 stream_class = CAMEL_STREAM_CLASS (class);
200 stream_class->read = imapx_stream_read;
201 stream_class->write = imapx_stream_write;
202 stream_class->close = imapx_stream_close;
203 stream_class->flush = imapx_stream_flush;
204 stream_class->eos = imapx_stream_eos;
208 camel_imapx_stream_init (CamelIMAPXStream *is)
210 /* +1 is room for appending a 0 if we need to for a token */
212 is->ptr = is->end = is->buf = g_malloc (is->bufsize + 1);
213 is->tokenbuf = g_malloc (is->bufsize + 1);
216 static void camel_imapx_stream_grow (CamelIMAPXStream *is, guint len, guchar **bufptr, guchar **tokptr)
218 guchar *oldtok = is->tokenbuf;
219 guchar *oldbuf = is->buf;
223 } while (is->bufsize <= len);
225 io(is->tagprefix, "Grow imapx buffers to %d bytes\n", is->bufsize);
227 is->tokenbuf = g_realloc (is->tokenbuf, is->bufsize + 1);
229 *tokptr = is->tokenbuf + (*tokptr - oldtok);
231 is->unget_token = is->tokenbuf + (is->unget_token - oldtok);
233 //io(is->tagprefix, "buf was %p, ptr %p end %p\n", is->buf, is->ptr, is->end);
234 is->buf = g_realloc (is->buf, is->bufsize + 1);
235 is->ptr = is->buf + (is->ptr - oldbuf);
236 is->end = is->buf + (is->end - oldbuf);
237 //io(is->tagprefix, "buf now %p, ptr %p end %p\n", is->buf, is->ptr, is->end);
239 *bufptr = is->buf + (*bufptr - oldbuf);
243 * camel_imapx_stream_new:
245 * Returns a NULL stream. A null stream is always at eof, and
246 * always returns success for all reads and writes.
248 * Returns: the stream
251 camel_imapx_stream_new (CamelStream *source)
253 CamelIMAPXStream *is;
255 is = g_object_new (CAMEL_TYPE_IMAPX_STREAM, NULL);
256 is->source = g_object_ref (source);
258 return (CamelStream *) is;
262 camel_imapx_error_quark (void)
264 static GQuark quark = 0;
266 if (G_UNLIKELY (quark == 0)) {
267 const gchar *string = "camel-imapx-error-quark";
268 quark = g_quark_from_static_string (string);
274 /* Returns if there is any data buffered that is ready for processing */
276 camel_imapx_stream_buffered (CamelIMAPXStream *is)
278 return is->end - is->ptr;
284 skip_ws (CamelIMAPXStream *is,
288 register guchar c, *p;
297 if (imapx_stream_fill (is, NULL) == IMAPX_TOK_ERROR)
298 return IMAPX_TOK_ERROR;
303 } while (c == ' ' || c == '\r');
312 /* FIXME: these should probably handle it themselves,
313 * and get rid of the token interface? */
315 camel_imapx_stream_atom (CamelIMAPXStream *is,
318 GCancellable *cancellable,
322 GError *local_error = NULL;
324 /* this is only 'approximate' atom */
325 switch (camel_imapx_stream_token (is, data, lenp, cancellable, &local_error)) {
326 case IMAPX_TOK_TOKEN:
332 case IMAPX_TOK_ERROR:
333 if (local_error != NULL)
334 g_propagate_error (error, local_error);
335 return IMAPX_TOK_ERROR;
337 if (local_error == NULL)
338 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting atom");
340 g_propagate_error (error, local_error);
341 io(is->tagprefix, "expecting atom!\n");
342 return IMAPX_TOK_PROTOCOL;
346 /* gets an atom, a quoted_string, or a literal */
348 camel_imapx_stream_astring (CamelIMAPXStream *is,
350 GCancellable *cancellable,
356 GError *local_error = NULL;
358 switch (camel_imapx_stream_token (is, data, &len, cancellable, &local_error)) {
359 case IMAPX_TOK_TOKEN:
361 case IMAPX_TOK_STRING:
363 case IMAPX_TOK_LITERAL:
364 if (len >= is->bufsize)
365 camel_imapx_stream_grow (is, len, NULL, NULL);
367 camel_imapx_stream_set_literal (is, len);
369 ret = camel_imapx_stream_getl (is, &start, &inlen, cancellable, error);
372 memcpy (p, start, inlen);
376 *data = is->tokenbuf;
378 case IMAPX_TOK_ERROR:
379 /* wont get unless no exception hanlder*/
380 if (local_error != NULL)
381 g_propagate_error (error, local_error);
382 return IMAPX_TOK_ERROR;
384 if (local_error == NULL)
385 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting astring");
387 g_propagate_error (error, local_error);
388 io(is->tagprefix, "expecting astring!\n");
389 return IMAPX_TOK_PROTOCOL;
393 /* check for NIL or (small) quoted_string or literal */
395 camel_imapx_stream_nstring (CamelIMAPXStream *is,
397 GCancellable *cancellable,
403 GError *local_error = NULL;
405 switch (camel_imapx_stream_token (is, data, &len, cancellable, &local_error)) {
406 case IMAPX_TOK_STRING:
408 case IMAPX_TOK_LITERAL:
409 if (len >= is->bufsize)
410 camel_imapx_stream_grow (is, len, NULL, NULL);
412 camel_imapx_stream_set_literal (is, len);
414 ret = camel_imapx_stream_getl (is, &start, &inlen, cancellable, error);
417 memcpy (p, start, inlen);
421 *data = is->tokenbuf;
423 case IMAPX_TOK_TOKEN:
425 if (toupper (p[0]) == 'N' && toupper (p[1]) == 'I' && toupper (p[2]) == 'L' && p[3] == 0) {
430 if (local_error == NULL)
431 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting nstring");
433 g_propagate_error (error, local_error);
434 return IMAPX_TOK_PROTOCOL;
435 case IMAPX_TOK_ERROR:
436 /* we'll never get this unless there are no exception handlers anyway */
437 if (local_error != NULL)
438 g_propagate_error (error, local_error);
439 return IMAPX_TOK_ERROR;
444 /* parse an nstring as a stream */
446 camel_imapx_stream_nstring_stream (CamelIMAPXStream *is,
447 CamelStream **stream,
448 GCancellable *cancellable,
450 /* throws IO,PARSE exception */
455 CamelStream * mem = NULL;
456 GError *local_error = NULL;
460 switch (camel_imapx_stream_token (is, &token, &len, cancellable, &local_error)) {
461 case IMAPX_TOK_STRING:
462 mem = camel_stream_mem_new_with_buffer ((gchar *) token, len);
465 case IMAPX_TOK_LITERAL:
466 /* if len is big, we could automatically use a file backing */
467 camel_imapx_stream_set_literal (is, len);
468 mem = camel_stream_mem_new ();
469 if (camel_stream_write_to_stream ((CamelStream *) is, mem, cancellable, error) == -1) {
470 g_object_unref (mem);
477 G_SEEK_SET, NULL, NULL);
481 case IMAPX_TOK_TOKEN:
482 if (toupper (token[0]) == 'N' && toupper (token[1]) == 'I' && toupper (token[2]) == 'L' && token[3] == 0) {
488 if (local_error == NULL)
489 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "nstring: token not string");
491 g_propagate_error (error, local_error);
498 camel_imapx_stream_number (CamelIMAPXStream *is,
499 GCancellable *cancellable,
504 GError *local_error = NULL;
506 if (camel_imapx_stream_token (is, &token, &len, cancellable, &local_error) != IMAPX_TOK_INT) {
507 if (local_error == NULL)
508 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting number");
510 g_propagate_error (error, local_error);
514 return strtoull ((gchar *) token, 0, 10);
518 camel_imapx_stream_text (CamelIMAPXStream *is,
520 GCancellable *cancellable,
523 GByteArray *build = g_byte_array_new ();
528 while (is->unget > 0) {
529 switch (is->unget_tok) {
530 case IMAPX_TOK_TOKEN:
531 case IMAPX_TOK_STRING:
533 g_byte_array_append (build, (guint8 *) is->unget_token, is->unget_len);
534 g_byte_array_append(build, (guint8 *) " ", 1);
535 default: /* invalid, but we'll ignore */
542 tok = camel_imapx_stream_gets (is, &token, &len, cancellable, error);
545 g_byte_array_free (build, TRUE);
549 g_byte_array_append (build, token, len);
552 g_byte_array_append(build, (guint8 *) "", 1);
554 g_byte_array_free (build, FALSE);
559 /* Get one token from the imap stream */
561 /* throws IO,PARSE exception */
562 camel_imapx_stream_token (CamelIMAPXStream *is,
565 GCancellable *cancellable,
568 register guchar c, *oe;
575 *data = is->unget_token;
576 *len = is->unget_len;
577 /*printf("token UNGET '%c' %s\n", is->unget_tok, is->unget_token);*/
578 return is->unget_tok;
582 g_warning("stream_token called with literal %d", is->literal);
587 /* skip whitespace/prefill buffer */
591 if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
592 return IMAPX_TOK_ERROR;
597 } while (c == ' ' || c == '\r');
599 /*strchr("\n*()[]+", c)*/
600 if (imapx_is_token_char (c)) {
602 t(is->tagprefix, "token '%c'\n", c);
604 } else if (c == '{') {
610 if (isdigit (c) && literal < (UINT_MAX / 10)) {
611 literal = literal * 10 + (c - '0');
612 } else if (c == '}') {
619 is->literal = literal;
620 t(is->tagprefix, "token LITERAL %d\n", literal);
621 return IMAPX_TOK_LITERAL;
625 if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
626 return IMAPX_TOK_ERROR;
632 io(is->tagprefix, "Protocol error: literal too big\n");
634 io(is->tagprefix, "Protocol error: literal contains invalid gchar %02x '%c'\n", c, isprint(c)?c:c);
640 if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
641 return IMAPX_TOK_ERROR;
645 } else if (c == '"') {
647 oe = is->tokenbuf + is->bufsize - 1;
654 if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
655 return IMAPX_TOK_ERROR;
660 } else if (c == '\"') {
663 *data = is->tokenbuf;
664 *len = o - is->tokenbuf;
665 t(is->tagprefix, "token STRING '%s'\n", is->tokenbuf);
666 return IMAPX_TOK_STRING;
668 if (c == '\n' || c == '\r') {
669 io(is->tagprefix, "Protocol error: truncated string\n");
673 camel_imapx_stream_grow (is, 0, &p, &o);
674 oe = is->tokenbuf + is->bufsize - 1;
680 if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
681 return IMAPX_TOK_ERROR;
687 oe = is->tokenbuf + is->bufsize - 1;
688 digits = isdigit (c);
693 /*if (strchr(" \r\n*()[]+", c) != NULL) {*/
694 if (imapx_is_notid_char (c)) {
695 if (c == ' ' || c == '\r')
700 *data = is->tokenbuf;
701 *len = o - is->tokenbuf;
702 t(is->tagprefix, "token TOKEN '%s'\n", is->tokenbuf);
703 return digits ? IMAPX_TOK_INT : IMAPX_TOK_TOKEN;
707 camel_imapx_stream_grow (is, 0, &p, &o);
708 oe = is->tokenbuf + is->bufsize - 1;
711 digits &= isdigit (c);
715 if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
716 return IMAPX_TOK_ERROR;
722 /* Protocol error, skip until next lf? */
724 io(is->tagprefix, "Got protocol error\n");
731 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "protocol error");
732 return IMAPX_TOK_PROTOCOL;
736 camel_imapx_stream_ungettoken (CamelIMAPXStream *is,
737 camel_imapx_token_t tok,
741 /*printf("ungettoken: '%c' '%s'\n", tok, token);*/
743 is->unget_token = token;
748 /* returns -1 on error, 0 if last lot of data, >0 if more remaining */
750 camel_imapx_stream_gets (CamelIMAPXStream *is,
753 GCancellable *cancellable,
761 max = is->end - is->ptr;
763 max = imapx_stream_fill (is, cancellable, error);
769 end = memchr (is->ptr, '\n', max);
771 max = (end - is->ptr) + 1;
776 return end == NULL ? 1 : 0;
779 void camel_imapx_stream_set_literal (CamelIMAPXStream *is, guint literal)
781 is->literal = literal;
784 /* returns -1 on erorr, 0 if last data, >0 if more data left */
786 camel_imapx_stream_getl (CamelIMAPXStream *is,
789 GCancellable *cancellable,
796 if (is->literal > 0) {
797 max = is->end - is->ptr;
799 max = imapx_stream_fill (is, cancellable, error);
804 max = MIN (max, is->literal);
817 /* skip the rest of the line of tokens */
819 camel_imapx_stream_skip (CamelIMAPXStream *is,
820 GCancellable *cancellable,
828 tok = camel_imapx_stream_token (is, &token, &len, cancellable, error);
829 if (tok == IMAPX_TOK_LITERAL) {
830 camel_imapx_stream_set_literal (is, len);
831 while ((tok = camel_imapx_stream_getl (is, &token, &len, cancellable, error)) > 0) {
832 io(is->tagprefix, "Skip literal data '%.*s'\n", (gint)len, token);
835 } while (tok != '\n' && tok >= 0);