Blob Blame History Raw
diff -up evolution-data-server-3.8.5/camel/camel-imapx-search.c.imapx-server-side-search evolution-data-server-3.8.5/camel/camel-imapx-search.c
--- evolution-data-server-3.8.5/camel/camel-imapx-search.c.imapx-server-side-search	2013-07-23 13:57:42.000000000 +0200
+++ evolution-data-server-3.8.5/camel/camel-imapx-search.c	2014-02-03 16:39:00.652093324 +0100
@@ -27,6 +27,7 @@
 
 struct _CamelIMAPXSearchPrivate {
 	GWeakRef server;
+	gint *local_data_search; /* not NULL, if testing whether all used headers are all locally available */
 };
 
 enum {
@@ -88,32 +89,194 @@ imapx_search_dispose (GObject *object)
 }
 
 static CamelSExpResult *
+imapx_search_result_match_all (CamelSExp *sexp,
+			       CamelFolderSearch *search)
+{
+	CamelSExpResult *result;
+
+	g_return_val_if_fail (search != NULL, NULL);
+
+	if (search->current != NULL) {
+		result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+		result->value.boolean = TRUE;
+	} else {
+		gint ii;
+
+		result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+		result->value.ptrarray = g_ptr_array_new ();
+
+		for (ii = 0; ii < search->summary->len; ii++)
+			g_ptr_array_add (
+				result->value.ptrarray,
+				(gpointer) search->summary->pdata[ii]);
+	}
+
+	return result;
+}
+
+static CamelSExpResult *
+imapx_search_result_match_none (CamelSExp *sexp,
+				CamelFolderSearch *search)
+{
+	CamelSExpResult *result;
+
+	g_return_val_if_fail (search != NULL, NULL);
+
+	if (search->current != NULL) {
+		result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+		result->value.boolean = FALSE;
+	} else {
+		result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+		result->value.ptrarray = g_ptr_array_new ();
+	}
+
+	return result;
+}
+
+static CamelSExpResult *
+imapx_search_process_criteria (CamelSExp *sexp,
+			       CamelFolderSearch *search,
+			       CamelIMAPXServer *server,
+			       const GString *criteria,
+			       const gchar *from_function)
+{
+	CamelSExpResult *result;
+	GPtrArray *uids = NULL;
+	GError *error = NULL;
+
+	uids = camel_imapx_server_uid_search (
+		server, search->folder, criteria->str, NULL, &error);
+
+	/* Sanity check. */
+	g_return_val_if_fail (
+		((uids != NULL) && (error == NULL)) ||
+		((uids == NULL) && (error != NULL)), NULL);
+
+	/* XXX No allowance for errors in CamelSExp callbacks!
+	 *     Dump the error to the console and make like we
+	 *     got an empty result. */
+	if (error != NULL) {
+		g_warning (
+			"%s: (UID SEARCH %s): %s",
+			from_function, criteria->str, error->message);
+		uids = g_ptr_array_new ();
+		g_error_free (error);
+	}
+
+	if (search->current != NULL) {
+		result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
+		result->value.boolean = (uids && uids->len > 0);
+	} else {
+		result = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_ARRAY_PTR);
+		result->value.ptrarray = g_ptr_array_ref (uids);
+	}
+
+	g_ptr_array_unref (uids);
+
+	return result;
+}
+
+static CamelSExpResult *
+imapx_search_match_all (CamelSExp *sexp,
+			gint argc,
+			CamelSExpTerm **argv,
+			CamelFolderSearch *search)
+{
+	CamelIMAPXSearch *imapx_search = CAMEL_IMAPX_SEARCH (search);
+	CamelIMAPXServer *server;
+	CamelSExpResult *result;
+	GPtrArray *summary;
+	gint local_data_search = 0, *prev_local_data_search, ii;
+
+	if (argc != 1)
+		return imapx_search_result_match_none (sexp, search);
+
+	server = camel_imapx_search_ref_server (CAMEL_IMAPX_SEARCH (search));
+	if (!server || search->current || !search->summary) {
+		g_clear_object (&server);
+
+		/* Chain up to parent's method. */
+		return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
+			match_all (sexp, argc, argv, search);
+	}
+
+	/* First try to see whether all used headers are available locally - if
+	   they are, then do not use server-side filtering at all. */
+	prev_local_data_search = imapx_search->priv->local_data_search;
+	imapx_search->priv->local_data_search = &local_data_search;
+
+	summary = search->summary_set ? search->summary_set : search->summary;
+
+	if (!CAMEL_IS_VEE_FOLDER (search->folder)) {
+		camel_folder_summary_prepare_fetch_all (search->folder->summary, NULL);
+	}
+
+	for (ii = 0; ii < summary->len; ii++) {
+		search->current = camel_folder_summary_get (search->folder->summary, summary->pdata[ii]);
+		if (search->current) {
+			result = camel_sexp_term_eval (sexp, argv[0]);
+			camel_sexp_result_free (sexp, result);
+			camel_message_info_free (search->current);
+			search->current = NULL;
+			break;
+		}
+	}
+	imapx_search->priv->local_data_search = prev_local_data_search;
+
+	if (local_data_search >= 0) {
+		g_clear_object (&server);
+
+		/* Chain up to parent's method. */
+		return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
+			match_all (sexp, argc, argv, search);
+	}
+
+	/* let's change the requirements a bit, the parent class expects as a result boolean,
+	   but here is expected GPtrArray of matched UIDs */
+	result = camel_sexp_term_eval (sexp, argv[0]);
+
+	g_object_unref (server);
+
+	g_return_val_if_fail (result != NULL, result);
+	g_return_val_if_fail (result->type == CAMEL_SEXP_RES_ARRAY_PTR, result);
+
+	return result;
+}
+
+static CamelSExpResult *
 imapx_search_body_contains (CamelSExp *sexp,
                             gint argc,
                             CamelSExpResult **argv,
                             CamelFolderSearch *search)
 {
+	CamelIMAPXSearch *imapx_search = CAMEL_IMAPX_SEARCH (search);
 	CamelIMAPXServer *server;
 	CamelSExpResult *result;
-	CamelSExpResultType type;
 	GString *criteria;
-	GPtrArray *uids;
 	gint ii, jj;
-	GError *error = NULL;
+
+	/* Always do body-search server-side */
+	if (imapx_search->priv->local_data_search) {
+		*imapx_search->priv->local_data_search = -1;
+		return imapx_search_result_match_none (sexp, search);
+	}
 
 	/* Match everything if argv = [""] */
 	if (argc == 1 && argv[0]->value.string[0] == '\0')
-		goto match_all;
+		return imapx_search_result_match_all (sexp, search);
 
 	/* Match nothing if empty argv or empty summary. */
 	if (argc == 0 || search->summary->len == 0)
-		goto match_none;
+		return imapx_search_result_match_none (sexp, search);
 
 	server = camel_imapx_search_ref_server (CAMEL_IMAPX_SEARCH (search));
 
 	/* This will be NULL if we're offline.  Search from cache. */
-	if (server == NULL)
-		goto chain_up;
+	if (server == NULL) {
+		/* Chain up to parent's method. */
+		return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
+			body_contains (sexp, argc, argv, search);
+	}
 
 	/* Build the IMAP search criteria. */
 
@@ -157,78 +320,217 @@ imapx_search_body_contains (CamelSExp *s
 		}
 	}
 
-	uids = camel_imapx_server_uid_search (
-		server, search->folder, criteria->str, NULL, &error);
+	result = imapx_search_process_criteria (sexp, search, server, criteria, G_STRFUNC);
 
-	/* Sanity check. */
-	g_return_val_if_fail (
-		((uids != NULL) && (error == NULL)) ||
-		((uids == NULL) && (error != NULL)), NULL);
+	g_string_free (criteria, TRUE);
+	g_object_unref (server);
 
-	/* XXX No allowance for errors in CamelSExp callbacks!
-	 *     Dump the error to the console and make like we
-	 *     got an empty result. */
-	if (error != NULL) {
-		g_warning (
-			"%s: (UID SEARCH %s): %s",
-			G_STRFUNC, criteria->str, error->message);
-		uids = g_ptr_array_new ();
-		g_error_free (error);
+	return result;
+}
+
+static gboolean
+imapx_search_is_header_from_summary (const gchar *header_name)
+{
+	return  g_ascii_strcasecmp (header_name, "From") == 0 ||
+		g_ascii_strcasecmp (header_name, "To") == 0 ||
+		g_ascii_strcasecmp (header_name, "CC") == 0 ||
+		g_ascii_strcasecmp (header_name, "Subject") == 0;
+}
+
+static CamelSExpResult *
+imapx_search_header_contains (CamelSExp *sexp,
+			      gint argc,
+			      CamelSExpResult **argv,
+			      CamelFolderSearch *search)
+{
+	CamelIMAPXSearch *imapx_search = CAMEL_IMAPX_SEARCH (search);
+	CamelIMAPXServer *server;
+	CamelSExpResult *result;
+	const gchar *headername, *command = NULL;
+	GString *criteria;
+	gint ii, jj;
+
+	/* Match nothing if empty argv or empty summary. */
+	if (argc <= 1 ||
+	    argv[0]->type != CAMEL_SEXP_RES_STRING ||
+	    search->summary->len == 0)
+		return imapx_search_result_match_none (sexp, search);
+
+	headername = argv[0]->value.string;
+
+	if (imapx_search_is_header_from_summary (headername)) {
+		if (imapx_search->priv->local_data_search) {
+			if (*imapx_search->priv->local_data_search >= 0)
+				*imapx_search->priv->local_data_search = (*imapx_search->priv->local_data_search) + 1;
+			return imapx_search_result_match_all (sexp, search);
+		}
+
+		/* Chain up to parent's method. */
+		return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
+			header_contains (sexp, argc, argv, search);
+	} else if (imapx_search->priv->local_data_search) {
+		*imapx_search->priv->local_data_search = -1;
+		return imapx_search_result_match_none (sexp, search);
+	}
+
+	server = camel_imapx_search_ref_server (CAMEL_IMAPX_SEARCH (search));
+
+	/* This will be NULL if we're offline.  Search from cache. */
+	if (server == NULL) {
+		/* Chain up to parent's method. */
+		return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
+			header_contains (sexp, argc, argv, search);
 	}
 
+	/* Build the IMAP search criteria. */
+
+	criteria = g_string_sized_new (128);
+
 	if (search->current != NULL) {
-		type = CAMEL_SEXP_RES_BOOL;
-		result = camel_sexp_result_new (sexp, type);
-		result->value.boolean = (uids->len > 0);
-	} else {
-		type = CAMEL_SEXP_RES_ARRAY_PTR;
-		result = camel_sexp_result_new (sexp, type);
-		result->value.ptrarray = g_ptr_array_ref (uids);
+		const gchar *uid;
+
+		/* Limit the search to a single UID. */
+		uid = camel_message_info_uid (search->current);
+		g_string_append_printf (criteria, "UID %s", uid);
 	}
 
-	g_ptr_array_unref (uids);
+	if (g_ascii_strcasecmp (headername, "From") == 0)
+		command = "FROM";
+	else if (g_ascii_strcasecmp (headername, "To") == 0)
+		command = "TO";
+	else if (g_ascii_strcasecmp (headername, "CC") == 0)
+		command = "CC";
+	else if (g_ascii_strcasecmp (headername, "Bcc") == 0)
+		command = "BCC";
+	else if (g_ascii_strcasecmp (headername, "Subject") == 0)
+		command = "SUBJECT";
 
-	g_string_free (criteria, TRUE);
+	for (ii = 1; ii < argc; ii++) {
+		struct _camel_search_words *words;
+		const guchar *term;
+
+		if (argv[ii]->type != CAMEL_SEXP_RES_STRING)
+			continue;
+
+		/* Handle multiple search words within a single term. */
+		term = (const guchar *) argv[ii]->value.string;
+		words = camel_search_words_split (term);
+
+		for (jj = 0; jj < words->len; jj++) {
+			gchar *cp;
+
+			if (criteria->len > 0)
+				g_string_append_c (criteria, ' ');
+
+			if (command)
+				g_string_append (criteria, command);
+			else
+				g_string_append_printf (criteria, "HEADER \"%s\"", headername);
+
+			g_string_append (criteria, " \"");
+
+			cp = words->words[jj]->word;
+			for (; *cp != '\0'; cp++) {
+				if (*cp == '\\' || *cp == '"')
+					g_string_append_c (criteria, '\\');
+				g_string_append_c (criteria, *cp);
+			}
+
+			g_string_append_c (criteria, '"');
+		}
+	}
+
+	result = imapx_search_process_criteria (sexp, search, server, criteria, G_STRFUNC);
 
+	g_string_free (criteria, TRUE);
 	g_object_unref (server);
 
 	return result;
+}
 
-match_all:
-	if (search->current != NULL) {
-		type = CAMEL_SEXP_RES_BOOL;
-		result = camel_sexp_result_new (sexp, type);
-		result->value.boolean = TRUE;
-	} else {
-		type = CAMEL_SEXP_RES_ARRAY_PTR;
-		result = camel_sexp_result_new (sexp, type);
-		result->value.ptrarray = g_ptr_array_new ();
+static CamelSExpResult *
+imapx_search_header_exists (CamelSExp *sexp,
+			    gint argc,
+			    CamelSExpResult **argv,
+			    CamelFolderSearch *search)
+{
+	CamelIMAPXSearch *imapx_search = CAMEL_IMAPX_SEARCH (search);
+	CamelIMAPXServer *server;
+	CamelSExpResult *result;
+	GString *criteria;
+	gint ii;
 
-		for (ii = 0; ii < search->summary->len; ii++)
-			g_ptr_array_add (
-				result->value.ptrarray,
-				(gpointer) search->summary->pdata[ii]);
+	/* Match nothing if empty argv or empty summary. */
+	if (argc == 0 || search->summary->len == 0)
+		return imapx_search_result_match_none (sexp, search);
+
+	/* Check if asking for locally stored headers only */
+	for (ii = 0; ii < argc; ii++) {
+		if (argv[ii]->type != CAMEL_SEXP_RES_STRING)
+			continue;
+
+		if (!imapx_search_is_header_from_summary (argv[ii]->value.string))
+			break;
 	}
 
-	return result;
+	/* All headers are from summary */
+	if (ii == argc) {
+		if (imapx_search->priv->local_data_search) {
+			if (*imapx_search->priv->local_data_search >= 0)
+				*imapx_search->priv->local_data_search = (*imapx_search->priv->local_data_search) + 1;
+
+			return imapx_search_result_match_all (sexp, search);
+		}
+
+		/* Chain up to parent's method. */
+		return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
+			header_exists (sexp, argc, argv, search);
+	} else if (imapx_search->priv->local_data_search) {
+		*imapx_search->priv->local_data_search = -1;
+		return imapx_search_result_match_none (sexp, search);
+	}
+
+	server = camel_imapx_search_ref_server (CAMEL_IMAPX_SEARCH (search));
+
+	/* This will be NULL if we're offline.  Search from cache. */
+	if (server == NULL) {
+		/* Chain up to parent's method. */
+		return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
+			header_exists (sexp, argc, argv, search);
+	}
+
+	/* Build the IMAP search criteria. */
+
+	criteria = g_string_sized_new (128);
 
-match_none:
 	if (search->current != NULL) {
-		type = CAMEL_SEXP_RES_BOOL;
-		result = camel_sexp_result_new (sexp, type);
-		result->value.boolean = FALSE;
-	} else {
-		type = CAMEL_SEXP_RES_ARRAY_PTR;
-		result = camel_sexp_result_new (sexp, type);
-		result->value.ptrarray = g_ptr_array_new ();
+		const gchar *uid;
+
+		/* Limit the search to a single UID. */
+		uid = camel_message_info_uid (search->current);
+		g_string_append_printf (criteria, "UID %s", uid);
 	}
 
-	return result;
+	for (ii = 0; ii < argc; ii++) {
+		const gchar *headername;
+
+		if (argv[ii]->type != CAMEL_SEXP_RES_STRING)
+			continue;
+
+		headername = argv[ii]->value.string;
+
+		if (criteria->len > 0)
+			g_string_append_c (criteria, ' ');
 
-chain_up:
-	/* Chain up to parent's body_contains() method. */
-	return CAMEL_FOLDER_SEARCH_CLASS (camel_imapx_search_parent_class)->
-		body_contains (sexp, argc, argv, search);
+		g_string_append_printf (criteria, "HEADER \"%s\" \"\"", headername);
+	}
+
+	result = imapx_search_process_criteria (sexp, search, server, criteria, G_STRFUNC);
+
+	g_string_free (criteria, TRUE);
+	g_object_unref (server);
+
+	return result;
 }
 
 static void
@@ -245,7 +547,10 @@ camel_imapx_search_class_init (CamelIMAP
 	object_class->dispose = imapx_search_dispose;
 
 	search_class = CAMEL_FOLDER_SEARCH_CLASS (class);
+	search_class->match_all = imapx_search_match_all;
 	search_class->body_contains = imapx_search_body_contains;
+	search_class->header_contains = imapx_search_header_contains;
+	search_class->header_exists = imapx_search_header_exists;
 
 	g_object_class_install_property (
 		object_class,
@@ -263,6 +568,7 @@ static void
 camel_imapx_search_init (CamelIMAPXSearch *search)
 {
 	search->priv = CAMEL_IMAPX_SEARCH_GET_PRIVATE (search);
+	search->priv->local_data_search = NULL;
 }
 
 /**