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 CAMEL_IMAPX_STREAM_GET_PRIVATE(obj) \
40 (G_TYPE_INSTANCE_GET_PRIVATE \
41 ((obj), CAMEL_TYPE_IMAPX_STREAM, CamelIMAPXStreamPrivate))
43 #define t(...) camel_imapx_debug(token, __VA_ARGS__)
44 #define io(...) camel_imapx_debug(io, __VA_ARGS__)
46 struct _CamelIMAPXStreamPrivate {
49 guchar *buf, *ptr, *end;
53 camel_imapx_token_t unget_tok;
66 G_DEFINE_TYPE (CamelIMAPXStream, camel_imapx_stream, CAMEL_TYPE_STREAM)
69 imapx_stream_fill (CamelIMAPXStream *is,
70 GCancellable *cancellable,
75 if (is->priv->source != NULL) {
76 left = is->priv->end - is->priv->ptr;
77 memcpy (is->priv->buf, is->priv->ptr, left);
78 is->priv->end = is->priv->buf + left;
79 is->priv->ptr = is->priv->buf;
80 left = camel_stream_read (
82 (gchar *) is->priv->end,
83 is->priv->bufsize - (is->priv->end - is->priv->buf),
86 is->priv->end += left;
87 io (is->tagprefix, "camel_imapx_read: buffer is '%.*s'\n", (gint)(is->priv->end - is->priv->ptr), is->priv->ptr);
88 return is->priv->end - is->priv->ptr;
90 io (is->tagprefix, "camel_imapx_read: -1\n");
91 /* If returning zero, camel_stream_read() doesn't consider
92 * that to be an error. But we do -- we should only be here
93 * if we *know* there are data to receive. So set the error
97 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
98 _("Source stream returned no data"));
103 io (is->tagprefix, "camel_imapx_read: -1\n");
106 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
107 _("Source stream unavailable"));
113 imapx_stream_set_source (CamelIMAPXStream *stream,
116 g_return_if_fail (CAMEL_IS_STREAM (source));
117 g_return_if_fail (stream->priv->source == NULL);
119 stream->priv->source = g_object_ref (source);
123 imapx_stream_set_property (GObject *object,
128 switch (property_id) {
130 imapx_stream_set_source (
131 CAMEL_IMAPX_STREAM (object),
132 g_value_get_object (value));
136 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
140 imapx_stream_get_property (GObject *object,
145 switch (property_id) {
147 g_value_take_object (
149 camel_imapx_stream_ref_source (
150 CAMEL_IMAPX_STREAM (object)));
154 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
158 imapx_stream_dispose (GObject *object)
160 CamelIMAPXStream *stream = CAMEL_IMAPX_STREAM (object);
162 if (stream->priv->source != NULL) {
163 g_object_unref (stream->priv->source);
164 stream->priv->source = NULL;
167 /* Chain up to parent's dispose() method. */
168 G_OBJECT_CLASS (camel_imapx_stream_parent_class)->dispose (object);
172 imapx_stream_finalize (GObject *object)
174 CamelIMAPXStream *stream = CAMEL_IMAPX_STREAM (object);
176 g_free (stream->priv->buf);
177 g_free (stream->priv->tokenbuf);
179 /* Chain up to parent's finalize() method. */
180 G_OBJECT_CLASS (camel_imapx_stream_parent_class)->finalize (object);
184 imapx_stream_read (CamelStream *stream,
187 GCancellable *cancellable,
190 CamelIMAPXStream *is = (CamelIMAPXStream *) stream;
193 if (is->priv->literal == 0 || n == 0)
196 max = is->priv->end - is->priv->ptr;
198 max = MIN (max, is->priv->literal);
200 memcpy (buffer, is->priv->ptr, max);
201 is->priv->ptr += max;
203 max = MIN (is->priv->literal, n);
204 max = camel_stream_read (
206 buffer, max, cancellable, error);
211 io (is->tagprefix, "camel_imapx_read(literal): '%.*s'\n", (gint) max, buffer);
213 is->priv->literal -= max;
219 imapx_stream_write (CamelStream *stream,
222 GCancellable *cancellable,
225 CamelIMAPXStream *is = (CamelIMAPXStream *) stream;
227 if (g_strstr_len (buffer, n, "LOGIN")) {
228 io (is->tagprefix, "camel_imapx_write: 'LOGIN...'\n");
230 io (is->tagprefix, "camel_imapx_write: '%.*s'\n", (gint) n, buffer);
233 return camel_stream_write (
235 buffer, n, cancellable, error);
239 imapx_stream_close (CamelStream *stream,
240 GCancellable *cancellable,
243 CamelIMAPXStream *is = (CamelIMAPXStream *) stream;
245 return camel_stream_close (is->priv->source, cancellable, error);
249 imapx_stream_flush (CamelStream *stream,
250 GCancellable *cancellable,
258 imapx_stream_eos (CamelStream *stream)
260 CamelIMAPXStream *is = (CamelIMAPXStream *) stream;
262 return is->priv->literal == 0;
266 camel_imapx_stream_class_init (CamelIMAPXStreamClass *class)
268 GObjectClass *object_class;
269 CamelStreamClass *stream_class;
271 g_type_class_add_private (class, sizeof (CamelIMAPXStreamPrivate));
273 object_class = G_OBJECT_CLASS (class);
274 object_class->set_property = imapx_stream_set_property;
275 object_class->get_property = imapx_stream_get_property;
276 object_class->dispose = imapx_stream_dispose;
277 object_class->finalize = imapx_stream_finalize;
279 stream_class = CAMEL_STREAM_CLASS (class);
280 stream_class->read = imapx_stream_read;
281 stream_class->write = imapx_stream_write;
282 stream_class->close = imapx_stream_close;
283 stream_class->flush = imapx_stream_flush;
284 stream_class->eos = imapx_stream_eos;
286 g_object_class_install_property (
289 g_param_spec_object (
295 G_PARAM_CONSTRUCT_ONLY |
296 G_PARAM_STATIC_STRINGS));
300 camel_imapx_stream_init (CamelIMAPXStream *is)
302 is->priv = CAMEL_IMAPX_STREAM_GET_PRIVATE (is);
304 /* +1 is room for appending a 0 if we need to for a token */
305 is->priv->bufsize = 4096;
306 is->priv->buf = g_malloc (is->priv->bufsize + 1);
307 is->priv->ptr = is->priv->end = is->priv->buf;
308 is->priv->tokenbuf = g_malloc (is->priv->bufsize + 1);
312 camel_imapx_stream_grow (CamelIMAPXStream *is,
317 guchar *oldtok = is->priv->tokenbuf;
318 guchar *oldbuf = is->priv->buf;
321 is->priv->bufsize <<= 1;
322 } while (is->priv->bufsize <= len);
324 io (is->tagprefix, "Grow imapx buffers to %d bytes\n", is->priv->bufsize);
326 is->priv->tokenbuf = g_realloc (
328 is->priv->bufsize + 1);
330 *tokptr = is->priv->tokenbuf + (*tokptr - oldtok);
332 is->priv->unget_token =
334 (is->priv->unget_token - oldtok);
336 is->priv->buf = g_realloc (is->priv->buf, is->priv->bufsize + 1);
337 is->priv->ptr = is->priv->buf + (is->priv->ptr - oldbuf);
338 is->priv->end = is->priv->buf + (is->priv->end - oldbuf);
340 *bufptr = is->priv->buf + (*bufptr - oldbuf);
344 camel_imapx_error_quark (void)
346 static GQuark quark = 0;
348 if (G_UNLIKELY (quark == 0)) {
349 const gchar *string = "camel-imapx-error-quark";
350 quark = g_quark_from_static_string (string);
357 * camel_imapx_stream_new:
359 * Returns a NULL stream. A null stream is always at eof, and
360 * always returns success for all reads and writes.
362 * Returns: the stream
365 camel_imapx_stream_new (CamelStream *source)
367 g_return_val_if_fail (CAMEL_IS_STREAM (source), NULL);
369 return g_object_new (
370 CAMEL_TYPE_IMAPX_STREAM,
371 "source", source, NULL);
375 camel_imapx_stream_ref_source (CamelIMAPXStream *is)
377 g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), NULL);
379 return g_object_ref (is->priv->source);
382 /* Returns if there is any data buffered that is ready for processing */
384 camel_imapx_stream_buffered (CamelIMAPXStream *is)
386 g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), 0);
388 return is->priv->end - is->priv->ptr;
391 /* FIXME: these should probably handle it themselves,
392 * and get rid of the token interface? */
394 camel_imapx_stream_atom (CamelIMAPXStream *is,
397 GCancellable *cancellable,
401 GError *local_error = NULL;
403 g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), IMAPX_TOK_ERROR);
404 g_return_val_if_fail (data != NULL, IMAPX_TOK_ERROR);
405 g_return_val_if_fail (lenp != NULL, IMAPX_TOK_ERROR);
407 /* this is only 'approximate' atom */
408 switch (camel_imapx_stream_token (is, data, lenp, cancellable, &local_error)) {
409 case IMAPX_TOK_TOKEN:
415 case IMAPX_TOK_ERROR:
416 if (local_error != NULL)
417 g_propagate_error (error, local_error);
418 return IMAPX_TOK_ERROR;
420 if (local_error == NULL)
421 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting atom");
423 g_propagate_error (error, local_error);
424 io (is->tagprefix, "expecting atom!\n");
425 return IMAPX_TOK_PROTOCOL;
429 /* gets an atom, a quoted_string, or a literal */
431 camel_imapx_stream_astring (CamelIMAPXStream *is,
433 GCancellable *cancellable,
439 GError *local_error = NULL;
441 g_return_val_if_fail (CAMEL_IMAPX_STREAM (is), IMAPX_TOK_ERROR);
442 g_return_val_if_fail (data != NULL, IMAPX_TOK_ERROR);
444 switch (camel_imapx_stream_token (is, data, &len, cancellable, &local_error)) {
445 case IMAPX_TOK_TOKEN:
447 case IMAPX_TOK_STRING:
449 case IMAPX_TOK_LITERAL:
450 if (len >= is->priv->bufsize)
451 camel_imapx_stream_grow (is, len, NULL, NULL);
452 p = is->priv->tokenbuf;
453 camel_imapx_stream_set_literal (is, len);
455 ret = camel_imapx_stream_getl (is, &start, &inlen, cancellable, error);
458 memcpy (p, start, inlen);
462 *data = is->priv->tokenbuf;
464 case IMAPX_TOK_ERROR:
465 /* wont get unless no exception hanlder*/
466 if (local_error != NULL)
467 g_propagate_error (error, local_error);
468 return IMAPX_TOK_ERROR;
470 if (local_error == NULL)
471 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting astring");
473 g_propagate_error (error, local_error);
474 io (is->tagprefix, "expecting astring!\n");
475 return IMAPX_TOK_PROTOCOL;
479 /* check for NIL or (small) quoted_string or literal */
481 camel_imapx_stream_nstring (CamelIMAPXStream *is,
483 GCancellable *cancellable,
489 GError *local_error = NULL;
491 g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), IMAPX_TOK_ERROR);
492 g_return_val_if_fail (data != NULL, IMAPX_TOK_ERROR);
494 switch (camel_imapx_stream_token (is, data, &len, cancellable, &local_error)) {
495 case IMAPX_TOK_STRING:
497 case IMAPX_TOK_LITERAL:
498 if (len >= is->priv->bufsize)
499 camel_imapx_stream_grow (is, len, NULL, NULL);
500 p = is->priv->tokenbuf;
501 camel_imapx_stream_set_literal (is, len);
503 ret = camel_imapx_stream_getl (is, &start, &inlen, cancellable, error);
506 memcpy (p, start, inlen);
510 *data = is->priv->tokenbuf;
512 case IMAPX_TOK_TOKEN:
514 if (toupper (p[0]) == 'N' && toupper (p[1]) == 'I' && toupper (p[2]) == 'L' && p[3] == 0) {
519 if (local_error == NULL)
520 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting nstring");
522 g_propagate_error (error, local_error);
523 return IMAPX_TOK_PROTOCOL;
524 case IMAPX_TOK_ERROR:
525 /* we'll never get this unless there are no exception handlers anyway */
526 if (local_error != NULL)
527 g_propagate_error (error, local_error);
528 return IMAPX_TOK_ERROR;
533 /* parse an nstring as a stream */
535 camel_imapx_stream_nstring_stream (CamelIMAPXStream *is,
536 CamelStream **stream,
537 GCancellable *cancellable,
543 CamelStream * mem = NULL;
544 GError *local_error = NULL;
546 g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), -1);
547 g_return_val_if_fail (stream != NULL, -1);
551 switch (camel_imapx_stream_token (is, &token, &len, cancellable, &local_error)) {
552 case IMAPX_TOK_STRING:
553 mem = camel_stream_mem_new_with_buffer ((gchar *) token, len);
556 case IMAPX_TOK_LITERAL:
557 /* if len is big, we could automatically use a file backing */
558 camel_imapx_stream_set_literal (is, len);
559 mem = camel_stream_mem_new ();
560 if (camel_stream_write_to_stream ((CamelStream *) is, mem, cancellable, error) == -1) {
561 g_object_unref (mem);
568 G_SEEK_SET, NULL, NULL);
572 case IMAPX_TOK_TOKEN:
573 if (toupper (token[0]) == 'N' && toupper (token[1]) == 'I' && toupper (token[2]) == 'L' && token[3] == 0) {
579 if (local_error == NULL)
580 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "nstring: token not string");
582 g_propagate_error (error, local_error);
589 camel_imapx_stream_number (CamelIMAPXStream *is,
590 GCancellable *cancellable,
595 GError *local_error = NULL;
597 g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), 0);
599 if (camel_imapx_stream_token (is, &token, &len, cancellable, &local_error) != IMAPX_TOK_INT) {
600 if (local_error == NULL)
601 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting number");
603 g_propagate_error (error, local_error);
607 return strtoull ((gchar *) token, 0, 10);
611 camel_imapx_stream_text (CamelIMAPXStream *is,
613 GCancellable *cancellable,
616 GByteArray *build = g_byte_array_new ();
621 g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), -1);
622 g_return_val_if_fail (text != NULL, -1);
624 while (is->priv->unget > 0) {
625 switch (is->priv->unget_tok) {
626 case IMAPX_TOK_TOKEN:
627 case IMAPX_TOK_STRING:
629 g_byte_array_append (
631 is->priv->unget_token,
632 is->priv->unget_len);
633 g_byte_array_append (
634 build, (guint8 *) " ", 1);
635 default: /* invalid, but we'll ignore */
642 tok = camel_imapx_stream_gets (is, &token, &len, cancellable, error);
645 g_byte_array_free (build, TRUE);
649 g_byte_array_append (build, token, len);
652 g_byte_array_append (build, (guint8 *) "", 1);
654 g_byte_array_free (build, FALSE);
659 /* Get one token from the imap stream */
661 camel_imapx_stream_token (CamelIMAPXStream *is,
664 GCancellable *cancellable,
667 register guchar c, *oe;
672 g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), IMAPX_TOK_ERROR);
673 g_return_val_if_fail (data != NULL, IMAPX_TOK_ERROR);
674 g_return_val_if_fail (len != NULL, IMAPX_TOK_ERROR);
676 if (is->priv->unget > 0) {
678 *data = is->priv->unget_token;
679 *len = is->priv->unget_len;
680 return is->priv->unget_tok;
683 if (is->priv->literal > 0)
685 "stream_token called with literal %d",
691 /* skip whitespace/prefill buffer */
695 if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
696 return IMAPX_TOK_ERROR;
701 } while (c == ' ' || c == '\r');
703 /*strchr("\n*()[]+", c)*/
704 if (imapx_is_token_char (c)) {
706 t (is->tagprefix, "token '%c'\n", c);
708 } else if (c == '{') {
714 if (isdigit (c) && literal < (UINT_MAX / 10)) {
715 literal = literal * 10 + (c - '0');
716 } else if (c == '}') {
723 is->priv->literal = literal;
724 t (is->tagprefix, "token LITERAL %d\n", literal);
725 return IMAPX_TOK_LITERAL;
729 if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
730 return IMAPX_TOK_ERROR;
736 io (is->tagprefix, "Protocol error: literal too big\n");
738 io (is->tagprefix, "Protocol error: literal contains invalid gchar %02x '%c'\n", c, isprint (c) ? c : c);
744 if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
745 return IMAPX_TOK_ERROR;
749 } else if (c == '"') {
750 o = is->priv->tokenbuf;
751 oe = is->priv->tokenbuf + is->priv->bufsize - 1;
758 if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
759 return IMAPX_TOK_ERROR;
764 } else if (c == '\"') {
767 *data = is->priv->tokenbuf;
768 *len = o - is->priv->tokenbuf;
769 t (is->tagprefix, "token STRING '%s'\n", is->priv->tokenbuf);
770 return IMAPX_TOK_STRING;
772 if (c == '\n' || c == '\r') {
773 io (is->tagprefix, "Protocol error: truncated string\n");
777 camel_imapx_stream_grow (is, 0, &p, &o);
778 oe = is->priv->tokenbuf + is->priv->bufsize - 1;
784 if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
785 return IMAPX_TOK_ERROR;
790 o = is->priv->tokenbuf;
791 oe = is->priv->tokenbuf + is->priv->bufsize - 1;
792 digits = isdigit (c);
797 /*if (strchr(" \r\n*()[]+", c) != NULL) {*/
798 if (imapx_is_notid_char (c)) {
799 if (c == ' ' || c == '\r')
802 is->priv->ptr = p - 1;
804 *data = is->priv->tokenbuf;
805 *len = o - is->priv->tokenbuf;
806 t (is->tagprefix, "token TOKEN '%s'\n", is->priv->tokenbuf);
807 return digits ? IMAPX_TOK_INT : IMAPX_TOK_TOKEN;
811 camel_imapx_stream_grow (is, 0, &p, &o);
812 oe = is->priv->tokenbuf + is->priv->bufsize - 1;
815 digits &= isdigit (c);
819 if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
820 return IMAPX_TOK_ERROR;
826 /* Protocol error, skip until next lf? */
828 io (is->tagprefix, "Got protocol error\n");
831 is->priv->ptr = p - 1;
835 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "protocol error");
836 return IMAPX_TOK_PROTOCOL;
840 camel_imapx_stream_ungettoken (CamelIMAPXStream *is,
841 camel_imapx_token_t tok,
845 g_return_if_fail (CAMEL_IS_IMAPX_STREAM (is));
847 is->priv->unget_tok = tok;
848 is->priv->unget_token = token;
849 is->priv->unget_len = len;
853 /* returns -1 on error, 0 if last lot of data, >0 if more remaining */
855 camel_imapx_stream_gets (CamelIMAPXStream *is,
858 GCancellable *cancellable,
864 g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), -1);
865 g_return_val_if_fail (start != NULL, -1);
866 g_return_val_if_fail (len != NULL, -1);
870 max = is->priv->end - is->priv->ptr;
872 max = imapx_stream_fill (is, cancellable, error);
877 *start = is->priv->ptr;
878 end = memchr (is->priv->ptr, '\n', max);
880 max = (end - is->priv->ptr) + 1;
881 *start = is->priv->ptr;
883 is->priv->ptr += max;
885 return end == NULL ? 1 : 0;
889 camel_imapx_stream_set_literal (CamelIMAPXStream *is,
892 g_return_if_fail (CAMEL_IS_IMAPX_STREAM (is));
894 is->priv->literal = literal;
897 /* returns -1 on erorr, 0 if last data, >0 if more data left */
899 camel_imapx_stream_getl (CamelIMAPXStream *is,
902 GCancellable *cancellable,
907 g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), -1);
908 g_return_val_if_fail (start != NULL, -1);
909 g_return_val_if_fail (len != NULL, -1);
913 if (is->priv->literal > 0) {
914 max = is->priv->end - is->priv->ptr;
916 max = imapx_stream_fill (is, cancellable, error);
921 max = MIN (max, is->priv->literal);
922 *start = is->priv->ptr;
924 is->priv->ptr += max;
925 is->priv->literal -= max;
928 if (is->priv->literal > 0)
934 /* skip the rest of the line of tokens */
936 camel_imapx_stream_skip (CamelIMAPXStream *is,
937 GCancellable *cancellable,
944 g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), -1);
947 tok = camel_imapx_stream_token (is, &token, &len, cancellable, error);
948 if (tok == IMAPX_TOK_LITERAL) {
949 camel_imapx_stream_set_literal (is, len);
950 while ((tok = camel_imapx_stream_getl (is, &token, &len, cancellable, error)) > 0) {
951 io (is->tagprefix, "Skip literal data '%.*s'\n", (gint) len, token);
954 } while (tok != '\n' && tok >= 0);