1 /*
2  * e-free-form-exp.c
3  *
4  * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com)
5  *
6  * This library is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation.
9  *
10  * This library is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
13  * for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this library. If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 #include "evolution-data-server-config.h"
21 
22 #include <string.h>
23 
24 #include "e-free-form-exp.h"
25 
26 /* <free-form-expression> := <token> *(" " <token>)
27    <token> := <and> | <or> | <not> | <expr>
28    <and> := "and:(" <free-form-expression>+ ")"
29    <or> := "or:(" <free-form-expression>+ ")"
30    <not> := "not:(" <free-form-expression>+ ")"
31    <expr> := <name> ["-" <options>] ":" <text>
32    <name> := CHAR+
33    <options> := CHAR+
34    <text> := ANYCHAR-EXCEPT-WHITESPACE | "\"" QUOTEDANYCHAR "\""
35 */
36 
37 static GSList *
ffe_tokenize_words(const gchar * ffe)38 ffe_tokenize_words (const gchar *ffe)
39 {
40 	GSList *words = NULL;
41 	const gchar *ptr, *start;
42 	gboolean in_quotes = FALSE;
43 
44 	if (!ffe)
45 		return NULL;
46 
47 	for (ptr = ffe, start = ptr; ptr == ffe || ptr[-1] != 0; ptr++) {
48 		if (in_quotes && (*ptr == '\"' || !*ptr)) {
49 			if (ptr[1] == '\"') {
50 				ptr++;
51 			} else {
52 				gchar *qword;
53 				gint ii, jj;
54 
55 				in_quotes = FALSE;
56 
57 				qword = g_malloc (ptr - start + 2);
58 
59 				/* tab (\t) as the first character is a marker
60 				   that the string was quoted */
61 				qword[0] = '\t';
62 				jj = 1;
63 
64 				/* convert double-quotes (\"\") into single quotes (\") */
65 				for (ii = 0; ii < ptr - start; ii++, jj++) {
66 					qword[jj] = start[ii];
67 
68 					if (start[ii] == '\"') {
69 						if (start[ii + 1] == '\"')
70 							ii++;
71 					}
72 				}
73 
74 				qword[jj] = '\0';
75 
76 				words = g_slist_prepend (words, qword);
77 				start = ptr + 1;
78 			}
79 		} else if (*ptr == '\"' && (ptr == ffe ||
80 					    ptr[-1] == ':' ||
81 					    ptr[-1] == '(' ||
82 					    ptr[-1] == ' ' ||
83 					    ptr[-1] == '\t' ||
84 					    ptr[-1] == '\n' ||
85 					    ptr[-1] == '\r')) {
86 			if (ptr > start) {
87 				words = g_slist_prepend (words, g_strndup (start, ptr - start));
88 			}
89 			in_quotes = TRUE;
90 			start = ptr + 1;
91 		} else if (!in_quotes) {
92 			/* word separators */
93 			if ((*ptr == '(' && start != ptr) || *ptr == ')' || *ptr == ' ' || *ptr == '\t' || *ptr == '\n' || *ptr == '\r' || !*ptr) {
94 				if (ptr > start || (ptr >= start && *ptr == '(')) {
95 					words = g_slist_prepend (words, g_strndup (start, ptr - start + (*ptr == '(' ? 1 : 0)));
96 				}
97 
98 				if (*ptr == ')') {
99 					words = g_slist_prepend (words, g_strdup (")"));
100 				}
101 
102 				start = ptr + 1;
103 			}
104 		}
105 	}
106 
107 	return g_slist_reverse (words);
108 }
109 
110 static const EFreeFormExpSymbol *
ffe_find_symbol_for(const EFreeFormExpSymbol * symbols,const gchar * word)111 ffe_find_symbol_for (const EFreeFormExpSymbol *symbols,
112 		     const gchar *word)
113 {
114 	const gchar *colon;
115 	gint ii, jj, kk;
116 
117 	g_return_val_if_fail (symbols != NULL, NULL);
118 	g_return_val_if_fail (word != NULL, NULL);
119 
120 	colon = strchr (word, ':');
121 
122 	if (colon <= word && *word)
123 		return NULL;
124 
125 	for (ii = 0; symbols[ii].names; ii++) {
126 		const gchar *names = symbols[ii].names;
127 
128 		if (!*word && !*names)
129 			return &(symbols[ii]);
130 
131 		if (!*word)
132 			continue;
133 
134 		for (kk = 0; names[kk]; kk++) {
135 			for (jj = 0; word[jj] && names[kk + jj] && names[kk + jj] != ':'; jj++) {
136 				if (g_ascii_toupper (word[jj]) != g_ascii_toupper (names[kk + jj]) || word[jj] == '-' || word[jj] == ':')
137 					break;
138 			}
139 
140 			if ((word[jj] == '-' || word[jj] == ':') && (names[kk + jj] == ':' || !names[kk + jj]))
141 				return &(symbols[ii]);
142 
143 			while (names[kk] && names[kk] != ':')
144 				kk++;
145 
146 			if (!names[kk])
147 				break;
148 		}
149 	}
150 
151 	return NULL;
152 }
153 
154 static gboolean
ffe_process_word(const EFreeFormExpSymbol * symbols,const gchar * in_word,const gchar * next_word,GString ** psexp)155 ffe_process_word (const EFreeFormExpSymbol *symbols,
156 		  const gchar *in_word,
157 		  const gchar *next_word,
158 		  GString **psexp)
159 {
160 	GString *sexp;
161 	gchar *options = NULL, *subsexp;
162 	const EFreeFormExpSymbol *symbol = NULL;
163 	gboolean used_next_word = FALSE;
164 
165 	g_return_val_if_fail (symbols != NULL, FALSE);
166 	g_return_val_if_fail (in_word != NULL, FALSE);
167 	g_return_val_if_fail (psexp != NULL, FALSE);
168 
169 	if (*in_word == '\t') {
170 		/* tab (\t) as the first character is a marker
171 		   that the string was quoted */
172 		in_word++;
173 	} else {
174 		gchar *word = NULL;
175 		const gchar *dash, *colon;
176 
177 		/* <function>[-<options>]:values */
178 		dash = strchr (in_word, '-');
179 		colon = strchr (in_word, ':');
180 
181 		if (colon > in_word) {
182 			if (dash > in_word && dash < colon) {
183 				options = g_strndup (dash + 1, colon - dash - 1);
184 				word = g_strndup (in_word, dash - in_word + 1);
185 				word[dash - in_word] = ':';
186 			} else {
187 				word = g_strndup (in_word, colon - in_word + 1);
188 			}
189 		}
190 
191 		if (word) {
192 			symbol = ffe_find_symbol_for (symbols, word);
193 			if (!symbol) {
194 				g_free (options);
195 				options = NULL;
196 			} else if (colon[1]) {
197 				in_word = colon + 1;
198 			} else if (next_word) {
199 				in_word = next_word;
200 				if (*in_word == '\t')
201 					in_word++;
202 				used_next_word = TRUE;
203 			} else {
204 				g_free (options);
205 				options = NULL;
206 			}
207 
208 			g_free (word);
209 		}
210 	}
211 
212 	if (!symbol)
213 		symbol = ffe_find_symbol_for (symbols, "");
214 
215 	g_return_val_if_fail (symbol != NULL, FALSE);
216 	g_return_val_if_fail (symbol->build_sexp != NULL, FALSE);
217 
218 	sexp = *psexp;
219 	subsexp = symbol->build_sexp (in_word, options, symbol->hint);
220 
221 	if (subsexp && *subsexp) {
222 		if (!sexp) {
223 			sexp = g_string_new (subsexp);
224 		} else {
225 			g_string_append (sexp, subsexp);
226 		}
227 	}
228 
229 	g_free (options);
230 	g_free (subsexp);
231 
232 	*psexp = sexp;
233 
234 	return used_next_word;
235 }
236 
237 static void
ffe_finish_and_or_not(GString * sexp)238 ffe_finish_and_or_not (GString *sexp)
239 {
240 	g_return_if_fail (sexp != NULL);
241 
242 	if (sexp->len > 4) {
243 		if (g_str_has_suffix (sexp->str + sexp->len - 5, "(and ") ||
244 		    g_str_has_suffix (sexp->str + sexp->len - 5, "(not ")) {
245 			g_string_truncate (sexp, sexp->len - 5);
246 		} else if (g_str_has_suffix (sexp->str + sexp->len - 4, "(or ")) {
247 			g_string_truncate (sexp, sexp->len - 4);
248 		} else {
249 			g_string_append_c (sexp, ')');
250 		}
251 	} else if (sexp->len == 4) {
252 		if (g_str_has_suffix (sexp->str + sexp->len - 4, "(or ")) {
253 			g_string_truncate (sexp, sexp->len - 4);
254 		} else {
255 			g_string_append_c (sexp, ')');
256 		}
257 	} else {
258 		g_string_append_c (sexp, ')');
259 	}
260 }
261 
262 /**
263  * e_free_form_exp_to_sexp:
264  * @free_form_exp: a Free Form Expression
265  * @symbols: known symbols, which can be used in the Free From Expression
266  *
267  * Converts the @free_form_exp to an S-Expression using the S-Expression
268  * builders defined in the @symbols. The @symbols should have one symbol
269  * with an empty string as its name, which is used for words which do not
270  * have a symbol name prefix.
271  *
272  * The @symbols is a NULL-terminated array of known symbols. The NULL should
273  * be set for the symbol's name.
274  *
275  * Returns: converted @free_form_exp into S-Expression, %NULL on error.
276  *    Free the returned string with a g_free(), when done with it.
277  *
278  * Since: 3.16
279  **/
280 gchar *
e_free_form_exp_to_sexp(const gchar * free_form_exp,const EFreeFormExpSymbol * symbols)281 e_free_form_exp_to_sexp (const gchar *free_form_exp,
282 			 const EFreeFormExpSymbol *symbols)
283 {
284 	GSList *raw_words, *link;
285 	GString *sexp = NULL;
286 	gint deep_stack = 0;
287 
288 	g_return_val_if_fail (free_form_exp != NULL, NULL);
289 	g_return_val_if_fail (symbols != NULL, NULL);
290 
291 	raw_words = ffe_tokenize_words (free_form_exp);
292 
293 	for (link = raw_words; link; link = g_slist_next (link)) {
294 		const gchar *word = link->data;
295 
296 		if (!word)
297 			continue;
298 
299 		if (*word == '\t') {
300 			/* tab (\t) as the first character is a marker
301 			   that the string was quoted */
302 			ffe_process_word (symbols, word + 1, NULL, &sexp);
303 		} else if (g_ascii_strncasecmp (word, "not:(", 5) == 0 ||
304 			   g_ascii_strncasecmp (word, "and:(", 5) == 0 ||
305 			   g_ascii_strncasecmp (word, "or:(", 4) == 0) {
306 			if (!sexp)
307 				sexp = g_string_new ("");
308 
309 			if (g_ascii_tolower (*word) == 'n')
310 				g_string_append (sexp, "(not ");
311 			else if (g_ascii_tolower (*word) == 'a')
312 				g_string_append (sexp, "(and ");
313 			else
314 				g_string_append (sexp, "(or ");
315 
316 			deep_stack++;
317 		} else if (g_ascii_strcasecmp (word, ")") == 0) {
318 			if (deep_stack) {
319 				g_return_val_if_fail (sexp != NULL, NULL);
320 
321 				ffe_finish_and_or_not (sexp);
322 				deep_stack--;
323 			}
324 		} else {
325 			if (ffe_process_word (symbols, word, link->next ? link->next->data : NULL, &sexp))
326 				link = g_slist_next (link);
327 		}
328 	}
329 
330 	g_slist_free_full (raw_words, g_free);
331 
332 	while (deep_stack > 0) {
333 		ffe_finish_and_or_not (sexp);
334 		deep_stack--;
335 	}
336 
337 	if (sexp) {
338 		g_string_prepend (sexp, "(and ");
339 		g_string_append_c (sexp, ')');
340 	}
341 
342 	return sexp ? g_string_free (sexp, FALSE) : NULL;
343 }
344