/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Authors: Jeffrey Stedfast * * Copyright 2000 Helix Code, Inc. (www.helixcode.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA. * */ #include #include #include #include #include #include "camel-imap-utils.h" #include "string-utils.h" #include #include "camel/camel-folder-summary.h" #define d(x) x char * imap_next_word (const char *buf) { char *word; /* skip over current word */ for (word = (char *)buf; *word && *word != ' '; word++); /* skip over white space */ for ( ; *word && *word == ' '; word++); return word; } /** * imap_parse_list_response: * @buf: the LIST or LSUB response * @flags: a pointer to a variable to store the flags in, or %NULL * @sep: a pointer to a variable to store the hierarchy separator in, or %NULL * @folder: a pointer to a variable to store the folder name in, or %NULL * * Parses a LIST or LSUB response and returns the desired parts of it. * If @folder is non-%NULL, its value must be freed by the caller. * * Return value: whether or not the response was successfully parsed. **/ gboolean imap_parse_list_response (const char *buf, int *flags, char *sep, char **folder) { char *word; int len; if (*buf != '*') return FALSE; word = imap_next_word (buf); if (g_strncasecmp (word, "LIST", 4) && g_strncasecmp (word, "LSUB", 4)) return FALSE; /* get the flags */ word = imap_next_word (word); if (*word != '(') return FALSE; if (flags) *flags = 0; word++; while (*word != ')') { len = strcspn (word, " )"); if (flags) { if (!g_strncasecmp (word, "\\Noinferiors", len)) *flags |= IMAP_LIST_FLAG_NOINFERIORS; else if (!g_strncasecmp (word, "\\Noselect", len)) *flags |= IMAP_LIST_FLAG_NOSELECT; else if (!g_strncasecmp (word, "\\Marked", len)) *flags |= IMAP_LIST_FLAG_MARKED; else if (!g_strncasecmp (word, "\\Unmarked", len)) *flags |= IMAP_LIST_FLAG_UNMARKED; } word += len; while (*word == ' ') word++; } /* get the directory separator */ word = imap_next_word (word); if (!strncmp (word, "NIL", 3)) { if (sep) *sep = '\0'; } else if (*word++ == '"') { if (*word == '\\') word++; if (sep) *sep = *word; word++; if (*word++ != '"') return FALSE; } else return FALSE; if (folder) { /* get the folder name */ word = imap_next_word (word); *folder = imap_parse_astring (&word, &len); return *folder != NULL; } return TRUE; } static ESExpResult * func_and (struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) { GList **list = data; ESExpResult *r; d(fprintf (stderr, "in AND func (argc = %d)\n", argc)); if (argc > 0) { char **strings; int i; strings = g_new (char*, argc+1); strings[argc] = NULL; for (i = 0; i < argc; i++) { GList *list_head = *list; d(fprintf (stderr, "\tAND func: %s\n", (*list) ? (char *) (*list)->data : "(null)")); strings[argc - (i+1)] = (*list) ? (*list)->data : g_strdup (""); *list = g_list_remove_link (*list, *list); g_list_free_1 (list_head); } *list = g_list_prepend (*list, g_strjoinv (" ", strings)); d(fprintf (stderr, "%s\n", (char *) (*list)->data)); for (i = 0 ; i < argc; i ++) g_free (strings[i]); g_free (strings); } r = e_sexp_result_new (ESEXP_RES_BOOL); r->value.bool = FALSE; return r; } static ESExpResult * func_or (struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) { GList **list = data; ESExpResult *r; d(fprintf (stderr, "in OR func (argc = %d)\n", argc)); if (argc == 2 && (*list)->data && (*list)->next && (*list)->next->data) { char **strings; int i; strings = g_new (char*, argc+2); strings[0] = g_strdup ("OR"); strings[argc+2 - 1] = NULL; for (i = 0; i < 2; i++) { GList *list_head = *list; d(fprintf (stderr, "\tOR func: %s\n", (*list) ? (char *) (*list)->data : "(null)")); strings[argc - i] = (*list) ? (*list)->data : g_strdup (""); *list = g_list_remove_link (*list, *list); g_list_free_1 (list_head); } *list = g_list_prepend (*list, g_strjoinv (" ", strings)); d(fprintf (stderr, "%s\n", (char *) (*list)->data)); for (i = 0 ; i < argc + 2; i ++) g_free (strings[i]); g_free (strings); } r = e_sexp_result_new (ESEXP_RES_BOOL); r->value.bool = FALSE; return r; } static ESExpResult * func_not (struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) { GList **list = data; ESExpResult *r; d(fprintf (stderr, "in NOT func\n")); /* just replace the head of the list with the NOT of it. */ if (argc > 0) { char *term = (*list)->data; (*list)->data = g_strdup_printf ("NOT %s", term); d(fprintf (stderr, "%s\n", (char *) (*list)->data)); g_free (term); } r = e_sexp_result_new (ESEXP_RES_BOOL); r->value.bool = FALSE; return r; } static char *tz_months [] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static char * format_date (time_t time, int offset) { struct tm tm; time += ((offset / 100) * (60*60)) + (offset % 100)*60; d(printf("converting date %s", ctime (&time))); memcpy (&tm, gmtime (&time), sizeof (tm)); return g_strdup_printf ("%d-%s-%04d", tm.tm_mday, tz_months[tm.tm_mon], tm.tm_year + 1900); } static ESExpResult * func_lt (struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) { GList **list = data; char *type = (*list)->data; time_t date = (time_t) (argv[1])->value.number; ESExpResult *r; d(fprintf (stderr, "in less-than func: (%d) (%s) (%d)\n", argc, type, (int) date)); if (argc > 0) { char *string, *date_str; date_str = format_date (date, 0); if (!strcmp ("SENT", type)) { string = g_strdup_printf ("SENTBEFORE \"%s\"", date_str); } else { string = g_strdup_printf ("BEFORE \"%s\"", date_str); } (*list)->data = string; g_free (type); } r = e_sexp_result_new (ESEXP_RES_BOOL); r->value.bool = FALSE; return r; } static ESExpResult * func_gt (struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) { GList **list = data; char *type = (*list)->data; time_t date = (time_t) (argv[1])->value.number; ESExpResult *r; d(fprintf (stderr, "in greater-than func: (%d) (%s) (%d)\n", argc, type, (int) date)); if (argc > 0) { char *string, *date_str; date_str = format_date (date, 0); if (!strcmp ("SENT", type)) { string = g_strdup_printf ("SENTSINCE \"%s\"", date_str); } else { string = g_strdup_printf ("SINCE \"%s\"", date_str); } (*list)->data = string; g_free (type); } r = e_sexp_result_new (ESEXP_RES_BOOL); r->value.bool = FALSE; return r; } static ESExpResult * func_eq (struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) { GList **list = data; char *type = (*list)->data; time_t date = (time_t) (argv[1])->value.number; ESExpResult *r; d(fprintf (stderr, "in equal-to func: (%d) (%s) (%d)\n", argc, type, (int) date)); if (argc > 0) { char *string, *date_str; date_str = format_date (date, 0); if (!strcmp ("SENT", type)) { string = g_strdup_printf ("SENTON \"%s\"", date_str); } else { string = g_strdup_printf ("ON \"%s\"", date_str); } (*list)->data = string; g_free (type); } r = e_sexp_result_new (ESEXP_RES_BOOL); r->value.bool = FALSE; return r; } static ESExpResult * func_match_all (struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) { /* match-all doesn't have a IMAP equiv */ ESExpResult *r; r = e_sexp_result_new (ESEXP_RES_BOOL); r->value.bool = FALSE; return r; } static ESExpResult * func_body_contains (struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) { GList **list = data; char *value = (*argv)->value.string; ESExpResult *r; if (argc > 0) { char *string; string = g_strdup_printf ("BODY \"%s\"", value); *list = g_list_prepend (*list, string); } r = e_sexp_result_new (ESEXP_RES_BOOL); r->value.bool = FALSE; return r; } static ESExpResult * func_header_contains (struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) { GList **list = data; char *header = (argv[0])->value.string; char *match = (argv[1])->value.string; ESExpResult *r; if (argc == 2) { char *string; string = g_strdup_printf ("HEADER %s \"%s\"", header, match); *list = g_list_prepend (*list, string); } r = e_sexp_result_new (ESEXP_RES_BOOL); r->value.bool = FALSE; return r; } static ESExpResult * func_user_tag (struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) { /* FIXME: what do I do here? */ ESExpResult *r; r = e_sexp_result_new (ESEXP_RES_BOOL); r->value.bool = FALSE; return r; } static ESExpResult * func_user_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) { /* FIXME: what do I do here? */ ESExpResult *r; r = e_sexp_result_new (ESEXP_RES_BOOL); r->value.bool = FALSE; return r; } static ESExpResult * func_get_sent_date (struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) { GList **list = data; ESExpResult *r; *list = g_list_prepend (*list, g_strdup ("SENT")); r = e_sexp_result_new (ESEXP_RES_BOOL); r->value.bool = FALSE; return r; } static ESExpResult * func_get_received_date (struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) { GList **list = data; ESExpResult *r; *list = g_list_prepend (*list, g_strdup ("RECEIVED")); r = e_sexp_result_new (ESEXP_RES_BOOL); r->value.bool = FALSE; return r; } static ESExpResult * func_get_current_date (struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) { ESExpResult *r; r = e_sexp_result_new (ESEXP_RES_INT); r->value.number = time (NULL); return r; } /* builtin functions */ static struct { char *name; ESExpFunc *func; int type; /* set to 1 if a function can perform shortcut evaluation, or doesn't execute everything, 0 otherwise */ } symbols[] = { { "and", (ESExpFunc *) func_and, 0 }, { "or", (ESExpFunc *) func_or, 0 }, { "not", (ESExpFunc *) func_not, 0 }, { "<", (ESExpFunc *) func_lt, 0 }, { ">", (ESExpFunc *) func_gt, 0 }, { "=", (ESExpFunc *) func_eq, 0 }, { "match-all", (ESExpFunc *) func_match_all, 0 }, { "body-contains", (ESExpFunc *) func_body_contains, 0 }, { "header-contains", (ESExpFunc *) func_header_contains, 0 }, { "user-tag", (ESExpFunc *) func_user_tag, 1 }, { "user-flag", (ESExpFunc *) func_user_flag, 1 }, { "get-sent-date", (ESExpFunc *) func_get_sent_date, 1 }, { "get-received-date", (ESExpFunc *) func_get_received_date, 1 }, { "get-current-date", (ESExpFunc *) func_get_current_date, 1 } }; char * imap_translate_sexp (const char *expression) { ESExp *sexp; ESExpResult *r; gchar *retval; GList *list = NULL; int i; sexp = e_sexp_new (); for (i = 0; i < sizeof (symbols) / sizeof (symbols[0]); i++) { if (symbols[i].type == 1) { e_sexp_add_ifunction (sexp, 0, symbols[i].name, (ESExpIFunc *)symbols[i].func, &list); } else { e_sexp_add_function (sexp, 0, symbols[i].name, symbols[i].func, &list); } } e_sexp_input_text (sexp, expression, strlen (expression)); e_sexp_parse (sexp); r = e_sexp_eval (sexp); gtk_object_unref (GTK_OBJECT (sexp)); e_sexp_result_free (r); if (list->next) { g_warning ("conversion to IMAP SEARCH string failed"); retval = NULL; g_list_foreach (list, (GFunc)g_free, NULL); } else { retval = list->data; } g_list_free (list); return retval; } char * imap_create_flag_list (guint32 flags) { GString *gstr; char *flag_list; gstr = g_string_new ("("); if (flags & CAMEL_MESSAGE_ANSWERED) g_string_append (gstr, "\\Answered "); if (flags & CAMEL_MESSAGE_DELETED) g_string_append (gstr, "\\Deleted "); if (flags & CAMEL_MESSAGE_DRAFT) g_string_append (gstr, "\\Draft "); if (flags & CAMEL_MESSAGE_FLAGGED) g_string_append (gstr, "\\Flagged "); if (flags & CAMEL_MESSAGE_SEEN) g_string_append (gstr, "\\Seen "); if (gstr->str[gstr->len - 1] == ' ') gstr->str[gstr->len - 1] = ')'; else g_string_append_c (gstr, ')'); flag_list = gstr->str; g_string_free (gstr, FALSE); return flag_list; } guint32 imap_parse_flag_list (const char *flag_list) { guint32 flags = 0; int len; if (*flag_list++ != '(') return 0; while (*flag_list != ')') { len = strcspn (flag_list, " )"); if (!g_strncasecmp (flag_list, "\\Answered", len)) flags |= CAMEL_MESSAGE_ANSWERED; else if (!g_strncasecmp (flag_list, "\\Deleted", len)) flags |= CAMEL_MESSAGE_DELETED; else if (!g_strncasecmp (flag_list, "\\Draft", len)) flags |= CAMEL_MESSAGE_DRAFT; else if (!g_strncasecmp (flag_list, "\\Flagged", len)) flags |= CAMEL_MESSAGE_FLAGGED; else if (!g_strncasecmp (flag_list, "\\Seen", len)) flags |= CAMEL_MESSAGE_SEEN; flag_list += len; if (*flag_list == ' ') flag_list++; } return flags; } /** * imap_parse_nstring: * @str_p: a pointer to a string * @len: a pointer to an int to return the length in * * This parses an "nstring" (NIL, a quoted string, or a literal) * starting at *@str_p. On success, *@str_p will point to the first * character after the end of the nstring, and *@len will contain * the length of the returned string. On failure, *@str_p will be * set to %NULL. * * This assumes that the string is in the form returned by * camel_imap_command(): that line breaks are indicated by LF rather * than CRLF. * * Return value: the parsed string, or %NULL if a NIL or no string * was parsed. (In the former case, *@str_p will be %NULL; in the * latter, it will point to the character after the NIL.) **/ char * imap_parse_nstring (char **str_p, int *len) { char *str = *str_p; char *out; if (!str) return NULL; else if (*str == '"') { char *p; int size; str++; size = strcspn (str, "\"") + 1; p = out = g_malloc (size); while (*str && *str != '"') { if (*str == '\\') str++; *p++ = *str++; if (p - out == size) { out = g_realloc (out, size * 2); p = out + size; size *= 2; } } if (*str != '"') { *str_p = NULL; g_free (out); return NULL; } *p = '\0'; *str_p = str + 1; *len = strlen (out); return out; } else if (*str == '{') { *len = strtoul (str + 1, (char **)&str, 10); if (*str++ != '}' || *str++ != '\n' || strlen (str) < *len) { *str_p = NULL; return NULL; } out = g_strndup (str, *len); *str_p = str + *len; return out; } else if (!g_strncasecmp (str, "nil", 3)) { *str_p += 3; *len = 0; return NULL; } else { *str_p = NULL; return NULL; } } /** * imap_parse_astring: * @str_p: a pointer to a string * @len: a pointer to an int to return the length in * * This parses an "astring" (an atom, a quoted string, or a literal) * starting at *@str_p. On success, *@str_p will point to the first * character after the end of the astring, and *@len will contain * the length of the returned string. On failure, *@str_p will be * set to %NULL. * * This assumes that the string is in the form returned by * camel_imap_command(): that line breaks are indicated by LF rather * than CRLF. * * Return value: the parsed string, or %NULL if no string * was parsed. (In this case, *@str_p will also be %NULL.) **/ char * imap_parse_astring (char **str_p, int *len) { char *p; if (**str_p == '{' || **str_p == '"') return imap_parse_nstring (str_p, len); p = *str_p; while (isascii ((unsigned char)*p) && !strchr ("(){ \"\\%*", *p)) p++; *len = p - *str_p; p = g_strndup (*str_p, *len); *str_p += *len; return p; } /** * imap_quote_string: * @str: the string to quote, which must not contain CR or LF * * Return value: an IMAP "quoted" corresponding to the string, which * the caller must free. **/ char * imap_quote_string (const char *str) { const char *p; char *quoted, *q; int len; len = strlen (str); p = str; while ((p = strpbrk (p, "\"\\"))) { len++; p++; } quoted = q = g_malloc (len + 3); *q++ = '"'; while ((p = strpbrk (str, "\"\\"))) { memcpy (q, str, p - str); q += p - str; *q++ = '\\'; *q++ = *p++; str = p; } sprintf (q, "%s\"", str); return quoted; }