Make use of G_DEFINE_QUARK()
[platform/upstream/evolution-data-server.git] / camel / camel-imapx-stream.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
2  *
3  * Author:
4  *  Michael Zucchi <notzed@ximian.com>
5  *
6  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
7  *
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.
11  *
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.
16  *
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.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <string.h>
28 #include <stdio.h>
29 #include <ctype.h>
30 #include <errno.h>
31
32 #include <glib/gi18n-lib.h>
33
34 #include <camel/camel-stream-mem.h>
35
36 #include "camel-imapx-utils.h"
37 #include "camel-imapx-stream.h"
38
39 #define CAMEL_IMAPX_STREAM_GET_PRIVATE(obj) \
40         (G_TYPE_INSTANCE_GET_PRIVATE \
41         ((obj), CAMEL_TYPE_IMAPX_STREAM, CamelIMAPXStreamPrivate))
42
43 #define t(...) camel_imapx_debug(token, __VA_ARGS__)
44 #define io(...) camel_imapx_debug(io, __VA_ARGS__)
45
46 struct _CamelIMAPXStreamPrivate {
47         CamelStream *source;
48
49         guchar *buf, *ptr, *end;
50         guint literal;
51
52         guint unget;
53         camel_imapx_token_t unget_tok;
54         guchar *unget_token;
55         guint unget_len;
56
57         guchar *tokenbuf;
58         guint bufsize;
59 };
60
61 enum {
62         PROP_0,
63         PROP_SOURCE
64 };
65
66 G_DEFINE_TYPE (CamelIMAPXStream, camel_imapx_stream, CAMEL_TYPE_STREAM)
67
68 static gint
69 imapx_stream_fill (CamelIMAPXStream *is,
70                    GCancellable *cancellable,
71                    GError **error)
72 {
73         gint left = 0;
74
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 (
81                         is->priv->source,
82                         (gchar *) is->priv->end,
83                         is->priv->bufsize - (is->priv->end - is->priv->buf),
84                         cancellable, error);
85                 if (left > 0) {
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;
89                 } else {
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
94                          * accordingly */
95                         if (!left)
96                                 g_set_error (
97                                         error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
98                                         _("Source stream returned no data"));
99                         return -1;
100                 }
101         }
102
103         io (is->tagprefix, "camel_imapx_read: -1\n");
104
105         g_set_error (
106                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
107                 _("Source stream unavailable"));
108
109         return -1;
110 }
111
112 static void
113 imapx_stream_set_source (CamelIMAPXStream *stream,
114                          CamelStream *source)
115 {
116         g_return_if_fail (CAMEL_IS_STREAM (source));
117         g_return_if_fail (stream->priv->source == NULL);
118
119         stream->priv->source = g_object_ref (source);
120 }
121
122 static void
123 imapx_stream_set_property (GObject *object,
124                            guint property_id,
125                            const GValue *value,
126                            GParamSpec *pspec)
127 {
128         switch (property_id) {
129                 case PROP_SOURCE:
130                         imapx_stream_set_source (
131                                 CAMEL_IMAPX_STREAM (object),
132                                 g_value_get_object (value));
133                         return;
134         }
135
136         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
137 }
138
139 static void
140 imapx_stream_get_property (GObject *object,
141                            guint property_id,
142                            GValue *value,
143                            GParamSpec *pspec)
144 {
145         switch (property_id) {
146                 case PROP_SOURCE:
147                         g_value_take_object (
148                                 value,
149                                 camel_imapx_stream_ref_source (
150                                 CAMEL_IMAPX_STREAM (object)));
151                         return;
152         }
153
154         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
155 }
156
157 static void
158 imapx_stream_dispose (GObject *object)
159 {
160         CamelIMAPXStream *stream = CAMEL_IMAPX_STREAM (object);
161
162         if (stream->priv->source != NULL) {
163                 g_object_unref (stream->priv->source);
164                 stream->priv->source = NULL;
165         }
166
167         /* Chain up to parent's dispose() method. */
168         G_OBJECT_CLASS (camel_imapx_stream_parent_class)->dispose (object);
169 }
170
171 static void
172 imapx_stream_finalize (GObject *object)
173 {
174         CamelIMAPXStream *stream = CAMEL_IMAPX_STREAM (object);
175
176         g_free (stream->priv->buf);
177         g_free (stream->priv->tokenbuf);
178
179         /* Chain up to parent's finalize() method. */
180         G_OBJECT_CLASS (camel_imapx_stream_parent_class)->finalize (object);
181 }
182
183 static gssize
184 imapx_stream_read (CamelStream *stream,
185                    gchar *buffer,
186                    gsize n,
187                    GCancellable *cancellable,
188                    GError **error)
189 {
190         CamelIMAPXStream *is = (CamelIMAPXStream *) stream;
191         gssize max;
192
193         if (is->priv->literal == 0 || n == 0)
194                 return 0;
195
196         max = is->priv->end - is->priv->ptr;
197         if (max > 0) {
198                 max = MIN (max, is->priv->literal);
199                 max = MIN (max, n);
200                 memcpy (buffer, is->priv->ptr, max);
201                 is->priv->ptr += max;
202         } else {
203                 max = MIN (is->priv->literal, n);
204                 max = camel_stream_read (
205                         is->priv->source,
206                         buffer, max, cancellable, error);
207                 if (max <= 0)
208                         return max;
209         }
210
211         io (is->tagprefix, "camel_imapx_read(literal): '%.*s'\n", (gint) max, buffer);
212
213         is->priv->literal -= max;
214
215         return max;
216 }
217
218 static gssize
219 imapx_stream_write (CamelStream *stream,
220                     const gchar *buffer,
221                     gsize n,
222                     GCancellable *cancellable,
223                     GError **error)
224 {
225         CamelIMAPXStream *is = (CamelIMAPXStream *) stream;
226
227         if (g_strstr_len (buffer, n, "LOGIN")) {
228                 io (is->tagprefix, "camel_imapx_write: 'LOGIN...'\n");
229         } else {
230                 io (is->tagprefix, "camel_imapx_write: '%.*s'\n", (gint) n, buffer);
231         }
232
233         return camel_stream_write (
234                 is->priv->source,
235                 buffer, n, cancellable, error);
236 }
237
238 static gint
239 imapx_stream_close (CamelStream *stream,
240                     GCancellable *cancellable,
241                     GError **error)
242 {
243         CamelIMAPXStream *is = (CamelIMAPXStream *) stream;
244
245         return camel_stream_close (is->priv->source, cancellable, error);
246 }
247
248 static gint
249 imapx_stream_flush (CamelStream *stream,
250                     GCancellable *cancellable,
251                     GError **error)
252 {
253         /* nop? */
254         return 0;
255 }
256
257 static gboolean
258 imapx_stream_eos (CamelStream *stream)
259 {
260         CamelIMAPXStream *is = (CamelIMAPXStream *) stream;
261
262         return is->priv->literal == 0;
263 }
264
265 static void
266 camel_imapx_stream_class_init (CamelIMAPXStreamClass *class)
267 {
268         GObjectClass *object_class;
269         CamelStreamClass *stream_class;
270
271         g_type_class_add_private (class, sizeof (CamelIMAPXStreamPrivate));
272
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;
278
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;
285
286         g_object_class_install_property (
287                 object_class,
288                 PROP_SOURCE,
289                 g_param_spec_object (
290                         "source",
291                         "Source",
292                         "Source stream",
293                         CAMEL_TYPE_STREAM,
294                         G_PARAM_READWRITE |
295                         G_PARAM_CONSTRUCT_ONLY |
296                         G_PARAM_STATIC_STRINGS));
297 }
298
299 static void
300 camel_imapx_stream_init (CamelIMAPXStream *is)
301 {
302         is->priv = CAMEL_IMAPX_STREAM_GET_PRIVATE (is);
303
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);
309 }
310
311 static void
312 camel_imapx_stream_grow (CamelIMAPXStream *is,
313                          guint len,
314                          guchar **bufptr,
315                          guchar **tokptr)
316 {
317         guchar *oldtok = is->priv->tokenbuf;
318         guchar *oldbuf = is->priv->buf;
319
320         do {
321                 is->priv->bufsize <<= 1;
322         } while (is->priv->bufsize <= len);
323
324         io (is->tagprefix, "Grow imapx buffers to %d bytes\n", is->priv->bufsize);
325
326         is->priv->tokenbuf = g_realloc (
327                 is->priv->tokenbuf,
328                 is->priv->bufsize + 1);
329         if (tokptr)
330                 *tokptr = is->priv->tokenbuf + (*tokptr - oldtok);
331         if (is->priv->unget)
332                 is->priv->unget_token =
333                         is->priv->tokenbuf +
334                         (is->priv->unget_token - oldtok);
335
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);
339         if (bufptr)
340                 *bufptr = is->priv->buf + (*bufptr - oldbuf);
341 }
342
343 G_DEFINE_QUARK (camel-imapx-error-quark, camel_imapx_error)
344
345 /**
346  * camel_imapx_stream_new:
347  *
348  * Returns a NULL stream.  A null stream is always at eof, and
349  * always returns success for all reads and writes.
350  *
351  * Returns: the stream
352  **/
353 CamelStream *
354 camel_imapx_stream_new (CamelStream *source)
355 {
356         g_return_val_if_fail (CAMEL_IS_STREAM (source), NULL);
357
358         return g_object_new (
359                 CAMEL_TYPE_IMAPX_STREAM,
360                 "source", source, NULL);
361 }
362
363 CamelStream *
364 camel_imapx_stream_ref_source (CamelIMAPXStream *is)
365 {
366         g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), NULL);
367
368         return g_object_ref (is->priv->source);
369 }
370
371 /* Returns if there is any data buffered that is ready for processing */
372 gint
373 camel_imapx_stream_buffered (CamelIMAPXStream *is)
374 {
375         g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), 0);
376
377         return is->priv->end - is->priv->ptr;
378 }
379
380 /* FIXME: these should probably handle it themselves,
381  * and get rid of the token interface? */
382 gint
383 camel_imapx_stream_atom (CamelIMAPXStream *is,
384                          guchar **data,
385                          guint *lenp,
386                          GCancellable *cancellable,
387                          GError **error)
388 {
389         guchar *p, c;
390         GError *local_error = NULL;
391
392         g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), IMAPX_TOK_ERROR);
393         g_return_val_if_fail (data != NULL, IMAPX_TOK_ERROR);
394         g_return_val_if_fail (lenp != NULL, IMAPX_TOK_ERROR);
395
396         /* this is only 'approximate' atom */
397         switch (camel_imapx_stream_token (is, data, lenp, cancellable, &local_error)) {
398         case IMAPX_TOK_TOKEN:
399                 p = *data;
400                 while ((c = *p))
401                         *p++ = toupper(c);
402         case IMAPX_TOK_INT:
403                 return 0;
404         case IMAPX_TOK_ERROR:
405                 if (local_error != NULL)
406                         g_propagate_error (error, local_error);
407                 return IMAPX_TOK_ERROR;
408         default:
409                 if (local_error == NULL)
410                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting atom");
411                 else
412                         g_propagate_error (error, local_error);
413                 io (is->tagprefix, "expecting atom!\n");
414                 return IMAPX_TOK_PROTOCOL;
415         }
416 }
417
418 /* gets an atom, a quoted_string, or a literal */
419 gint
420 camel_imapx_stream_astring (CamelIMAPXStream *is,
421                             guchar **data,
422                             GCancellable *cancellable,
423                             GError **error)
424 {
425         guchar *p, *start;
426         guint len, inlen;
427         gint ret;
428         GError *local_error = NULL;
429
430         g_return_val_if_fail (CAMEL_IMAPX_STREAM (is), IMAPX_TOK_ERROR);
431         g_return_val_if_fail (data != NULL, IMAPX_TOK_ERROR);
432
433         switch (camel_imapx_stream_token (is, data, &len, cancellable, &local_error)) {
434         case IMAPX_TOK_TOKEN:
435         case IMAPX_TOK_INT:
436         case IMAPX_TOK_STRING:
437                 return 0;
438         case IMAPX_TOK_LITERAL:
439                 if (len >= is->priv->bufsize)
440                         camel_imapx_stream_grow (is, len, NULL, NULL);
441                 p = is->priv->tokenbuf;
442                 camel_imapx_stream_set_literal (is, len);
443                 do {
444                         ret = camel_imapx_stream_getl (is, &start, &inlen, cancellable, error);
445                         if (ret < 0)
446                                 return ret;
447                         memcpy (p, start, inlen);
448                         p += inlen;
449                 } while (ret > 0);
450                 *p = 0;
451                 *data = is->priv->tokenbuf;
452                 return 0;
453         case IMAPX_TOK_ERROR:
454                 /* wont get unless no exception hanlder*/
455                 if (local_error != NULL)
456                         g_propagate_error (error, local_error);
457                 return IMAPX_TOK_ERROR;
458         default:
459                 if (local_error == NULL)
460                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting astring");
461                 else
462                         g_propagate_error (error, local_error);
463                 io (is->tagprefix, "expecting astring!\n");
464                 return IMAPX_TOK_PROTOCOL;
465         }
466 }
467
468 /* check for NIL or (small) quoted_string or literal */
469 gint
470 camel_imapx_stream_nstring (CamelIMAPXStream *is,
471                             guchar **data,
472                             GCancellable *cancellable,
473                             GError **error)
474 {
475         guchar *p, *start;
476         guint len, inlen;
477         gint ret;
478         GError *local_error = NULL;
479
480         g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), IMAPX_TOK_ERROR);
481         g_return_val_if_fail (data != NULL, IMAPX_TOK_ERROR);
482
483         switch (camel_imapx_stream_token (is, data, &len, cancellable, &local_error)) {
484         case IMAPX_TOK_STRING:
485                 return 0;
486         case IMAPX_TOK_LITERAL:
487                 if (len >= is->priv->bufsize)
488                         camel_imapx_stream_grow (is, len, NULL, NULL);
489                 p = is->priv->tokenbuf;
490                 camel_imapx_stream_set_literal (is, len);
491                 do {
492                         ret = camel_imapx_stream_getl (is, &start, &inlen, cancellable, error);
493                         if (ret < 0)
494                                 return ret;
495                         memcpy (p, start, inlen);
496                         p += inlen;
497                 } while (ret > 0);
498                 *p = 0;
499                 *data = is->priv->tokenbuf;
500                 return 0;
501         case IMAPX_TOK_TOKEN:
502                 p = *data;
503                 if (toupper (p[0]) == 'N' && toupper (p[1]) == 'I' && toupper (p[2]) == 'L' && p[3] == 0) {
504                         *data = NULL;
505                         return 0;
506                 }
507         default:
508                 if (local_error == NULL)
509                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting nstring");
510                 else
511                         g_propagate_error (error, local_error);
512                 return IMAPX_TOK_PROTOCOL;
513         case IMAPX_TOK_ERROR:
514                 /* we'll never get this unless there are no exception  handlers anyway */
515                 if (local_error != NULL)
516                         g_propagate_error (error, local_error);
517                 return IMAPX_TOK_ERROR;
518
519         }
520 }
521
522 /* parse an nstring as a stream */
523 gint
524 camel_imapx_stream_nstring_stream (CamelIMAPXStream *is,
525                                    CamelStream **stream,
526                                    GCancellable *cancellable,
527                                    GError **error)
528 {
529         guchar *token;
530         guint len;
531         gint ret = 0;
532         CamelStream * mem = NULL;
533         GError *local_error = NULL;
534
535         g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), -1);
536         g_return_val_if_fail (stream != NULL, -1);
537
538         *stream = NULL;
539
540         switch (camel_imapx_stream_token (is, &token, &len, cancellable, &local_error)) {
541                 case IMAPX_TOK_STRING:
542                         mem = camel_stream_mem_new_with_buffer ((gchar *) token, len);
543                         *stream = mem;
544                         break;
545                 case IMAPX_TOK_LITERAL:
546                         /* if len is big, we could automatically use a file backing */
547                         camel_imapx_stream_set_literal (is, len);
548                         mem = camel_stream_mem_new ();
549                         if (camel_stream_write_to_stream ((CamelStream *) is, mem, cancellable, error) == -1) {
550                                 g_object_unref (mem);
551                                 ret = -1;
552                                 break;
553                         }
554
555                         g_seekable_seek (
556                                 G_SEEKABLE (mem), 0,
557                                 G_SEEK_SET, NULL, NULL);
558
559                         *stream = mem;
560                         break;
561                 case IMAPX_TOK_TOKEN:
562                         if (toupper (token[0]) == 'N' && toupper (token[1]) == 'I' && toupper (token[2]) == 'L' && token[3] == 0) {
563                                 *stream = NULL;
564                                 break;
565                         }
566                 default:
567                         ret = -1;
568                         if (local_error == NULL)
569                                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "nstring: token not string");
570                         else
571                                 g_propagate_error (error, local_error);
572         }
573
574         return ret;
575 }
576
577 guint64
578 camel_imapx_stream_number (CamelIMAPXStream *is,
579                            GCancellable *cancellable,
580                            GError **error)
581 {
582         guchar *token;
583         guint len;
584         GError *local_error = NULL;
585
586         g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), 0);
587
588         if (camel_imapx_stream_token (is, &token, &len, cancellable, &local_error) != IMAPX_TOK_INT) {
589                 if (local_error == NULL)
590                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting number");
591                 else
592                         g_propagate_error (error, local_error);
593                 return 0;
594         }
595
596         return strtoull ((gchar *) token, 0, 10);
597 }
598
599 gint
600 camel_imapx_stream_text (CamelIMAPXStream *is,
601                          guchar **text,
602                          GCancellable *cancellable,
603                          GError **error)
604 {
605         GByteArray *build = g_byte_array_new ();
606         guchar *token;
607         guint len;
608         gint tok;
609
610         g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), -1);
611         g_return_val_if_fail (text != NULL, -1);
612
613         while (is->priv->unget > 0) {
614                 switch (is->priv->unget_tok) {
615                         case IMAPX_TOK_TOKEN:
616                         case IMAPX_TOK_STRING:
617                         case IMAPX_TOK_INT:
618                                 g_byte_array_append (
619                                         build, (guint8 *)
620                                         is->priv->unget_token,
621                                         is->priv->unget_len);
622                                 g_byte_array_append (
623                                         build, (guint8 *) " ", 1);
624                         default: /* invalid, but we'll ignore */
625                                 break;
626                 }
627                 is->priv->unget--;
628         }
629
630         do {
631                 tok = camel_imapx_stream_gets (is, &token, &len, cancellable, error);
632                 if (tok < 0) {
633                         *text = NULL;
634                         g_byte_array_free (build, TRUE);
635                         return -1;
636                 }
637                 if (len)
638                         g_byte_array_append (build, token, len);
639         } while (tok > 0);
640
641         g_byte_array_append (build, (guint8 *) "", 1);
642         *text = build->data;
643         g_byte_array_free (build, FALSE);
644
645         return 0;
646 }
647
648 /* Get one token from the imap stream */
649 camel_imapx_token_t
650 camel_imapx_stream_token (CamelIMAPXStream *is,
651                           guchar **data,
652                           guint *len,
653                           GCancellable *cancellable,
654                           GError **error)
655 {
656         register guchar c, *oe;
657         guchar *o, *p, *e;
658         guint literal;
659         gint digits;
660
661         g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), IMAPX_TOK_ERROR);
662         g_return_val_if_fail (data != NULL, IMAPX_TOK_ERROR);
663         g_return_val_if_fail (len != NULL, IMAPX_TOK_ERROR);
664
665         if (is->priv->unget > 0) {
666                 is->priv->unget--;
667                 *data = is->priv->unget_token;
668                 *len = is->priv->unget_len;
669                 return is->priv->unget_tok;
670         }
671
672         if (is->priv->literal > 0)
673                 g_warning (
674                         "stream_token called with literal %d",
675                         is->priv->literal);
676
677         p = is->priv->ptr;
678         e = is->priv->end;
679
680         /* skip whitespace/prefill buffer */
681         do {
682                 while (p >= e ) {
683                         is->priv->ptr = p;
684                         if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
685                                 return IMAPX_TOK_ERROR;
686                         p = is->priv->ptr;
687                         e = is->priv->end;
688                 }
689                 c = *p++;
690         } while (c == ' ' || c == '\r');
691
692         /*strchr("\n*()[]+", c)*/
693         if (imapx_is_token_char (c)) {
694                 is->priv->ptr = p;
695                 t (is->tagprefix, "token '%c'\n", c);
696                 return c;
697         } else if (c == '{') {
698                 literal = 0;
699                 *data = p;
700                 while (1) {
701                         while (p < e) {
702                                 c = *p++;
703                                 if (isdigit (c) && literal < (UINT_MAX / 10)) {
704                                         literal = literal * 10 + (c - '0');
705                                 } else if (c == '}') {
706                                         while (1) {
707                                                 while (p < e) {
708                                                         c = *p++;
709                                                         if (c == '\n') {
710                                                                 *len = literal;
711                                                                 is->priv->ptr = p;
712                                                                 is->priv->literal = literal;
713                                                                 t (is->tagprefix, "token LITERAL %d\n", literal);
714                                                                 return IMAPX_TOK_LITERAL;
715                                                         }
716                                                 }
717                                                 is->priv->ptr = p;
718                                                 if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
719                                                         return IMAPX_TOK_ERROR;
720                                                 p = is->priv->ptr;
721                                                 e = is->priv->end;
722                                         }
723                                 } else {
724                                         if (isdigit (c)) {
725                                                 io (is->tagprefix, "Protocol error: literal too big\n");
726                                         } else {
727                                                 io (is->tagprefix, "Protocol error: literal contains invalid gchar %02x '%c'\n", c, isprint (c) ? c : c);
728                                         }
729                                         goto protocol_error;
730                                 }
731                         }
732                         is->priv->ptr = p;
733                         if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
734                                 return IMAPX_TOK_ERROR;
735                         p = is->priv->ptr;
736                         e = is->priv->end;
737                 }
738         } else if (c == '"') {
739                 o = is->priv->tokenbuf;
740                 oe = is->priv->tokenbuf + is->priv->bufsize - 1;
741                 while (1) {
742                         while (p < e) {
743                                 c = *p++;
744                                 if (c == '\\') {
745                                         while (p >= e) {
746                                                 is->priv->ptr = p;
747                                                 if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
748                                                         return IMAPX_TOK_ERROR;
749                                                 p = is->priv->ptr;
750                                                 e = is->priv->end;
751                                         }
752                                         c = *p++;
753                                 } else if (c == '\"') {
754                                         is->priv->ptr = p;
755                                         *o = 0;
756                                         *data = is->priv->tokenbuf;
757                                         *len = o - is->priv->tokenbuf;
758                                         t (is->tagprefix, "token STRING '%s'\n", is->priv->tokenbuf);
759                                         return IMAPX_TOK_STRING;
760                                 }
761                                 if (c == '\n' || c == '\r') {
762                                         io (is->tagprefix, "Protocol error: truncated string\n");
763                                         goto protocol_error;
764                                 }
765                                 if (o >= oe) {
766                                         camel_imapx_stream_grow (is, 0, &p, &o);
767                                         oe = is->priv->tokenbuf + is->priv->bufsize - 1;
768                                         e = is->priv->end;
769                                 }
770                                 *o++ = c;
771                         }
772                         is->priv->ptr = p;
773                         if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
774                                 return IMAPX_TOK_ERROR;
775                         p = is->priv->ptr;
776                         e = is->priv->end;
777                 }
778         } else {
779                 o = is->priv->tokenbuf;
780                 oe = is->priv->tokenbuf + is->priv->bufsize - 1;
781                 digits = isdigit (c);
782                 *o++ = c;
783                 while (1) {
784                         while (p < e) {
785                                 c = *p++;
786                                 /*if (strchr(" \r\n*()[]+", c) != NULL) {*/
787                                 if (imapx_is_notid_char (c)) {
788                                         if (c == ' ' || c == '\r')
789                                                 is->priv->ptr = p;
790                                         else
791                                                 is->priv->ptr = p - 1;
792                                         *o = 0;
793                                         *data = is->priv->tokenbuf;
794                                         *len = o - is->priv->tokenbuf;
795                                         t (is->tagprefix, "token TOKEN '%s'\n", is->priv->tokenbuf);
796                                         return digits ? IMAPX_TOK_INT : IMAPX_TOK_TOKEN;
797                                 }
798
799                                 if (o >= oe) {
800                                         camel_imapx_stream_grow (is, 0, &p, &o);
801                                         oe = is->priv->tokenbuf + is->priv->bufsize - 1;
802                                         e = is->priv->end;
803                                 }
804                                 digits &= isdigit (c);
805                                 *o++ = c;
806                         }
807                         is->priv->ptr = p;
808                         if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
809                                 return IMAPX_TOK_ERROR;
810                         p = is->priv->ptr;
811                         e = is->priv->end;
812                 }
813         }
814
815         /* Protocol error, skip until next lf? */
816 protocol_error:
817         io (is->tagprefix, "Got protocol error\n");
818
819         if (c == '\n')
820                 is->priv->ptr = p - 1;
821         else
822                 is->priv->ptr = p;
823
824         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "protocol error");
825         return IMAPX_TOK_PROTOCOL;
826 }
827
828 void
829 camel_imapx_stream_ungettoken (CamelIMAPXStream *is,
830                                camel_imapx_token_t tok,
831                                guchar *token,
832                                guint len)
833 {
834         g_return_if_fail (CAMEL_IS_IMAPX_STREAM (is));
835
836         is->priv->unget_tok = tok;
837         is->priv->unget_token = token;
838         is->priv->unget_len = len;
839         is->priv->unget++;
840 }
841
842 /* returns -1 on error, 0 if last lot of data, >0 if more remaining */
843 gint
844 camel_imapx_stream_gets (CamelIMAPXStream *is,
845                          guchar **start,
846                          guint *len,
847                          GCancellable *cancellable,
848                          GError **error)
849 {
850         gint max;
851         guchar *end;
852
853         g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), -1);
854         g_return_val_if_fail (start != NULL, -1);
855         g_return_val_if_fail (len != NULL, -1);
856
857         *len = 0;
858
859         max = is->priv->end - is->priv->ptr;
860         if (max == 0) {
861                 max = imapx_stream_fill (is, cancellable, error);
862                 if (max <= 0)
863                         return max;
864         }
865
866         *start = is->priv->ptr;
867         end = memchr (is->priv->ptr, '\n', max);
868         if (end)
869                 max = (end - is->priv->ptr) + 1;
870         *start = is->priv->ptr;
871         *len = max;
872         is->priv->ptr += max;
873
874         return end == NULL ? 1 : 0;
875 }
876
877 void
878 camel_imapx_stream_set_literal (CamelIMAPXStream *is,
879                                 guint literal)
880 {
881         g_return_if_fail (CAMEL_IS_IMAPX_STREAM (is));
882
883         is->priv->literal = literal;
884 }
885
886 /* returns -1 on erorr, 0 if last data, >0 if more data left */
887 gint
888 camel_imapx_stream_getl (CamelIMAPXStream *is,
889                          guchar **start,
890                          guint *len,
891                          GCancellable *cancellable,
892                          GError **error)
893 {
894         gint max;
895
896         g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), -1);
897         g_return_val_if_fail (start != NULL, -1);
898         g_return_val_if_fail (len != NULL, -1);
899
900         *len = 0;
901
902         if (is->priv->literal > 0) {
903                 max = is->priv->end - is->priv->ptr;
904                 if (max == 0) {
905                         max = imapx_stream_fill (is, cancellable, error);
906                         if (max <= 0)
907                                 return max;
908                 }
909
910                 max = MIN (max, is->priv->literal);
911                 *start = is->priv->ptr;
912                 *len = max;
913                 is->priv->ptr += max;
914                 is->priv->literal -= max;
915         }
916
917         if (is->priv->literal > 0)
918                 return 1;
919
920         return 0;
921 }
922
923 /* skip the rest of the line of tokens */
924 gint
925 camel_imapx_stream_skip (CamelIMAPXStream *is,
926                          GCancellable *cancellable,
927                          GError **error)
928 {
929         gint tok;
930         guchar *token;
931         guint len;
932
933         g_return_val_if_fail (CAMEL_IS_IMAPX_STREAM (is), -1);
934
935         do {
936                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, error);
937                 if (tok == IMAPX_TOK_LITERAL) {
938                         camel_imapx_stream_set_literal (is, len);
939                         while ((tok = camel_imapx_stream_getl (is, &token, &len, cancellable, error)) > 0) {
940                                 io (is->tagprefix, "Skip literal data '%.*s'\n", (gint) len, token);
941                         }
942                 }
943         } while (tok != '\n' && tok >= 0);
944
945         if (tok < 0)
946                 return -1;
947
948         return 0;
949 }
950