2 * camel-imapx-command.c
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with the program; if not, see <http://www.gnu.org/licenses/>
19 #include "camel-imapx-command.h"
23 #include <glib/gstdio.h>
24 #include <glib/gi18n-lib.h>
26 #include "camel-imapx-job.h"
27 #include "camel-imapx-server.h"
28 #include "camel-imapx-store.h"
30 #define c(...) camel_imapx_debug(command, __VA_ARGS__)
32 typedef struct _CamelIMAPXRealCommand CamelIMAPXRealCommand;
34 /* CamelIMAPXCommand + some private bits */
35 struct _CamelIMAPXRealCommand {
36 CamelIMAPXCommand public;
38 volatile gint ref_count;
42 /* For building the part. */
45 /* Used for running some commands synchronously. */
46 GCond *done_sync_cond;
47 GMutex *done_sync_mutex;
48 gboolean done_sync_flag;
51 /* Safe to cast to a GQueue. */
52 struct _CamelIMAPXCommandQueue {
57 camel_imapx_command_new (CamelIMAPXServer *is,
63 CamelIMAPXRealCommand *real_ic;
67 real_ic = g_slice_new0 (CamelIMAPXRealCommand);
69 /* Initialize private bits. */
70 real_ic->ref_count = 1;
71 real_ic->buffer = g_string_sized_new (512);
72 real_ic->done_sync_cond = g_cond_new ();
73 real_ic->done_sync_mutex = g_mutex_new ();
75 /* Initialize public bits. */
76 real_ic->public.is = is;
77 real_ic->public.tag = tag++;
78 real_ic->public.name = name;
79 real_ic->public.select = select;
80 g_queue_init (&real_ic->public.parts);
82 if (format != NULL && *format != '\0') {
83 va_start (ap, format);
84 camel_imapx_command_addv (
85 (CamelIMAPXCommand *) real_ic, format, ap);
89 return (CamelIMAPXCommand *) real_ic;
93 camel_imapx_command_ref (CamelIMAPXCommand *ic)
95 CamelIMAPXRealCommand *real_ic;
97 g_return_val_if_fail (CAMEL_IS_IMAPX_COMMAND (ic), NULL);
99 real_ic = (CamelIMAPXRealCommand *) ic;
101 g_atomic_int_inc (&real_ic->ref_count);
107 camel_imapx_command_unref (CamelIMAPXCommand *ic)
109 CamelIMAPXRealCommand *real_ic;
111 g_return_if_fail (CAMEL_IS_IMAPX_COMMAND (ic));
113 real_ic = (CamelIMAPXRealCommand *) ic;
115 if (g_atomic_int_dec_and_test (&real_ic->ref_count)) {
116 CamelIMAPXCommandPart *cp;
118 /* Free the public stuff. */
120 imapx_free_status (ic->status);
122 while ((cp = g_queue_pop_head (&ic->parts)) != NULL) {
125 switch (cp->type & CAMEL_IMAPX_COMMAND_MASK) {
126 case CAMEL_IMAPX_COMMAND_FILE:
127 case CAMEL_IMAPX_COMMAND_STRING:
131 g_object_unref (cp->ob);
137 /* Free the private stuff. */
139 if (real_ic->job != NULL)
140 camel_imapx_job_unref (real_ic->job);
142 g_string_free (real_ic->buffer, TRUE);
144 g_cond_free (real_ic->done_sync_cond);
145 g_mutex_free (real_ic->done_sync_mutex);
147 /* Do NOT try to free the GError. If set it should have been
148 * propagated to the CamelIMAPXJob, so it's either NULL or the
149 * CamelIMAPXJob owns it now. */
151 /* Fill the memory with a bit pattern before releasing
152 * it back to the slab allocator, so we can more easily
153 * identify dangling CamelIMAPXCommand pointers. */
154 memset (real_ic, 0xaa, sizeof (CamelIMAPXRealCommand));
156 /* But leave the reference count set to zero, so
157 * CAMEL_IS_IMAPX_COMMAND can identify it as bad. */
158 real_ic->ref_count = 0;
160 g_slice_free (CamelIMAPXRealCommand, real_ic);
165 camel_imapx_command_check (CamelIMAPXCommand *ic)
167 CamelIMAPXRealCommand *real_ic;
169 real_ic = (CamelIMAPXRealCommand *) ic;
171 return (real_ic != NULL && real_ic->ref_count > 0);
175 camel_imapx_command_compare (CamelIMAPXCommand *ic1,
176 CamelIMAPXCommand *ic2)
178 g_return_val_if_fail (CAMEL_IS_IMAPX_COMMAND (ic1), 0);
179 g_return_val_if_fail (CAMEL_IS_IMAPX_COMMAND (ic2), 0);
181 if (ic1->pri == ic2->pri)
184 return (ic1->pri < ic2->pri) ? -1 : 1;
188 camel_imapx_command_get_job (CamelIMAPXCommand *ic)
190 CamelIMAPXRealCommand *real_ic;
192 g_return_val_if_fail (CAMEL_IS_IMAPX_COMMAND (ic), NULL);
194 real_ic = (CamelIMAPXRealCommand *) ic;
200 camel_imapx_command_set_job (CamelIMAPXCommand *ic,
203 CamelIMAPXRealCommand *real_ic;
205 g_return_if_fail (CAMEL_IS_IMAPX_COMMAND (ic));
207 real_ic = (CamelIMAPXRealCommand *) ic;
210 g_return_if_fail (CAMEL_IS_IMAPX_JOB (job));
211 camel_imapx_job_ref (job);
214 if (real_ic->job != NULL)
215 camel_imapx_job_unref (real_ic->job);
221 camel_imapx_command_add (CamelIMAPXCommand *ic,
227 g_return_if_fail (CAMEL_IS_IMAPX_COMMAND (ic));
229 if (format != NULL && *format != '\0') {
230 va_start (ap, format);
231 camel_imapx_command_addv (ic, format, ap);
237 camel_imapx_command_addv (CamelIMAPXCommand *ic,
241 const gchar *p, *ps, *start;
255 gchar literal_format[16];
257 CamelStore *parent_store;
259 gchar *fname = NULL, *encoded = NULL;
260 const gchar *full_name;
262 g_return_if_fail (CAMEL_IS_IMAPX_COMMAND (ic));
264 c(ic->is->tagprefix, "adding command, format = '%s'\n", format);
266 buffer = ((CamelIMAPXRealCommand *) ic)->buffer;
270 while ((c = *p++) != '\0') {
274 g_string_append_len (buffer, ps, p - ps);
280 g_string_append_len (buffer, ps, p - ps - 1);
296 if (g_ascii_isdigit (c))
297 width = width * 10 + (c - '0');
300 } while ((c = *p++));
308 case 'A': /* auth object - sasl auth, treat as special kind of continuation */
309 A = va_arg (ap, CamelSasl *);
310 camel_imapx_command_add_part (ic, CAMEL_IMAPX_COMMAND_AUTH, A);
312 case 'S': /* stream */
313 S = va_arg (ap, CamelStream *);
314 c(ic->is->tagprefix, "got stream '%p'\n", S);
315 camel_imapx_command_add_part (ic, CAMEL_IMAPX_COMMAND_STREAM, S);
317 case 'D': /* datawrapper */
318 D = va_arg (ap, CamelDataWrapper *);
319 c(ic->is->tagprefix, "got data wrapper '%p'\n", D);
320 camel_imapx_command_add_part (ic, CAMEL_IMAPX_COMMAND_DATAWRAPPER, D);
322 case 'P': /* filename path */
323 P = va_arg (ap, gchar *);
324 c(ic->is->tagprefix, "got file path '%s'\n", P);
325 camel_imapx_command_add_part (ic, CAMEL_IMAPX_COMMAND_FILE, P);
327 case 't': /* token */
328 s = va_arg (ap, gchar *);
329 g_string_append (buffer, s);
331 case 's': /* simple string */
332 s = va_arg (ap, gchar *);
333 c(ic->is->tagprefix, "got string '%s'\n", g_str_has_prefix (format, "LOGIN") ? "***" : s);
336 guchar mask = imapx_is_mask (s);
338 if (mask & IMAPX_TYPE_ATOM_CHAR)
339 g_string_append (buffer, s);
340 else if (mask & IMAPX_TYPE_TEXT_CHAR) {
341 g_string_append_c (buffer, '"');
345 while (*s && imapx_is_quoted_char (*s))
347 g_string_append_len (buffer, start, s - start);
349 g_string_append_c (buffer, '\\');
350 g_string_append_c (buffer, *s);
354 g_string_append_c (buffer, '"');
356 camel_imapx_command_add_part (ic, CAMEL_IMAPX_COMMAND_STRING, s);
359 g_string_append (buffer, "\"\"");
366 case 'f': /* imap folder name */
367 folder = va_arg (ap, CamelFolder *);
368 full_name = camel_folder_get_full_name (folder);
369 c(ic->is->tagprefix, "got folder '%s'\n", full_name);
370 parent_store = camel_folder_get_parent_store (folder);
371 fname = camel_imapx_store_summary_full_from_path (((CamelIMAPXStore *) parent_store)->summary, full_name);
373 encoded = camel_utf8_utf7 (fname);
376 encoded = camel_utf8_utf7 (full_name);
382 g_string_append (buffer, "\"\"");
385 case 'F': /* IMAP flags set */
386 f = va_arg (ap, guint32);
387 F = va_arg (ap, CamelFlag *);
388 imapx_write_flags (buffer, f, F);
391 d = va_arg (ap, gint);
393 g_string_append_c (buffer, ch);
395 case 'd': /* int/unsigned */
398 l = va_arg (ap, glong);
399 c(ic->is->tagprefix, "got glong '%d'\n", (gint)l);
400 memcpy (literal_format, start, p - start);
401 literal_format[p - start] = 0;
402 g_string_append_printf (buffer, literal_format, l);
403 } else if (llong == 2) {
404 guint64 i64 = va_arg (ap, guint64);
405 c(ic->is->tagprefix, "got guint64 '%d'\n", (gint)i64);
406 memcpy (literal_format, start, p - start);
407 literal_format[p - start] = 0;
408 g_string_append_printf (buffer, literal_format, i64);
410 d = va_arg (ap, gint);
411 c(ic->is->tagprefix, "got gint '%d'\n", d);
412 memcpy (literal_format, start, p - start);
413 literal_format[p - start] = 0;
414 g_string_append_printf (buffer, literal_format, d);
422 case '\\': /* only for \\ really, we dont support \n\r etc at all */
425 g_assert (c == '\\');
426 g_string_append_len (buffer, ps, p - ps);
433 g_string_append_len (buffer, ps, p - ps - 1);
437 camel_imapx_command_add_part (CamelIMAPXCommand *ic,
438 CamelIMAPXCommandPartType type,
441 CamelIMAPXCommandPart *cp;
442 CamelStreamNull *null;
446 buffer = ((CamelIMAPXRealCommand *) ic)->buffer;
448 /* TODO: literal+? */
450 switch (type & CAMEL_IMAPX_COMMAND_MASK) {
451 case CAMEL_IMAPX_COMMAND_DATAWRAPPER:
452 case CAMEL_IMAPX_COMMAND_STREAM: {
453 CamelObject *ob = data;
455 /* TODO: seekable streams we could just seek to the end and back */
456 null = (CamelStreamNull *) camel_stream_null_new ();
457 if ( (type & CAMEL_IMAPX_COMMAND_MASK) == CAMEL_IMAPX_COMMAND_DATAWRAPPER) {
458 camel_data_wrapper_write_to_stream_sync ((CamelDataWrapper *) ob, (CamelStream *) null, NULL, NULL);
460 g_seekable_seek (G_SEEKABLE (ob), 0, G_SEEK_SET, NULL, NULL);
461 camel_stream_write_to_stream ((CamelStream *) ob, (CamelStream *) null, NULL, NULL);
462 g_seekable_seek (G_SEEKABLE (ob), 0, G_SEEK_SET, NULL, NULL);
464 type |= CAMEL_IMAPX_COMMAND_LITERAL_PLUS;
466 ob_size = null->written;
467 g_object_unref (null);
470 case CAMEL_IMAPX_COMMAND_AUTH: {
471 CamelObject *ob = data;
472 const gchar *mechanism;
474 /* we presume we'll need to get additional data only if we're not authenticated yet */
476 mechanism = camel_sasl_get_mechanism (CAMEL_SASL (ob));
477 g_string_append (buffer, mechanism);
478 if (!camel_sasl_get_authenticated ((CamelSasl *) ob))
479 type |= CAMEL_IMAPX_COMMAND_CONTINUATION;
482 case CAMEL_IMAPX_COMMAND_FILE: {
486 if (g_stat (path, &st) == 0) {
487 data = g_strdup (data);
488 ob_size = st.st_size;
492 type |= CAMEL_IMAPX_COMMAND_LITERAL_PLUS;
495 case CAMEL_IMAPX_COMMAND_STRING:
496 data = g_strdup (data);
497 ob_size = strlen (data);
498 type |= CAMEL_IMAPX_COMMAND_LITERAL_PLUS;
504 if (type & CAMEL_IMAPX_COMMAND_LITERAL_PLUS) {
505 g_string_append_c (buffer, '{');
506 g_string_append_printf (buffer, "%u", ob_size);
507 if (ic->is->cinfo && ic->is->cinfo->capa & IMAPX_CAPABILITY_LITERALPLUS) {
508 g_string_append_c (buffer, '+');
510 type &= ~CAMEL_IMAPX_COMMAND_LITERAL_PLUS;
511 type |= CAMEL_IMAPX_COMMAND_CONTINUATION;
513 g_string_append_c (buffer, '}');
516 cp = g_malloc0 (sizeof (*cp));
518 cp->ob_size = ob_size;
520 cp->data_size = buffer->len;
521 cp->data = g_strdup (buffer->str);
523 g_string_set_size (buffer, 0);
525 g_queue_push_tail (&ic->parts, cp);
529 camel_imapx_command_close (CamelIMAPXCommand *ic)
533 g_return_if_fail (CAMEL_IS_IMAPX_COMMAND (ic));
535 buffer = ((CamelIMAPXRealCommand *) ic)->buffer;
537 if (buffer->len > 5 && g_ascii_strncasecmp (buffer->str, "LOGIN", 5) == 0) {
538 c(ic->is->tagprefix, "completing command buffer is [%d] 'LOGIN...'\n", (gint) buffer->len);
540 c(ic->is->tagprefix, "completing command buffer is [%d] '%.*s'\n", (gint) buffer->len, (gint) buffer->len, buffer->str);
543 camel_imapx_command_add_part (ic, CAMEL_IMAPX_COMMAND_SIMPLE, NULL);
545 g_string_set_size (buffer, 0);
549 camel_imapx_command_wait (CamelIMAPXCommand *ic)
551 CamelIMAPXRealCommand *real_ic;
553 g_return_if_fail (CAMEL_IS_IMAPX_COMMAND (ic));
555 real_ic = (CamelIMAPXRealCommand *) ic;
557 g_mutex_lock (real_ic->done_sync_mutex);
558 while (!real_ic->done_sync_flag)
560 real_ic->done_sync_cond,
561 real_ic->done_sync_mutex);
562 g_mutex_unlock (real_ic->done_sync_mutex);
566 camel_imapx_command_done (CamelIMAPXCommand *ic)
568 CamelIMAPXRealCommand *real_ic;
570 g_return_if_fail (CAMEL_IS_IMAPX_COMMAND (ic));
572 real_ic = (CamelIMAPXRealCommand *) ic;
574 g_mutex_lock (real_ic->done_sync_mutex);
575 real_ic->done_sync_flag = TRUE;
576 g_cond_broadcast (real_ic->done_sync_cond);
577 g_mutex_unlock (real_ic->done_sync_mutex);
581 camel_imapx_command_set_error_if_failed (CamelIMAPXCommand *ic,
584 g_return_val_if_fail (CAMEL_IS_IMAPX_COMMAND (ic), FALSE);
586 if (ic->status != NULL && ic->status->result != IMAPX_OK) {
587 if (ic->status->text != NULL)
589 error, CAMEL_IMAPX_ERROR, 1,
590 "%s", ic->status->text);
593 error, CAMEL_IMAPX_ERROR, 1,
594 "%s", _("Unknown error"));
601 CamelIMAPXCommandQueue *
602 camel_imapx_command_queue_new (void)
604 /* An initialized GQueue is simply zero-filled,
605 * so we can skip calling g_queue_init() here. */
606 return g_slice_new0 (CamelIMAPXCommandQueue);
610 camel_imapx_command_queue_free (CamelIMAPXCommandQueue *queue)
612 CamelIMAPXCommand *ic;
614 g_return_if_fail (queue != NULL);
616 while ((ic = g_queue_pop_head ((GQueue *) queue)) != NULL)
617 camel_imapx_command_unref (ic);
619 g_slice_free (CamelIMAPXCommandQueue, queue);
623 camel_imapx_command_queue_transfer (CamelIMAPXCommandQueue *from,
624 CamelIMAPXCommandQueue *to)
628 g_return_if_fail (from != NULL);
629 g_return_if_fail (to != NULL);
631 while ((link = g_queue_pop_head_link ((GQueue *) from)) != NULL)
632 g_queue_push_tail_link ((GQueue *) to, link);
636 camel_imapx_command_queue_push_tail (CamelIMAPXCommandQueue *queue,
637 CamelIMAPXCommand *ic)
639 g_return_if_fail (queue != NULL);
640 g_return_if_fail (CAMEL_IS_IMAPX_COMMAND (ic));
642 camel_imapx_command_ref (ic);
644 g_queue_push_tail ((GQueue *) queue, ic);
648 camel_imapx_command_queue_insert_sorted (CamelIMAPXCommandQueue *queue,
649 CamelIMAPXCommand *ic)
651 g_return_if_fail (queue != NULL);
652 g_return_if_fail (CAMEL_IS_IMAPX_COMMAND (ic));
654 camel_imapx_command_ref (ic);
656 g_queue_insert_sorted (
657 (GQueue *) queue, ic, (GCompareDataFunc)
658 camel_imapx_command_compare, NULL);
662 camel_imapx_command_queue_is_empty (CamelIMAPXCommandQueue *queue)
664 g_return_val_if_fail (queue != NULL, TRUE);
666 return g_queue_is_empty ((GQueue *) queue);
670 camel_imapx_command_queue_get_length (CamelIMAPXCommandQueue *queue)
672 g_return_val_if_fail (queue != NULL, 0);
674 return g_queue_get_length ((GQueue *) queue);
678 camel_imapx_command_queue_peek_head (CamelIMAPXCommandQueue *queue)
680 g_return_val_if_fail (queue != NULL, NULL);
682 return g_queue_peek_head ((GQueue *) queue);
686 camel_imapx_command_queue_peek_head_link (CamelIMAPXCommandQueue *queue)
688 g_return_val_if_fail (queue != NULL, NULL);
690 return g_queue_peek_head_link ((GQueue *) queue);
694 camel_imapx_command_queue_remove (CamelIMAPXCommandQueue *queue,
695 CamelIMAPXCommand *ic)
697 g_return_val_if_fail (queue != NULL, FALSE);
698 g_return_val_if_fail (CAMEL_IS_IMAPX_COMMAND (ic), FALSE);
700 if (g_queue_remove ((GQueue *) queue, ic)) {
701 camel_imapx_command_unref (ic);
709 camel_imapx_command_queue_delete_link (CamelIMAPXCommandQueue *queue,
712 g_return_if_fail (queue != NULL);
713 g_return_if_fail (link != NULL);
715 /* Verify the link is actually in the queue. */
716 if (g_queue_link_index ((GQueue *) queue, link) == -1) {
717 g_warning ("%s: Link not found in queue", G_STRFUNC);
721 camel_imapx_command_unref ((CamelIMAPXCommand *) link->data);
722 g_queue_delete_link ((GQueue *) queue, link);