/* Copyright (c) 2004-2006 NFG Net Facilities Group BV support@nfg.nl 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** * \file dbmail-mailbox.c * * implements DbmailMailbox object */ #include "dbmail.h" #define THIS_MODULE "mailbox" extern db_param_t _db_params; #define DBPFX _db_params.pfx /* internal utilities */ /* class methods */ struct DbmailMailbox * dbmail_mailbox_new(u64_t id) { struct DbmailMailbox *self = g_new0(struct DbmailMailbox, 1); assert(self); dbmail_mailbox_set_id(self,id); dbmail_mailbox_set_uid(self, FALSE); self->search = NULL; self->fi = NULL; if (dbmail_mailbox_open(self)) { TRACE(TRACE_ERROR,"opening mailbox failed"); dbmail_mailbox_free(self); return NULL; } return self; } static gboolean _node_free(GNode *node, gpointer dummy UNUSED) { search_key_t *s = (search_key_t *)node->data; if (s->found) g_tree_destroy(s->found); g_free(s); return FALSE; } void dbmail_mailbox_free(struct DbmailMailbox *self) { if (self->sorted) g_list_destroy(self->sorted); if (self->ids) g_tree_destroy(self->ids); if (self->msn) g_tree_destroy(self->msn); if (self->search) { g_node_traverse(g_node_get_root(self->search), G_POST_ORDER, G_TRAVERSE_ALL, -1, (GNodeTraverseFunc)_node_free, NULL); g_node_destroy(self->search); } if (self->fi) { if (self->fi->bodyfetch) // FIXME: Should we be using dbmail-imapsession.c: _body_fetch_free? g_list_foreach(self->fi->bodyfetch, (GFunc)g_free, NULL); g_free(self->fi); self->fi = NULL; } if (self->charset) { g_free(self->charset); self->charset = NULL; } g_free(self); } void dbmail_mailbox_set_id(struct DbmailMailbox *self, u64_t id) { assert(id > 0); self->id = id; } u64_t dbmail_mailbox_get_id(struct DbmailMailbox *self) { assert(self->id > 0); return self->id; } void dbmail_mailbox_set_uid(struct DbmailMailbox *self, gboolean uid) { self->uid = uid; } gboolean dbmail_mailbox_get_uid(struct DbmailMailbox *self) { return self->uid; } static void uid_msn_map(struct DbmailMailbox *self) { GList *ids = NULL; u64_t *id, *msn = NULL; ids = g_tree_keys(self->ids); if (self->msn) g_tree_destroy(self->msn); self->msn = g_tree_new_full((GCompareDataFunc)ucmp,NULL,NULL,NULL); self->rows = 1; ids = g_list_first(ids); while (ids) { id = (u64_t *)ids->data; msn = g_tree_lookup(self->ids, id); *msn = self->rows++; g_tree_insert(self->msn, msn, id); if (! g_list_next(ids)) break; ids = g_list_next(ids); } g_list_free(g_list_first(ids)); TRACE(TRACE_DEBUG,"total [%d] UIDs", g_tree_nnodes(self->ids)); TRACE(TRACE_DEBUG,"total [%d] MSNs", g_tree_nnodes(self->msn)); } void mailbox_uid_msn_new(struct DbmailMailbox *self) { if (self->ids) g_tree_destroy(self->ids); if (self->msn) g_tree_destroy(self->msn); self->ids = NULL; self->msn = NULL; self->ids = g_tree_new_full((GCompareDataFunc)ucmp,NULL,(GDestroyNotify)g_free,(GDestroyNotify)g_free); self->msn = g_tree_new_full((GCompareDataFunc)ucmp,NULL,NULL,NULL); self->rows = 1; } static void mailbox_build_uid_map(struct DbmailMailbox *self) { int i, rows; u64_t *id, *msn; mailbox_uid_msn_new(self); rows = db_num_rows(); for (i=0; i< rows; i++) { id = g_new0(u64_t,1); *id = db_get_result_u64(i,0); msn = g_new0(u64_t,1); *msn = i+1; g_tree_insert(self->ids,id,msn); g_tree_insert(self->msn,msn,id); } TRACE(TRACE_DEBUG,"ids [%d], msn [%d]", g_tree_nnodes(self->ids), g_tree_nnodes(self->msn)); } int dbmail_mailbox_open(struct DbmailMailbox *self) { GString *q = g_string_new(""); g_string_printf(q, "SELECT message_idnr FROM %smessages " "WHERE mailbox_idnr = %llu " "AND status IN (%d,%d) " "ORDER BY message_idnr", DBPFX, dbmail_mailbox_get_id(self), MESSAGE_STATUS_NEW, MESSAGE_STATUS_SEEN); if (db_query(q->str) == DM_EQUERY) { g_string_free(q,TRUE); return DM_EQUERY; } g_string_free(q,TRUE); mailbox_build_uid_map(self); db_free_result(); return DM_SUCCESS; } int dbmail_mailbox_remove_uid(struct DbmailMailbox *self, u64_t *id) { if (! g_tree_remove(self->ids, id)) { TRACE(TRACE_ERROR,"trying to remove unknown UID [%llu]", *id); return DM_EGENERAL; } uid_msn_map(self); return DM_SUCCESS; } #define FROM_STANDARD_DATE "Tue Oct 11 13:06:24 2005" static size_t dump_message_to_stream(struct DbmailMessage *message, GMimeStream *ostream) { size_t r = 0; gchar *s, *d; GString *sender; GString *date; InternetAddressList *ialist; InternetAddress *ia; GString *t; g_return_val_if_fail(GMIME_IS_MESSAGE(message->content),0); s = dbmail_message_to_string(message); if (! strncmp(s,"From ",5)==0) { ialist = internet_address_parse_string(g_mime_message_get_sender(GMIME_MESSAGE(message->content))); sender = g_string_new("nobody@foo"); if (ialist) { ia = ialist->address; if (ia) { g_strstrip(g_strdelimit(ia->value.addr,"\"",' ')); g_string_printf(sender,"%s", ia->value.addr); } } internet_address_list_destroy(ialist); d = dbmail_message_get_internal_date(message, 0); date = g_string_new(d); g_free(d); if (date->len < 1) date = g_string_new(FROM_STANDARD_DATE); t = g_string_new("From "); g_string_append_printf(t,"%s %s\n", sender->str, date->str); r = g_mime_stream_write_string(ostream,t->str); g_string_free(t,TRUE); g_string_free(sender,TRUE); g_string_free(date,TRUE); } r += g_mime_stream_write_string(ostream,s); r += g_mime_stream_write_string(ostream,"\n"); g_free(s); return r; } /* Caller must fclose the file pointer itself. */ int dbmail_mailbox_dump(struct DbmailMailbox *self, FILE *file) { unsigned i,j; int count=0; gboolean h; GMimeStream *ostream; GList *ids, *cids = NULL, *slice, *topslice; struct DbmailMessage *message = NULL; GString *q, *t; const char *internal_date = NULL; char * date2char; if (self->ids==NULL || g_tree_nnodes(self->ids) == 0) { TRACE(TRACE_DEBUG,"cannot dump empty mailbox"); return 0; } assert(self->ids); q = g_string_new(""); t = g_string_new(""); ostream = g_mime_stream_file_new(file); g_mime_stream_file_set_owner ((GMimeStreamFile *)ostream, FALSE); ids = g_tree_keys(self->ids); while (ids) { cids = g_list_append(cids,g_strdup_printf("%llu", *(u64_t *)ids->data)); if (! g_list_next(ids)) break; ids = g_list_next(ids); } topslice = g_list_slices(cids,100); slice = g_list_first(topslice); g_list_destroy(cids); g_list_free(g_list_first(ids)); date2char = date2char_str("internal_date"); while (slice) { g_string_printf(q,"SELECT is_header,messageblk,%s FROM %smessageblks b " "JOIN %sphysmessage p ON b.physmessage_id = p.id " "JOIN %smessages m USING (physmessage_id) " "WHERE message_idnr IN (%s) " "ORDER BY messageblk_idnr ", date2char, DBPFX, DBPFX, DBPFX, (char *)slice->data); if (db_query(q->str) == -1) { g_string_free(t,TRUE); g_string_free(q,TRUE); g_object_unref(ostream); g_list_destroy(topslice); return -1; } if ((j = db_num_rows()) < 1) break; for (i=0; ilen > 0) { message = dbmail_message_new(); message = dbmail_message_init_with_string(message,t); dbmail_message_set_internal_date(message, (char *)internal_date); if(dump_message_to_stream(message,ostream) > 0) count++; dbmail_message_free(message); } g_string_printf(t,"%s", db_get_result(i,1)); } else { g_string_append(t, db_get_result(i,1)); } internal_date = db_get_result(i,2); } db_free_result(); if (! g_list_next(slice)) break; slice = g_list_next(slice); } g_free(date2char); if (t->len) { message = dbmail_message_new(); message = dbmail_message_init_with_string(message,t); dbmail_message_set_internal_date(message, (char *)internal_date); if (dump_message_to_stream(message, ostream) > 0) count++; dbmail_message_free(message); } g_list_destroy(topslice); g_string_free(t,TRUE); g_string_free(q,TRUE); g_object_unref(ostream); return count; } static gboolean _tree_foreach(gpointer key UNUSED, gpointer value, GString * data) { gboolean res = FALSE; u64_t *id; GList *sublist = g_list_first((GList *)value); GString *t = g_string_new(""); int m = g_list_length(sublist); sublist = g_list_first(sublist); while(sublist) { id = sublist->data; g_string_append_printf(t, "(%llu)", *id); if (! g_list_next(sublist)) break; sublist = g_list_next(sublist); } if (m > 1) g_string_append_printf(data, "(%s)", t->str); else g_string_append_printf(data, "%s", t->str); g_string_free(t,TRUE); return res; } char * dbmail_mailbox_orderedsubject(struct DbmailMailbox *self) { GList *sublist = NULL; GString *q = g_string_new(""); u64_t i = 0, r = 0, idnr = 0; char *subj; char *res = NULL; u64_t *id, *msn; GTree *tree; GString *threads; /* thread-roots (ordered) */ g_string_printf(q, "SELECT min(message_idnr),subjectfield " "FROM %smessages " "JOIN %ssubjectfield USING (physmessage_id) " "JOIN %sdatefield USING (physmessage_id) " "WHERE mailbox_idnr=%llu " "AND status IN (%d, %d) " "GROUP BY subjectfield", DBPFX, DBPFX, DBPFX, dbmail_mailbox_get_id(self), MESSAGE_STATUS_NEW, MESSAGE_STATUS_SEEN); if (db_query(q->str) == DM_EQUERY) { g_string_free(q,TRUE); return res; } if ((r = db_num_rows())==0) { g_string_free(q,TRUE); db_free_result(); return res; } tree = g_tree_new_full((GCompareDataFunc)strcmp,NULL,(GDestroyNotify)g_free, NULL); i=0; while (i < r) { idnr = db_get_result_u64(i,0); if (! g_tree_lookup(self->ids,&idnr)) { i++; continue; } subj = (char *)db_get_result(i,1); g_tree_insert(tree,g_strdup(subj), NULL); i++; } db_free_result(); /* full threads (unordered) */ g_string_printf(q, "SELECT message_idnr,subjectfield " "FROM %smessages " "JOIN %ssubjectfield using (physmessage_id) " "JOIN %sdatefield using (physmessage_id) " "WHERE mailbox_idnr=%llu " "AND status IN (%d,%d) " "ORDER BY subjectfield,datefield", DBPFX, DBPFX, DBPFX, dbmail_mailbox_get_id(self), MESSAGE_STATUS_NEW,MESSAGE_STATUS_SEEN); if (db_query(q->str) == DM_EQUERY) { g_string_free(q,TRUE); return res; } if ((r = db_num_rows())==0) { g_string_free(q,TRUE); db_free_result(); return res; } i=0; while (i < r) { idnr = db_get_result_u64(i,0); if (! (msn = g_tree_lookup(self->ids, &idnr))) { i++; continue; } subj = (char *)db_get_result(i,1); id = g_new0(u64_t,1); if (dbmail_mailbox_get_uid(self)) *id = idnr; else *id = *msn; sublist = g_tree_lookup(tree,(gconstpointer)subj); sublist = g_list_append(sublist,id); g_tree_insert(tree,g_strdup(subj),sublist); i++; } db_free_result(); threads = g_string_new(""); g_tree_foreach(tree,(GTraverseFunc)_tree_foreach,threads); res = threads->str; g_string_free(threads,FALSE); g_string_free(q,TRUE); return res; } /* * return self->ids as a string */ char * dbmail_mailbox_ids_as_string(struct DbmailMailbox *self) { GString *t; gchar *s = NULL; GList *l = NULL, *h = NULL; if ((self->ids == NULL) || g_tree_nnodes(self->ids) <= 0) { TRACE(TRACE_DEBUG,"no ids found"); return s; } t = g_string_new(""); switch (dbmail_mailbox_get_uid(self)) { case TRUE: l = g_tree_keys(self->ids); break; case FALSE: l = g_tree_values(self->ids); break; } h = l; while(l->data) { g_string_append_printf(t,"%llu ", *(u64_t *)l->data); if (! g_list_next(l)) break; l = g_list_next(l); } g_list_free(h); s = t->str; g_string_free(t,FALSE); return g_strchomp(s); } char * dbmail_mailbox_sorted_as_string(struct DbmailMailbox *self) { GString *t; gchar *s = NULL; GList *l = NULL; gboolean uid; u64_t *msn; l = g_list_first(self->sorted); if (! g_list_length(l)>0) return s; t = g_string_new(""); uid = dbmail_mailbox_get_uid(self); while(l->data) { msn = g_tree_lookup(self->ids, l->data); if (msn) { if (uid) g_string_append_printf(t,"%llu ", *(u64_t *)l->data); else g_string_append_printf(t,"%llu ", *(u64_t *)msn); } if (! g_list_next(l)) break; l = g_list_next(l); } s = t->str; g_string_free(t,FALSE); return g_strchomp(s); } /* imap sorted search */ static int append_search(struct DbmailMailbox *self, search_key_t *value, gboolean descend) { GNode *n; if (self->search) n = g_node_append_data(self->search, value); else { descend = TRUE; n = g_node_new(value); } if (descend) self->search = n; TRACE(TRACE_DEBUG, "[%p] leaf [%d] type [%d] field [%s] search [%s] at depth [%u]\n", value, G_NODE_IS_LEAF(n), value->type, value->hdrfld, value->search, g_node_depth(self->search)); return 0; } static void _append_join(char *join, char *table) { char *tmp; TRACE(TRACE_DEBUG,"%s", table); tmp = g_strdup_printf("LEFT JOIN %s%s ON m.physmessage_id=%s%s.physmessage_id ", DBPFX, table, DBPFX, table); g_strlcat(join, tmp, MAX_SEARCH_LEN); g_free(tmp); } static void _append_sort(char *order, char *field, gboolean reverse) { char *tmp; tmp = g_strdup_printf("%s%s,", field, reverse ? " DESC" : ""); TRACE(TRACE_DEBUG,"%s", tmp); g_strlcat(order, tmp, MAX_SEARCH_LEN); g_free(tmp); } static int _handle_sort_args(struct DbmailMailbox *self, char **search_keys, search_key_t *value, u64_t *idx) { value->type = IST_SORT; gboolean reverse = FALSE; if (! (search_keys && search_keys[*idx])) return -1; char *key = search_keys[*idx]; if ( MATCH(key, "reverse") ) { reverse = TRUE; (*idx)++; key = search_keys[*idx]; } if ( MATCH(key, "arrival") ) { _append_sort(value->order, "internal_date", reverse); (*idx)++; } else if ( MATCH(key, "size") ) { _append_sort(value->order, "messagesize", reverse); (*idx)++; } else if ( MATCH(key, "from") ) { _append_join(value->table, "fromfield"); _append_sort(value->order, "fromaddr", reverse); (*idx)++; } else if ( MATCH(key, "subject") ) { _append_join(value->table, "subjectfield"); _append_sort(value->order, "subjectfield", reverse); (*idx)++; } else if ( MATCH(key, "cc") ) { _append_join(value->table, "ccfield"); _append_sort(value->order, "ccaddr", reverse); (*idx)++; } else if ( MATCH(key, "to") ) { _append_join(value->table, "tofield"); _append_sort(value->order, "toaddr", reverse); (*idx)++; } else if ( MATCH(key, "date") ) { _append_join(value->table, "datefield"); _append_sort(value->order, "datefield", reverse); (*idx)++; } else if ( MATCH(key, "(") ) (*idx)++; else if ( MATCH(key, ")") ) (*idx)++; else if ( MATCH(key, "utf-8") ) { (*idx)++; append_search(self, value, 0); return 1; } else if ( MATCH(key, "us-ascii") ) { (*idx)++; append_search(self, value, 0); return 1; } else if ( MATCH(key, "iso-8859-1") ) { (*idx)++; append_search(self, value, 0); return 1; } else return -1; /* done */ return 0; } static void pop_search(struct DbmailMailbox *self) { // switch back to parent if (self->search && self->search->parent) self->search = self->search->parent; } static int _handle_search_args(struct DbmailMailbox *self, char **search_keys, u64_t *idx) { int result = 0; if (! (search_keys && search_keys[*idx])) return 1; char *p = NULL, *t = NULL, *key = search_keys[*idx]; search_key_t *value = g_new0(search_key_t,1); /* SEARCH */ if ( MATCH(key, "all") ) { value->type = IST_UIDSET; strcpy(value->search, "1:*"); (*idx)++; } else if ( MATCH(key, "uid") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); g_return_val_if_fail(check_msg_set(search_keys[*idx + 1]),-1); value->type = IST_UIDSET; (*idx)++; strncpy(value->search, search_keys[(*idx)], MAX_SEARCH_LEN); (*idx)++; } /* * FLAG search keys */ else if ( MATCH(key, "answered") ) { value->type = IST_FLAG; strncpy(value->search, "answered_flag=1", MAX_SEARCH_LEN); (*idx)++; } else if ( MATCH(key, "deleted") ) { value->type = IST_FLAG; strncpy(value->search, "deleted_flag=1", MAX_SEARCH_LEN); (*idx)++; } else if ( MATCH(key, "flagged") ) { value->type = IST_FLAG; strncpy(value->search, "flagged_flag=1", MAX_SEARCH_LEN); (*idx)++; } else if ( MATCH(key, "recent") ) { value->type = IST_FLAG; strncpy(value->search, "recent_flag=1", MAX_SEARCH_LEN); (*idx)++; } else if ( MATCH(key, "seen") ) { value->type = IST_FLAG; strncpy(value->search, "seen_flag=1", MAX_SEARCH_LEN); (*idx)++; } else if ( MATCH(key, "keyword") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); value->type = IST_SET; (*idx)++; strcpy(value->search, "0"); (*idx)++; } else if ( MATCH(key, "draft") ) { value->type = IST_FLAG; strncpy(value->search, "draft_flag=1", MAX_SEARCH_LEN); (*idx)++; } else if ( MATCH(key, "new") ) { value->type = IST_FLAG; strncpy(value->search, "(seen_flag=0 AND recent_flag=1)", MAX_SEARCH_LEN); (*idx)++; } else if ( MATCH(key, "old") ) { value->type = IST_FLAG; strncpy(value->search, "recent_flag=0", MAX_SEARCH_LEN); (*idx)++; } else if ( MATCH(key, "unanswered") ) { value->type = IST_FLAG; strncpy(value->search, "answered_flag=0", MAX_SEARCH_LEN); (*idx)++; } else if ( MATCH(key, "undeleted") ) { value->type = IST_FLAG; strncpy(value->search, "deleted_flag=0", MAX_SEARCH_LEN); (*idx)++; } else if ( MATCH(key, "unflagged") ) { value->type = IST_FLAG; strncpy(value->search, "flagged_flag=0", MAX_SEARCH_LEN); (*idx)++; } else if ( MATCH(key, "unseen") ) { value->type = IST_FLAG; strncpy(value->search, "seen_flag=0", MAX_SEARCH_LEN); (*idx)++; } else if ( MATCH(key, "unkeyword") ) { g_return_val_if_fail(search_keys[(*idx) + 1],-1); value->type = IST_SET; (*idx)++; strcpy(value->search, "1:*"); (*idx)++; } else if ( MATCH(key, "undraft") ) { value->type = IST_FLAG; strncpy(value->search, "draft_flag=0", MAX_SEARCH_LEN); (*idx)++; } /* * HEADER search keys */ #define IMAP_SET_SEARCH (*idx)++; \ if ((p = dbmail_iconv_str_to_db((const char *)search_keys[*idx], self->charset)) != NULL) { \ t = dm_stresc(p); \ g_free(p); \ } else { \ TRACE(TRACE_WARNING, "search_key [%s] is not charset [%s]", search_keys[*idx], self->charset); \ t = dm_stresc(search_keys[*idx]); \ } \ strncpy(value->search, t, MAX_SEARCH_LEN); \ g_free(t); \ (*idx)++ else if ( MATCH(key, "bcc") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); value->type = IST_HDR; strncpy(value->hdrfld, "bcc", MIME_FIELD_MAX); IMAP_SET_SEARCH; } else if ( MATCH(key, "cc") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); value->type = IST_HDR; strncpy(value->hdrfld, "cc", MIME_FIELD_MAX); IMAP_SET_SEARCH; } else if ( MATCH(key, "from") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); value->type = IST_HDR; strncpy(value->hdrfld, "from", MIME_FIELD_MAX); IMAP_SET_SEARCH; } else if ( MATCH(key, "to") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); value->type = IST_HDR; strncpy(value->hdrfld, "to", MIME_FIELD_MAX); IMAP_SET_SEARCH; } else if ( MATCH(key, "subject") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); value->type = IST_HDR; strncpy(value->hdrfld, "subject", MIME_FIELD_MAX); IMAP_SET_SEARCH; } else if ( MATCH(key, "header") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); g_return_val_if_fail(search_keys[*idx + 2], -1); value->type = IST_HDR; t = dm_stresc(search_keys[*idx + 1]); strncpy(value->hdrfld, t, MIME_FIELD_MAX); g_free(t); t = dm_stresc(search_keys[*idx + 2]); strncpy(value->search, t, MAX_SEARCH_LEN); g_free(t); (*idx) += 3; } else if ( MATCH(key, "sentbefore") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); value->type = IST_HDRDATE_BEFORE; strncpy(value->hdrfld, "datefield", MIME_FIELD_MAX); IMAP_SET_SEARCH; } else if ( MATCH(key, "senton") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); value->type = IST_HDRDATE_ON; strncpy(value->hdrfld, "datefield", MIME_FIELD_MAX); IMAP_SET_SEARCH; } else if ( MATCH(key, "sentsince") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); value->type = IST_HDRDATE_SINCE; strncpy(value->hdrfld, "datefield", MIME_FIELD_MAX); IMAP_SET_SEARCH; } /* * INTERNALDATE keys */ else if ( MATCH(key, "before") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); g_return_val_if_fail(check_date(search_keys[*idx + 1]),-1); value->type = IST_IDATE; (*idx)++; g_snprintf(value->search, MAX_SEARCH_LEN, "internal_date < '%s'", date_imap2sql(search_keys[*idx])); (*idx)++; } else if ( MATCH(key, "on") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); g_return_val_if_fail(check_date(search_keys[*idx + 1]),-1); value->type = IST_IDATE; (*idx)++; g_snprintf(value->search, MAX_SEARCH_LEN, "internal_date %s '%s%%'", db_get_sql(SQL_SENSITIVE_LIKE), date_imap2sql(search_keys[*idx])); (*idx)++; } else if ( MATCH(key, "since") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); g_return_val_if_fail(check_date(search_keys[*idx + 1]),-1); value->type = IST_IDATE; (*idx)++; g_snprintf(value->search, MAX_SEARCH_LEN, "internal_date > '%s'", date_imap2sql(search_keys[*idx])); (*idx)++; } /* * DATA-keys */ else if ( MATCH(key, "body") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); value->type = IST_DATA_BODY; IMAP_SET_SEARCH; } else if ( MATCH(key, "text") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); value->type = IST_DATA_TEXT; IMAP_SET_SEARCH; } /* * SIZE keys */ else if ( MATCH(key, "larger") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); value->type = IST_SIZE_LARGER; (*idx)++; value->size = strtoull(search_keys[(*idx)], NULL, 10); (*idx)++; } else if ( MATCH(key, "smaller") ) { g_return_val_if_fail(search_keys[*idx + 1], -1); value->type = IST_SIZE_SMALLER; (*idx)++; value->size = strtoull(search_keys[(*idx)], NULL, 10); (*idx)++; } /* * NOT, OR, () */ else if ( MATCH(key, "not") ) { char *nextkey; g_return_val_if_fail(search_keys[*idx + 1], -1); nextkey = search_keys[*idx+1]; if ( MATCH(nextkey, "answered") ) { value->type = IST_FLAG; strncpy(value->search, "answered_flag=0", MAX_SEARCH_LEN); (*idx)+=2; } else if ( MATCH(nextkey, "deleted") ) { value->type = IST_FLAG; strncpy(value->search, "deleted_flag=0", MAX_SEARCH_LEN); (*idx)+=2; } else if ( MATCH(nextkey, "flagged") ) { value->type = IST_FLAG; strncpy(value->search, "flagged_flag=0", MAX_SEARCH_LEN); (*idx)+=2; } else if ( MATCH(nextkey, "recent") ) { value->type = IST_FLAG; strncpy(value->search, "recent_flag=0", MAX_SEARCH_LEN); (*idx)+=2; } else if ( MATCH(nextkey, "seen") ) { value->type = IST_FLAG; strncpy(value->search, "seen_flag=0", MAX_SEARCH_LEN); (*idx)+=2; } else if ( MATCH(nextkey, "draft") ) { value->type = IST_FLAG; strncpy(value->search, "draft_flag=0", MAX_SEARCH_LEN); (*idx)+=2; } else if ( MATCH(nextkey, "new") ) { value->type = IST_FLAG; strncpy(value->search, "(seen_flag=1 AND recent_flag=0)", MAX_SEARCH_LEN); (*idx)+=2; } else if ( MATCH(nextkey, "old") ) { value->type = IST_FLAG; strncpy(value->search, "recent_flag=1", MAX_SEARCH_LEN); (*idx)+=2; } else { value->type = IST_SUBSEARCH_NOT; (*idx)++; append_search(self, value, 1); if ((result = _handle_search_args(self, search_keys, idx)) < 0) return result; pop_search(self); return 0; } } else if ( MATCH(key, "or") ) { value->type = IST_SUBSEARCH_OR; (*idx)++; append_search(self, value, 1); if ((result = _handle_search_args(self, search_keys, idx)) < 0) return result; if ((result = _handle_search_args(self, search_keys, idx)) < 0) return result; pop_search(self); return 0; } else if ( MATCH(key, "(") ) { value->type = IST_SUBSEARCH_AND; (*idx)++; append_search(self,value,1); while ((result = dbmail_mailbox_build_imap_search(self, search_keys, idx, 0)) == 0); pop_search(self); return 0; } else if ( MATCH(key, ")") ) { (*idx)++; g_free(value); return 1; } else if (check_msg_set(key)) { value->type = IST_SET; strncpy(value->search, key, MAX_SEARCH_LEN); (*idx)++; /* ignore the charset. Let the database handle this */ } else if ( MATCH(key, "charset") ) { (*idx)++;// FIXME: check for valid charset here self->charset = g_strdup(search_keys[*idx]); TRACE(TRACE_DEBUG,"using charset [%s] for searching", self->charset); (*idx)++; } else { /* unknown search key */ TRACE(TRACE_DEBUG,"unknown search key [%s]", key); g_free(value); return -1; } if (value->type) append_search(self, value, 0); else g_free(value); return 0; } /* * build_imap_search() * * builds a linked list of search items from a set of IMAP search keys * sl should be initialized; new search items are simply added to the list * * returns -1 on syntax error, -2 on memory error; 0 on success, 1 if ')' has been encountered */ int dbmail_mailbox_build_imap_search(struct DbmailMailbox *self, char **search_keys, u64_t *idx, search_order_t order) { int result = 0; search_key_t * value, * s; if (! (search_keys && search_keys[*idx])) return 1; /* default initial key for ANDing */ value = g_new0(search_key_t,1); if (self->uid) value->type = IST_UIDSET; else value->type = IST_SET; if (check_msg_set(search_keys[*idx])) { strncpy(value->search, search_keys[*idx], MAX_SEARCH_LEN); (*idx)++; } else { /* match all messages if no initial sequence set is defined */ strncpy(value->search, "1:*", MAX_SEARCH_LEN); } append_search(self, value, 0); /* SORT */ switch (order) { case SEARCH_SORTED: value = g_new0(search_key_t,1); value->type = IST_SORT; s = value; while(((result = _handle_sort_args(self, search_keys, value, idx)) == 0) && search_keys[*idx]); if (result < 0) g_free(s); break; case SEARCH_THREAD_ORDEREDSUBJECT: case SEARCH_THREAD_REFERENCES: (*idx)++; TRACE(TRACE_DEBUG,"search_key: [%s]", search_keys[*idx]); // eat the charset arg if (MATCH(search_keys[*idx],"utf-8")) (*idx)++; else if (MATCH(search_keys[*idx],"us-ascii")) (*idx)++; else if (MATCH(search_keys[*idx],"iso-8859-1")) (*idx)++; else return -1; break; case SEARCH_UNORDERED: default: // ignore break; } /* SEARCH */ while( search_keys[*idx] && ((result = _handle_search_args(self, search_keys, idx)) == 0) ); TRACE(TRACE_DEBUG,"done [%d] at idx [%llu]", result, *idx); return result; } static gboolean _do_sort(GNode *node, struct DbmailMailbox *self) { GString *q; u64_t tid, *id; unsigned i, rows; GTree *z; search_key_t *s = (search_key_t *)node->data; TRACE(TRACE_DEBUG,"type [%d]", s->type); if (s->type != IST_SORT) return FALSE; if (s->searched) return FALSE; q = g_string_new(""); g_string_printf(q, "SELECT message_idnr FROM %smessages m " "LEFT JOIN %sphysmessage p ON m.physmessage_id=p.id " "%s" "WHERE m.mailbox_idnr = %llu AND m.status IN (%d,%d) " "ORDER BY %smessage_idnr", DBPFX, DBPFX, s->table, dbmail_mailbox_get_id(self), MESSAGE_STATUS_NEW, MESSAGE_STATUS_SEEN, s->order); if (db_query(q->str) == -1) return TRUE; if (self->sorted) { g_list_destroy(self->sorted); self->sorted = NULL; } z = g_tree_new((GCompareFunc)ucmp); rows = db_num_rows(); for (i=0; i< rows; i++) { tid = db_get_result_u64(i,0); if (g_tree_lookup(self->ids,&tid) && (! g_tree_lookup(z, &tid))) { id = g_new0(u64_t,1); *id = tid; g_tree_insert(z, id, id); self->sorted = g_list_prepend(self->sorted,id); } } g_tree_destroy(z); self->sorted = g_list_reverse(self->sorted); g_string_free(q,TRUE); db_free_result(); s->searched = TRUE; return FALSE; } static GTree * mailbox_search(struct DbmailMailbox *self, search_key_t *s) { unsigned i, rows; char *qs, *date, *field; u64_t *k, *v, *w; u64_t id; char gt_lt = 0; const char *op; char partial[DEF_FRAGSIZE]; GString *t; GString *q; if (!s->search) return NULL; t = g_string_new(""); q = g_string_new(""); switch (s->type) { case IST_HDRDATE_ON: case IST_HDRDATE_SINCE: case IST_HDRDATE_BEFORE: field = g_strdup_printf(db_get_sql(SQL_TO_DATE), s->hdrfld); qs = g_strdup_printf("'%s'", date_imap2sql(s->search)); date = g_strdup_printf(db_get_sql(SQL_TO_DATE), qs); g_free(qs); if (s->type == IST_HDRDATE_SINCE) op = ">="; else if (s->type == IST_HDRDATE_BEFORE) op = "<"; else op = "="; g_string_printf(t,"%s %s %s", field, op, date); g_free(date); g_free(field); g_string_printf(q,"SELECT message_idnr FROM %smessages m " "JOIN %sphysmessage p ON m.physmessage_id=p.id " "JOIN %sdatefield d ON d.physmessage_id=p.id " "WHERE mailbox_idnr= %llu AND status IN (%d,%d) " "AND %s " "ORDER BY message_idnr", DBPFX, DBPFX, DBPFX, dbmail_mailbox_get_id(self), MESSAGE_STATUS_NEW, MESSAGE_STATUS_SEEN, t->str); break; case IST_HDR: memset(partial,0,sizeof(partial)); snprintf(partial, DEF_FRAGSIZE, db_get_sql(SQL_PARTIAL), "headervalue"); g_string_printf(q, "SELECT message_idnr FROM %smessages m " "JOIN %sphysmessage p ON m.physmessage_id=p.id " "JOIN %sheadervalue v ON v.physmessage_id=p.id " "JOIN %sheadername n ON v.headername_id=n.id " "WHERE mailbox_idnr = %llu " "AND status IN (%d,%d) " "AND headername %s '%s' AND %s %s '%%%s%%' " "ORDER BY message_idnr", DBPFX, DBPFX, DBPFX, DBPFX, dbmail_mailbox_get_id(self), MESSAGE_STATUS_NEW, MESSAGE_STATUS_SEEN, db_get_sql(SQL_INSENSITIVE_LIKE), s->hdrfld, partial, db_get_sql(SQL_INSENSITIVE_LIKE), s->search); break; case IST_DATA_TEXT: memset(partial,0,sizeof(partial)); snprintf(partial, DEF_FRAGSIZE, db_get_sql(SQL_PARTIAL), "headervalue"); g_string_printf(q, "SELECT message_idnr FROM %smessages m " "JOIN %sphysmessage p ON m.physmessage_id=p.id " "JOIN %sheadervalue v on v.physmessage_id=p.id " "WHERE mailbox_idnr = %llu " "AND status IN (%d,%d) " "AND %s %s '%%%s%%' " "ORDER BY message_idnr", DBPFX, DBPFX, DBPFX, dbmail_mailbox_get_id(self), MESSAGE_STATUS_NEW, MESSAGE_STATUS_SEEN, partial, db_get_sql(SQL_INSENSITIVE_LIKE), s->search); break; case IST_IDATE: g_string_printf(q, "SELECT message_idnr FROM %smessages m " "JOIN %sphysmessage p ON m.physmessage_id=p.id " "WHERE mailbox_idnr = %llu " "AND status IN (%d,%d) AND p.%s " "ORDER BY message_idnr", DBPFX, DBPFX, dbmail_mailbox_get_id(self), MESSAGE_STATUS_NEW, MESSAGE_STATUS_SEEN, s->search); break; case IST_DATA_BODY: g_string_printf(t,db_get_sql(SQL_ENCODE_ESCAPE), "k.messageblk"); g_string_printf(q, "SELECT m.message_idnr,k.messageblk FROM %smessageblks k " "JOIN %sphysmessage p ON k.physmessage_id = p.id " "JOIN %smessages m ON p.id = m.physmessage_id " "WHERE mailbox_idnr = %llu " "AND status IN (%d,%d ) " "AND k.is_header = '0' " "GROUP BY m.message_idnr,k.messageblk " "HAVING %s %s '%%%s%%'", DBPFX, DBPFX, DBPFX, dbmail_mailbox_get_id(self), MESSAGE_STATUS_NEW, MESSAGE_STATUS_SEEN, t->str, db_get_sql(SQL_INSENSITIVE_LIKE), s->search); break; case IST_SIZE_LARGER: gt_lt = '>'; case IST_SIZE_SMALLER: if (!gt_lt) gt_lt = '<'; g_string_printf(q, "SELECT m.message_idnr FROM %smessages m " "JOIN %sphysmessage p ON m.physmessage_id = p.id " "WHERE m.mailbox_idnr = %llu " "AND m.status IN (%d,%d) AND p.messagesize %c %llu " "ORDER BY message_idnr", DBPFX, DBPFX, dbmail_mailbox_get_id(self), MESSAGE_STATUS_NEW, MESSAGE_STATUS_SEEN, gt_lt, s->size); break; default: g_string_printf(q, "SELECT message_idnr FROM %smessages " "WHERE mailbox_idnr = %llu " "AND status IN (%d,%d) AND %s " "ORDER BY message_idnr", DBPFX, dbmail_mailbox_get_id(self), MESSAGE_STATUS_NEW, MESSAGE_STATUS_SEEN, s->search); // FIXME: Sometimes s->search is "" break; } g_string_free(t,TRUE); if (db_query(q->str) == -1) { TRACE(TRACE_ERROR, "could not execute query"); g_string_free(q,TRUE); return NULL; } g_string_free(q,TRUE); rows = db_num_rows(); s->found = g_tree_new_full((GCompareDataFunc)ucmp,NULL,(GDestroyNotify)g_free, (GDestroyNotify)g_free); if (rows > 0) { for (i=0; i < rows; i++) { id = db_get_result_u64(i,0); if (! (w = g_tree_lookup(self->ids, &id))) { TRACE(TRACE_ERROR, "key missing in self->ids: [%llu]\n", id); continue; } assert(w); k = g_new0(u64_t,1); v = g_new0(u64_t,1); *k = id; *v = *w; g_tree_insert(s->found, k, v); } } db_free_result(); return s->found; } GTree * dbmail_mailbox_get_set(struct DbmailMailbox *self, const char *set, gboolean uid) { GList *ids = NULL, *sets = NULL; GString *t; char *rest; u64_t i, l, r, lo = 0, hi = 0; u64_t *k, *v, *w = NULL; GTree *a, *b, *c; gboolean error = FALSE; b = NULL; if (! self->ids) dbmail_mailbox_open(self); if (! (self->ids && set)) return b; b = g_tree_new_full((GCompareDataFunc)ucmp,NULL, (GDestroyNotify)g_free, (GDestroyNotify)g_free); if (g_tree_nnodes(self->ids) == 0) return b; TRACE(TRACE_DEBUG,"[%s] uid [%d]", set, uid); if (uid) { ids = g_tree_keys(self->ids); assert(ids); ids = g_list_last(ids); hi = *((u64_t *)ids->data); ids = g_list_first(ids); lo = *((u64_t *)ids->data); g_list_free(g_list_first(ids)); } else { lo = 1; hi = g_tree_nnodes(self->ids); } a = g_tree_new_full((GCompareDataFunc)ucmp,NULL, (GDestroyNotify)g_free, (GDestroyNotify)g_free); t = g_string_new(set); sets = g_string_split(t,","); sets = g_list_first(sets); while(sets) { l = 0; r = 0; if (strlen((char *)sets->data) < 1) break; rest = sets->data; if (rest[0] == '*') { l = hi; r = l; if (strlen(rest) > 1) rest++; } else { if (! (l = dm_strtoull(sets->data,&rest,10))) { error = TRUE; break; } if (l == 0xffffffff) // outlook l = hi; l = max(l,lo); r = l; } if (rest[0]==':') { if (strlen(rest)>1) rest++; if (rest[0] == '*') r = hi; else { if (! (r = strtoull(rest,NULL,10))) { error = TRUE; break; } if (r == 0xffffffff) // outlook r = hi; } if (!r) break; if (r > hi) r = hi; if (r < lo) r = lo; } if (! (l && r)) break; if (uid) c = self->ids; else c = self->msn; for (i = min(l,r); i <= max(l,r); i++) { if (! (w = g_tree_lookup(c,&i))) continue; k = g_new0(u64_t,1); v = g_new0(u64_t,1); *k = i; *v = *w; // we always want to return a tree with // uids as keys and msns as values if (uid) g_tree_insert(a,k,v); else g_tree_insert(a,v,k); } if (g_tree_merge(b,a,IST_SUBSEARCH_OR)) { error = TRUE; TRACE(TRACE_ERROR, "cannot compare null trees"); break; } if (! g_list_next(sets)) break; sets = g_list_next(sets); } g_list_destroy(sets); g_string_free(t,TRUE); if (a) g_tree_destroy(a); if (error) { g_tree_destroy(b); b = NULL; } return b; } static gboolean _found_tree_copy(u64_t *key, u64_t *val, GTree *tree) { u64_t *a,*b; a = g_new0(u64_t,1); b = g_new0(u64_t,1); *a = *key; *b = *val; g_tree_insert(tree, a, b); return FALSE; } static gboolean _do_search(GNode *node, struct DbmailMailbox *self) { search_key_t *s = (search_key_t *)node->data; if (s->searched) return FALSE; switch (s->type) { case IST_SORT: return FALSE; break; case IST_SET: if (! (s->found = dbmail_mailbox_get_set(self, (const char *)s->search, 0))) return TRUE; break; case IST_UIDSET: if (! (s->found = dbmail_mailbox_get_set(self, (const char *)s->search, 1))) return TRUE; break; case IST_SIZE_LARGER: case IST_SIZE_SMALLER: case IST_HDRDATE_BEFORE: case IST_HDRDATE_SINCE: case IST_HDRDATE_ON: case IST_IDATE: case IST_FLAG: case IST_HDR: case IST_DATA_TEXT: case IST_DATA_BODY: mailbox_search(self, s); break; case IST_SUBSEARCH_NOT: case IST_SUBSEARCH_AND: case IST_SUBSEARCH_OR: g_node_children_foreach(node, G_TRAVERSE_ALL, (GNodeForeachFunc)_do_search, (gpointer)self); s->found = g_tree_new_full((GCompareDataFunc)ucmp,NULL,(GDestroyNotify)g_free, (GDestroyNotify)g_free); break; default: return TRUE; } s->searched = TRUE; TRACE(TRACE_DEBUG,"[%p] depth [%d] type [%d] rows [%d]\n", s, g_node_depth(node), s->type, s->found ? g_tree_nnodes(s->found): 0); return FALSE; } static gboolean _merge_search(GNode *node, GTree *found) { search_key_t *s = (search_key_t *)node->data; search_key_t *a, *b; GNode *x, *y; if (s->type == IST_SORT) return FALSE; if (s->merged == TRUE) return FALSE; switch(s->type) { case IST_SUBSEARCH_AND: g_node_children_foreach(node, G_TRAVERSE_ALL, (GNodeForeachFunc)_merge_search, (gpointer) found); break; case IST_SUBSEARCH_NOT: g_tree_foreach(found, (GTraverseFunc)_found_tree_copy, s->found); g_node_children_foreach(node, G_TRAVERSE_ALL, (GNodeForeachFunc)_merge_search, (gpointer) s->found); g_tree_merge(found, s->found, IST_SUBSEARCH_NOT); s->merged = TRUE; g_tree_destroy(s->found); s->found = NULL; break; case IST_SUBSEARCH_OR: x = g_node_nth_child(node,0); y = g_node_nth_child(node,1); a = (search_key_t *)x->data; b = (search_key_t *)y->data; if (a->type == IST_SUBSEARCH_AND) { g_tree_foreach(found, (GTraverseFunc)_found_tree_copy, a->found); g_node_children_foreach(x, G_TRAVERSE_ALL, (GNodeForeachFunc)_merge_search, (gpointer)a->found); } if (b->type == IST_SUBSEARCH_AND) { g_tree_foreach(found, (GTraverseFunc)_found_tree_copy, b->found); g_node_children_foreach(y, G_TRAVERSE_ALL, (GNodeForeachFunc)_merge_search, (gpointer)b->found); } g_tree_merge(a->found, b->found,IST_SUBSEARCH_OR); b->merged = TRUE; g_tree_destroy(b->found); b->found = NULL; g_tree_merge(s->found, a->found,IST_SUBSEARCH_OR); a->merged = TRUE; g_tree_destroy(a->found); a->found = NULL; g_tree_merge(found, s->found, IST_SUBSEARCH_AND); s->merged = TRUE; g_tree_destroy(s->found); s->found = NULL; break; default: g_tree_merge(found, s->found, IST_SUBSEARCH_AND); s->merged = TRUE; g_tree_destroy(s->found); s->found = NULL; break; } TRACE(TRACE_DEBUG,"[%p] leaf [%d] depth [%d] type [%d] found [%d]", s, G_NODE_IS_LEAF(node), g_node_depth(node), s->type, found ? g_tree_nnodes(found): 0); return FALSE; } int dbmail_mailbox_sort(struct DbmailMailbox *self) { if (! self->search) return 0; g_node_traverse(g_node_get_root(self->search), G_PRE_ORDER, G_TRAVERSE_ALL, -1, (GNodeTraverseFunc)_do_sort, (gpointer)self); return 0; } int dbmail_mailbox_search(struct DbmailMailbox *self) { if (! self->search) return 0; g_node_traverse(g_node_get_root(self->search), G_PRE_ORDER, G_TRAVERSE_ALL, -1, (GNodeTraverseFunc)_do_search, (gpointer)self); g_node_traverse(g_node_get_root(self->search), G_PRE_ORDER, G_TRAVERSE_ALL, -1, (GNodeTraverseFunc)_merge_search, (gpointer)self->ids); if (self->ids == NULL) TRACE(TRACE_DEBUG,"found no ids\n"); else TRACE(TRACE_DEBUG,"found [%d] ids\n", self->ids ? g_tree_nnodes(self->ids): 0); return 0; }