Bug #606181 - Accepting bad SSL certificate applies to any hostname
[platform/upstream/evolution-data-server.git] / camel / providers / imapx / 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-imapx-utils.h"
35 #include "camel-imapx-stream.h"
36
37 #define t(...) camel_imapx_debug(token, __VA_ARGS__)
38 #define io(...) camel_imapx_debug(io, __VA_ARGS__)
39
40 G_DEFINE_TYPE (CamelIMAPXStream, camel_imapx_stream, CAMEL_TYPE_STREAM)
41
42 static gint
43 imapx_stream_fill (CamelIMAPXStream *is,
44                    GCancellable *cancellable,
45                    GError **error)
46 {
47         gint left = 0;
48
49         if (is->source) {
50                 left = is->end - is->ptr;
51                 memcpy (is->buf, is->ptr, left);
52                 is->end = is->buf + left;
53                 is->ptr = is->buf;
54                 left = camel_stream_read (
55                         is->source, (gchar *) is->end,
56                         is->bufsize - (is->end - is->buf),
57                         cancellable, error);
58                 if (left > 0) {
59                         is->end += left;
60                         io(is->tagprefix, "camel_imapx_read: buffer is '%.*s'\n", (gint)(is->end - is->ptr), is->ptr);
61                         return is->end - is->ptr;
62                 } else {
63                         io(is->tagprefix, "camel_imapx_read: -1\n");
64                         /* If returning zero, camel_stream_read() doesn't consider
65                          * that to be an error. But we do -- we should only be here
66                          * if we *know* there are data to receive. So set the error
67                          * accordingly */
68                         if (!left)
69                                 g_set_error (error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
70                                             _("Source stream returned no data"));
71                         return -1;
72                 }
73         }
74
75         io(is->tagprefix, "camel_imapx_read: -1\n");
76
77         g_set_error (
78                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
79                 _("Source stream unavailable"));
80
81         return -1;
82 }
83
84 static void
85 imapx_stream_dispose (GObject *object)
86 {
87         CamelIMAPXStream *stream = CAMEL_IMAPX_STREAM (object);
88
89         if (stream->source != NULL) {
90                 g_object_unref (stream->source);
91                 stream->source = NULL;
92         }
93
94         /* Chain up to parent's dispose() method. */
95         G_OBJECT_CLASS (camel_imapx_stream_parent_class)->dispose (object);
96 }
97
98 static void
99 imapx_stream_finalize (GObject *object)
100 {
101         CamelIMAPXStream *stream = CAMEL_IMAPX_STREAM (object);
102
103         g_free (stream->buf);
104         g_free (stream->tokenbuf);
105
106         /* Chain up to parent's finalize() method. */
107         G_OBJECT_CLASS (camel_imapx_stream_parent_class)->finalize (object);
108 }
109
110 static gssize
111 imapx_stream_read (CamelStream *stream,
112                    gchar *buffer,
113                    gsize n,
114                    GCancellable *cancellable,
115                    GError **error)
116 {
117         CamelIMAPXStream *is = (CamelIMAPXStream *) stream;
118         gssize max;
119
120         if (is->literal == 0 || n == 0)
121                 return 0;
122
123         max = is->end - is->ptr;
124         if (max > 0) {
125                 max = MIN (max, is->literal);
126                 max = MIN (max, n);
127                 memcpy (buffer, is->ptr, max);
128                 is->ptr += max;
129         } else {
130                 max = MIN (is->literal, n);
131                 max = camel_stream_read (is->source, buffer, max, cancellable, error);
132                 if (max <= 0)
133                         return max;
134         }
135
136         io(is->tagprefix, "camel_imapx_read(literal): '%.*s'\n", (gint)max, buffer);
137
138         is->literal -= max;
139
140         return max;
141 }
142
143 static gssize
144 imapx_stream_write (CamelStream *stream,
145                     const gchar *buffer,
146                     gsize n,
147                     GCancellable *cancellable,
148                     GError **error)
149 {
150         CamelIMAPXStream *is = (CamelIMAPXStream *) stream;
151
152         if (g_strstr_len (buffer, n, "LOGIN")) {
153                 io(is->tagprefix, "camel_imapx_write: 'LOGIN...'\n");
154         } else {
155                 io(is->tagprefix, "camel_imapx_write: '%.*s'\n", (gint)n, buffer);
156         }
157
158         return camel_stream_write (is->source, buffer, n, cancellable, error);
159 }
160
161 static gint
162 imapx_stream_close (CamelStream *stream,
163                     GCancellable *cancellable,
164                     GError **error)
165 {
166         /* nop? */
167         return 0;
168 }
169
170 static gint
171 imapx_stream_flush (CamelStream *stream,
172                     GCancellable *cancellable,
173                     GError **error)
174 {
175         /* nop? */
176         return 0;
177 }
178
179 static gboolean
180 imapx_stream_eos (CamelStream *stream)
181 {
182         CamelIMAPXStream *is = (CamelIMAPXStream *) stream;
183
184         return is->literal == 0;
185 }
186
187 static void
188 camel_imapx_stream_class_init (CamelIMAPXStreamClass *class)
189 {
190         GObjectClass *object_class;
191         CamelStreamClass *stream_class;
192
193         object_class = G_OBJECT_CLASS (class);
194         object_class->dispose = imapx_stream_dispose;
195         object_class->finalize = imapx_stream_finalize;
196
197         stream_class = CAMEL_STREAM_CLASS (class);
198         stream_class->read = imapx_stream_read;
199         stream_class->write = imapx_stream_write;
200         stream_class->close = imapx_stream_close;
201         stream_class->flush = imapx_stream_flush;
202         stream_class->eos = imapx_stream_eos;
203 }
204
205 static void
206 camel_imapx_stream_init (CamelIMAPXStream *is)
207 {
208         /* +1 is room for appending a 0 if we need to for a token */
209         is->bufsize = 4096;
210         is->ptr = is->end = is->buf = g_malloc (is->bufsize + 1);
211         is->tokenbuf = g_malloc (is->bufsize + 1);
212 }
213
214 static void camel_imapx_stream_grow (CamelIMAPXStream *is, guint len, guchar **bufptr, guchar **tokptr)
215 {
216         guchar *oldtok = is->tokenbuf;
217         guchar *oldbuf = is->buf;
218
219         do {
220                 is->bufsize <<= 1;
221         } while (is->bufsize <= len);
222
223         io(is->tagprefix, "Grow imapx buffers to %d bytes\n", is->bufsize);
224
225         is->tokenbuf = g_realloc (is->tokenbuf, is->bufsize + 1);
226         if (tokptr)
227                 *tokptr = is->tokenbuf + (*tokptr - oldtok);
228         if (is->unget)
229                 is->unget_token = is->tokenbuf + (is->unget_token - oldtok);
230
231         //io(is->tagprefix, "buf was %p, ptr %p end %p\n", is->buf, is->ptr, is->end);
232         is->buf = g_realloc (is->buf, is->bufsize + 1);
233         is->ptr = is->buf + (is->ptr - oldbuf);
234         is->end = is->buf + (is->end - oldbuf);
235         //io(is->tagprefix, "buf now %p, ptr %p end %p\n", is->buf, is->ptr, is->end);
236         if (bufptr)
237                 *bufptr = is->buf + (*bufptr - oldbuf);
238 }
239
240 /**
241  * camel_imapx_stream_new:
242  *
243  * Returns a NULL stream.  A null stream is always at eof, and
244  * always returns success for all reads and writes.
245  *
246  * Returns: the stream
247  **/
248 CamelStream *
249 camel_imapx_stream_new (CamelStream *source)
250 {
251         CamelIMAPXStream *is;
252
253         is = g_object_new (CAMEL_TYPE_IMAPX_STREAM, NULL);
254         is->source = g_object_ref (source);
255
256         return (CamelStream *) is;
257 }
258
259 GQuark
260 camel_imapx_error_quark (void)
261 {
262         static GQuark quark = 0;
263
264         if (G_UNLIKELY (quark == 0)) {
265                 const gchar *string = "camel-imapx-error-quark";
266                 quark = g_quark_from_static_string (string);
267         }
268
269         return quark;
270 }
271
272 /* Returns if there is any data buffered that is ready for processing */
273 gint
274 camel_imapx_stream_buffered (CamelIMAPXStream *is)
275 {
276         return is->end - is->ptr;
277 }
278
279 #if 0
280
281 static gint
282 skip_ws (CamelIMAPXStream *is,
283          guchar *pp,
284          guchar *pe)
285 {
286         register guchar c, *p;
287         guchar *e;
288
289         p = is->ptr;
290         e = is->end;
291
292         do {
293                 while (p >= e ) {
294                         is->ptr = p;
295                         if (imapx_stream_fill (is, NULL) == IMAPX_TOK_ERROR)
296                                 return IMAPX_TOK_ERROR;
297                         p = is->ptr;
298                         e = is->end;
299                 }
300                 c = *p++;
301         } while (c == ' ' || c == '\r');
302
303         is->ptr = p;
304         is->end = e;
305
306         return c;
307 }
308 #endif
309
310 /* FIXME: these should probably handle it themselves,
311  * and get rid of the token interface? */
312 gint
313 camel_imapx_stream_atom (CamelIMAPXStream *is,
314                          guchar **data,
315                          guint *lenp,
316                          GCancellable *cancellable,
317                          GError **error)
318 {
319         guchar *p, c;
320         GError *local_error = NULL;
321
322         /* this is only 'approximate' atom */
323         switch (camel_imapx_stream_token (is, data, lenp, cancellable, &local_error)) {
324         case IMAPX_TOK_TOKEN:
325                 p = *data;
326                 while ((c = *p))
327                         *p++ = toupper(c);
328         case IMAPX_TOK_INT:
329                 return 0;
330         case IMAPX_TOK_ERROR:
331                 if (local_error != NULL)
332                         g_propagate_error (error, local_error);
333                 return IMAPX_TOK_ERROR;
334         default:
335                 if (local_error == NULL)
336                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting atom");
337                 else
338                         g_propagate_error (error, local_error);
339                 io(is->tagprefix, "expecting atom!\n");
340                 return IMAPX_TOK_PROTOCOL;
341         }
342 }
343
344 /* gets an atom, a quoted_string, or a literal */
345 gint
346 camel_imapx_stream_astring (CamelIMAPXStream *is,
347                             guchar **data,
348                             GCancellable *cancellable,
349                             GError **error)
350 {
351         guchar *p, *start;
352         guint len, inlen;
353         gint ret;
354         GError *local_error = NULL;
355
356         switch (camel_imapx_stream_token (is, data, &len, cancellable, &local_error)) {
357         case IMAPX_TOK_TOKEN:
358         case IMAPX_TOK_INT:
359         case IMAPX_TOK_STRING:
360                 return 0;
361         case IMAPX_TOK_LITERAL:
362                 if (len >= is->bufsize)
363                         camel_imapx_stream_grow (is, len, NULL, NULL);
364                 p = is->tokenbuf;
365                 camel_imapx_stream_set_literal (is, len);
366                 do {
367                         ret = camel_imapx_stream_getl (is, &start, &inlen, cancellable, error);
368                         if (ret < 0)
369                                 return ret;
370                         memcpy (p, start, inlen);
371                         p += inlen;
372                 } while (ret > 0);
373                 *p = 0;
374                 *data = is->tokenbuf;
375                 return 0;
376         case IMAPX_TOK_ERROR:
377                 /* wont get unless no exception hanlder*/
378                 if (local_error != NULL)
379                         g_propagate_error (error, local_error);
380                 return IMAPX_TOK_ERROR;
381         default:
382                 if (local_error == NULL)
383                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting astring");
384                 else
385                         g_propagate_error (error, local_error);
386                 io(is->tagprefix, "expecting astring!\n");
387                 return IMAPX_TOK_PROTOCOL;
388         }
389 }
390
391 /* check for NIL or (small) quoted_string or literal */
392 gint
393 camel_imapx_stream_nstring (CamelIMAPXStream *is,
394                             guchar **data,
395                             GCancellable *cancellable,
396                             GError **error)
397 {
398         guchar *p, *start;
399         guint len, inlen;
400         gint ret;
401         GError *local_error = NULL;
402
403         switch (camel_imapx_stream_token (is, data, &len, cancellable, &local_error)) {
404         case IMAPX_TOK_STRING:
405                 return 0;
406         case IMAPX_TOK_LITERAL:
407                 if (len >= is->bufsize)
408                         camel_imapx_stream_grow (is, len, NULL, NULL);
409                 p = is->tokenbuf;
410                 camel_imapx_stream_set_literal (is, len);
411                 do {
412                         ret = camel_imapx_stream_getl (is, &start, &inlen, cancellable, error);
413                         if (ret < 0)
414                                 return ret;
415                         memcpy (p, start, inlen);
416                         p += inlen;
417                 } while (ret > 0);
418                 *p = 0;
419                 *data = is->tokenbuf;
420                 return 0;
421         case IMAPX_TOK_TOKEN:
422                 p = *data;
423                 if (toupper (p[0]) == 'N' && toupper (p[1]) == 'I' && toupper (p[2]) == 'L' && p[3] == 0) {
424                         *data = NULL;
425                         return 0;
426                 }
427         default:
428                 if (local_error == NULL)
429                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting nstring");
430                 else
431                         g_propagate_error (error, local_error);
432                 return IMAPX_TOK_PROTOCOL;
433         case IMAPX_TOK_ERROR:
434                 /* we'll never get this unless there are no exception  handlers anyway */
435                 if (local_error != NULL)
436                         g_propagate_error (error, local_error);
437                 return IMAPX_TOK_ERROR;
438
439         }
440 }
441
442 /* parse an nstring as a stream */
443 gint
444 camel_imapx_stream_nstring_stream (CamelIMAPXStream *is,
445                                    CamelStream **stream,
446                                    GCancellable *cancellable,
447                                    GError **error)
448 /* throws IO,PARSE exception */
449 {
450         guchar *token;
451         guint len;
452         gint ret = 0;
453         CamelStream * mem = NULL;
454         GError *local_error = NULL;
455
456         *stream = NULL;
457
458         switch (camel_imapx_stream_token (is, &token, &len, cancellable, &local_error)) {
459                 case IMAPX_TOK_STRING:
460                         mem = camel_stream_mem_new_with_buffer ((gchar *) token, len);
461                         *stream = mem;
462                         break;
463                 case IMAPX_TOK_LITERAL:
464                         /* if len is big, we could automatically use a file backing */
465                         camel_imapx_stream_set_literal (is, len);
466                         mem = camel_stream_mem_new ();
467                         if (camel_stream_write_to_stream ((CamelStream *) is, mem, cancellable, error) == -1) {
468                                 g_object_unref (mem);
469                                 ret = -1;
470                                 break;
471                         }
472
473                         g_seekable_seek (
474                                 G_SEEKABLE (mem), 0,
475                                 G_SEEK_SET, NULL, NULL);
476
477                         *stream = mem;
478                         break;
479                 case IMAPX_TOK_TOKEN:
480                         if (toupper (token[0]) == 'N' && toupper (token[1]) == 'I' && toupper (token[2]) == 'L' && token[3] == 0) {
481                                 *stream = NULL;
482                                 break;
483                         }
484                 default:
485                         ret = -1;
486                         if (local_error == NULL)
487                                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "nstring: token not string");
488                         else
489                                 g_propagate_error (error, local_error);
490         }
491
492         return ret;
493 }
494
495 guint64
496 camel_imapx_stream_number (CamelIMAPXStream *is,
497                            GCancellable *cancellable,
498                            GError **error)
499 {
500         guchar *token;
501         guint len;
502         GError *local_error = NULL;
503
504         if (camel_imapx_stream_token (is, &token, &len, cancellable, &local_error) != IMAPX_TOK_INT) {
505                 if (local_error == NULL)
506                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting number");
507                 else
508                         g_propagate_error (error, local_error);
509                 return 0;
510         }
511
512         return strtoull ((gchar *) token, 0, 10);
513 }
514
515 gint
516 camel_imapx_stream_text (CamelIMAPXStream *is,
517                          guchar **text,
518                          GCancellable *cancellable,
519                          GError **error)
520 {
521         GByteArray *build = g_byte_array_new ();
522         guchar *token;
523         guint len;
524         gint tok;
525
526         while (is->unget > 0) {
527                 switch (is->unget_tok) {
528                         case IMAPX_TOK_TOKEN:
529                         case IMAPX_TOK_STRING:
530                         case IMAPX_TOK_INT:
531                                 g_byte_array_append (build, (guint8 *) is->unget_token, is->unget_len);
532                                 g_byte_array_append(build, (guint8 *) " ", 1);
533                         default: /* invalid, but we'll ignore */
534                                 break;
535                 }
536                 is->unget--;
537         }
538
539         do {
540                 tok = camel_imapx_stream_gets (is, &token, &len, cancellable, error);
541                 if (tok < 0) {
542                         *text = NULL;
543                         g_byte_array_free (build, TRUE);
544                         return -1;
545                 }
546                 if (len)
547                         g_byte_array_append (build, token, len);
548         } while (tok > 0);
549
550         g_byte_array_append(build, (guint8 *) "", 1);
551         *text = build->data;
552         g_byte_array_free (build, FALSE);
553
554         return 0;
555 }
556
557 /* Get one token from the imap stream */
558 camel_imapx_token_t
559 /* throws IO,PARSE exception */
560 camel_imapx_stream_token (CamelIMAPXStream *is,
561                           guchar **data,
562                           guint *len,
563                           GCancellable *cancellable,
564                           GError **error)
565 {
566         register guchar c, *oe;
567         guchar *o, *p, *e;
568         guint literal;
569         gint digits;
570
571         if (is->unget > 0) {
572                 is->unget--;
573                 *data = is->unget_token;
574                 *len = is->unget_len;
575                 /*printf("token UNGET '%c' %s\n", is->unget_tok, is->unget_token);*/
576                 return is->unget_tok;
577         }
578
579         if (is->literal > 0)
580                 g_warning("stream_token called with literal %d", is->literal);
581
582         p = is->ptr;
583         e = is->end;
584
585         /* skip whitespace/prefill buffer */
586         do {
587                 while (p >= e ) {
588                         is->ptr = p;
589                         if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
590                                 return IMAPX_TOK_ERROR;
591                         p = is->ptr;
592                         e = is->end;
593                 }
594                 c = *p++;
595         } while (c == ' ' || c == '\r');
596
597         /*strchr("\n*()[]+", c)*/
598         if (imapx_is_token_char (c)) {
599                 is->ptr = p;
600                 t(is->tagprefix, "token '%c'\n", c);
601                 return c;
602         } else if (c == '{') {
603                 literal = 0;
604                 *data = p;
605                 while (1) {
606                         while (p < e) {
607                                 c = *p++;
608                                 if (isdigit (c) && literal < (UINT_MAX / 10)) {
609                                         literal = literal * 10 + (c - '0');
610                                 } else if (c == '}') {
611                                         while (1) {
612                                                 while (p < e) {
613                                                         c = *p++;
614                                                         if (c == '\n') {
615                                                                 *len = literal;
616                                                                 is->ptr = p;
617                                                                 is->literal = literal;
618                                                                 t(is->tagprefix, "token LITERAL %d\n", literal);
619                                                                 return IMAPX_TOK_LITERAL;
620                                                         }
621                                                 }
622                                                 is->ptr = p;
623                                                 if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
624                                                         return IMAPX_TOK_ERROR;
625                                                 p = is->ptr;
626                                                 e = is->end;
627                                         }
628                                 } else {
629                                         if (isdigit (c)) {
630                                                 io(is->tagprefix, "Protocol error: literal too big\n");
631                                         } else {
632                                                 io(is->tagprefix, "Protocol error: literal contains invalid gchar %02x '%c'\n", c, isprint(c)?c:c);
633                                         }
634                                         goto protocol_error;
635                                 }
636                         }
637                         is->ptr = p;
638                         if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
639                                 return IMAPX_TOK_ERROR;
640                         p = is->ptr;
641                         e = is->end;
642                 }
643         } else if (c == '"') {
644                 o = is->tokenbuf;
645                 oe = is->tokenbuf + is->bufsize - 1;
646                 while (1) {
647                         while (p < e) {
648                                 c = *p++;
649                                 if (c == '\\') {
650                                         while (p >= e) {
651                                                 is->ptr = p;
652                                                 if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
653                                                         return IMAPX_TOK_ERROR;
654                                                 p = is->ptr;
655                                                 e = is->end;
656                                         }
657                                         c = *p++;
658                                 } else if (c == '\"') {
659                                         is->ptr = p;
660                                         *o = 0;
661                                         *data = is->tokenbuf;
662                                         *len = o - is->tokenbuf;
663                                         t(is->tagprefix, "token STRING '%s'\n", is->tokenbuf);
664                                         return IMAPX_TOK_STRING;
665                                 }
666                                 if (c == '\n' || c == '\r') {
667                                         io(is->tagprefix, "Protocol error: truncated string\n");
668                                         goto protocol_error;
669                                 }
670                                 if (o >= oe) {
671                                         camel_imapx_stream_grow (is, 0, &p, &o);
672                                         oe = is->tokenbuf + is->bufsize - 1;
673                                         e = is->end;
674                                 }
675                                 *o++ = c;
676                         }
677                         is->ptr = p;
678                         if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
679                                 return IMAPX_TOK_ERROR;
680                         p = is->ptr;
681                         e = is->end;
682                 }
683         } else {
684                 o = is->tokenbuf;
685                 oe = is->tokenbuf + is->bufsize - 1;
686                 digits = isdigit (c);
687                 *o++ = c;
688                 while (1) {
689                         while (p < e) {
690                                 c = *p++;
691                                 /*if (strchr(" \r\n*()[]+", c) != NULL) {*/
692                                 if (imapx_is_notid_char (c)) {
693                                         if (c == ' ' || c == '\r')
694                                                 is->ptr = p;
695                                         else
696                                                 is->ptr = p - 1;
697                                         *o = 0;
698                                         *data = is->tokenbuf;
699                                         *len = o - is->tokenbuf;
700                                         t(is->tagprefix, "token TOKEN '%s'\n", is->tokenbuf);
701                                         return digits ? IMAPX_TOK_INT : IMAPX_TOK_TOKEN;
702                                 }
703
704                                 if (o >= oe) {
705                                         camel_imapx_stream_grow (is, 0, &p, &o);
706                                         oe = is->tokenbuf + is->bufsize - 1;
707                                         e = is->end;
708                                 }
709                                 digits &= isdigit (c);
710                                 *o++ = c;
711                         }
712                         is->ptr = p;
713                         if (imapx_stream_fill (is, cancellable, error) == IMAPX_TOK_ERROR)
714                                 return IMAPX_TOK_ERROR;
715                         p = is->ptr;
716                         e = is->end;
717                 }
718         }
719
720         /* Protocol error, skip until next lf? */
721 protocol_error:
722         io(is->tagprefix, "Got protocol error\n");
723
724         if (c == '\n')
725                 is->ptr = p - 1;
726         else
727                 is->ptr = p;
728
729         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "protocol error");
730         return IMAPX_TOK_PROTOCOL;
731 }
732
733 void
734 camel_imapx_stream_ungettoken (CamelIMAPXStream *is,
735                                camel_imapx_token_t tok,
736                                guchar *token,
737                                guint len)
738 {
739         /*printf("ungettoken: '%c' '%s'\n", tok, token);*/
740         is->unget_tok = tok;
741         is->unget_token = token;
742         is->unget_len = len;
743         is->unget++;
744 }
745
746 /* returns -1 on error, 0 if last lot of data, >0 if more remaining */
747 gint
748 camel_imapx_stream_gets (CamelIMAPXStream *is,
749                          guchar **start,
750                          guint *len,
751                          GCancellable *cancellable,
752                          GError **error)
753 {
754         gint max;
755         guchar *end;
756
757         *len = 0;
758
759         max = is->end - is->ptr;
760         if (max == 0) {
761                 max = imapx_stream_fill (is, cancellable, error);
762                 if (max <= 0)
763                         return max;
764         }
765
766         *start = is->ptr;
767         end = memchr (is->ptr, '\n', max);
768         if (end)
769                 max = (end - is->ptr) + 1;
770         *start = is->ptr;
771         *len = max;
772         is->ptr += max;
773
774         return end == NULL ? 1 : 0;
775 }
776
777 void camel_imapx_stream_set_literal (CamelIMAPXStream *is, guint literal)
778 {
779         is->literal = literal;
780 }
781
782 /* returns -1 on erorr, 0 if last data, >0 if more data left */
783 gint
784 camel_imapx_stream_getl (CamelIMAPXStream *is,
785                          guchar **start,
786                          guint *len,
787                          GCancellable *cancellable,
788                          GError **error)
789 {
790         gint max;
791
792         *len = 0;
793
794         if (is->literal > 0) {
795                 max = is->end - is->ptr;
796                 if (max == 0) {
797                         max = imapx_stream_fill (is, cancellable, error);
798                         if (max <= 0)
799                                 return max;
800                 }
801
802                 max = MIN (max, is->literal);
803                 *start = is->ptr;
804                 *len = max;
805                 is->ptr += max;
806                 is->literal -= max;
807         }
808
809         if (is->literal > 0)
810                 return 1;
811
812         return 0;
813 }
814
815 /* skip the rest of the line of tokens */
816 gint
817 camel_imapx_stream_skip (CamelIMAPXStream *is,
818                          GCancellable *cancellable,
819                          GError **error)
820 {
821         gint tok;
822         guchar *token;
823         guint len;
824
825         do {
826                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, error);
827                 if (tok == IMAPX_TOK_LITERAL) {
828                         camel_imapx_stream_set_literal (is, len);
829                         while ((tok = camel_imapx_stream_getl (is, &token, &len, cancellable, error)) > 0) {
830                                 io(is->tagprefix, "Skip literal data '%.*s'\n", (gint)len, token);
831                         }
832                 }
833         } while (tok != '\n' && tok >= 0);
834
835         if (tok < 0)
836                 return -1;
837
838         return 0;
839 }