Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / servers / exchange / lib / e2k-restriction.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /* Copyright (C) 2001-2004 Novell, Inc.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of version 2 of the GNU Lesser General Public
7  * License as published by the Free Software Foundation.
8  *
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  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 /* e2k-restriction.c: message restrictions (WHERE clauses / Rule conditions) */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include "e2k-restriction.h"
27 #include "e2k-properties.h"
28 #include "e2k-rule.h"
29
30 #include <stdarg.h>
31 #include <string.h>
32
33 static E2kRestriction *
34 conjoin (E2kRestrictionType type, int nrns, E2kRestriction **rns, gboolean unref)
35 {
36         E2kRestriction *ret = g_new0 (E2kRestriction, 1);
37         int i;
38
39         ret->type = type;
40         ret->res.and.nrns = nrns;
41         ret->res.and.rns = g_new (E2kRestriction *, nrns);
42         for (i = 0; i < nrns; i++) {
43                 ret->res.and.rns[i] = rns[i];
44                 if (!unref)
45                         e2k_restriction_ref (rns[i]);
46         }
47
48         return ret;
49 }
50
51 /**
52  * e2k_restriction_and:
53  * @nrns: length of @rns
54  * @rns: an array of #E2kRestriction
55  * @unref: whether or not to unref the restrictions when it is done
56  *
57  * Creates a new restriction which is true if all of the restrictions
58  * in @rns are true.
59  *
60  * If @unref is %TRUE, then e2k_restriction_and() is essentially
61  * stealing the caller's references on the restrictions. If it is
62  * %FALSE, then e2k_restriction_and() will acquire its own references
63  * to each of the restrictions.
64  *
65  * Return value: the new restriction
66  **/
67 E2kRestriction *
68 e2k_restriction_and (int nrns, E2kRestriction **rns, gboolean unref)
69 {
70         return conjoin (E2K_RESTRICTION_AND, nrns, rns, unref);
71 }
72
73 /**
74  * e2k_restriction_or:
75  * @nrns: length of @rns
76  * @rns: an array of #E2kRestriction
77  * @unref: see e2k_restriction_and()
78  *
79  * Creates a new restriction which is true if any of the restrictions
80  * in @rns are true.
81  *
82  * Return value: the new restriction
83  **/
84 E2kRestriction *
85 e2k_restriction_or (int nrns, E2kRestriction **rns, gboolean unref)
86 {
87         return conjoin (E2K_RESTRICTION_OR, nrns, rns, unref);
88 }
89
90 static E2kRestriction *
91 conjoinv (E2kRestrictionType type, E2kRestriction *rn, va_list ap)
92 {
93         E2kRestriction *ret = g_new0 (E2kRestriction, 1);
94         GPtrArray *rns;
95
96         rns = g_ptr_array_new ();
97         while (rn) {
98                 g_ptr_array_add (rns, rn);
99                 rn = va_arg (ap, E2kRestriction *);
100         }
101         va_end (ap);
102
103         ret->type = type;
104         ret->res.and.nrns = rns->len;
105         ret->res.and.rns = (E2kRestriction **)rns->pdata;
106         g_ptr_array_free (rns, FALSE);
107
108         return ret;
109 }
110
111 /**
112  * e2k_restriction_andv:
113  * @rn: an #E2kRestriction
114  * @...: a %NULL-terminated list of additional #E2kRestrictions
115  *
116  * Creates a new restriction which is true if all of the passed-in
117  * restrictions are true. e2k_restriction_andv() steals the caller's
118  * reference on each of the passed-in restrictions.
119  *
120  * Return value: the new restriction
121  **/
122 E2kRestriction *
123 e2k_restriction_andv (E2kRestriction *rn, ...)
124 {
125         va_list ap;
126
127         va_start (ap, rn);
128         return conjoinv (E2K_RESTRICTION_AND, rn, ap);
129 }
130
131 /**
132  * e2k_restriction_orv:
133  * @rn: an #E2kRestriction
134  * @...: a %NULL-terminated list of additional #E2kRestrictions
135  *
136  * Creates a new restriction which is true if any of the passed-in
137  * restrictions are true. e2k_restriction_orv() steals the caller's
138  * reference on each of the passed-in restrictions.
139  *
140  * Return value: the new restriction
141  **/
142 E2kRestriction *
143 e2k_restriction_orv (E2kRestriction *rn, ...)
144 {
145         va_list ap;
146
147         va_start (ap, rn);
148         return conjoinv (E2K_RESTRICTION_OR, rn, ap);
149 }
150
151 /**
152  * e2k_restriction_not:
153  * @rn: an #E2kRestriction
154  * @unref: see e2k_restriction_and()
155  *
156  * Creates a new restriction which is true if @rn is false.
157  *
158  * Return value: the new restriction
159  **/
160 E2kRestriction *
161 e2k_restriction_not (E2kRestriction *rn, gboolean unref)
162 {
163         E2kRestriction *ret = g_new0 (E2kRestriction, 1);
164
165         ret->type = E2K_RESTRICTION_NOT;
166         ret->res.not.rn = rn;
167         if (!unref)
168                 e2k_restriction_ref (rn);
169
170         return ret;
171 }
172
173 /**
174  * e2k_restriction_content:
175  * @propname: text property to compare against
176  * @fuzzy_level: how to compare
177  * @value: value to compare against
178  *
179  * Creates a new restriction that is true for objects where the
180  * indicated property's value matches @value according to @fuzzy_level.
181  *
182  * For a WebDAV SEARCH, @fuzzy_level should be %E2K_FL_FULLSTRING,
183  * %E2K_FL_SUBSTRING, %E2K_FL_PREFIX, or %E2K_FL_SUFFIX.
184  *
185  * For a MAPI restriction, @fuzzy_level may not be %E2K_FL_SUFFIX, but
186  * may be ORed with any of the additional values %E2K_FL_IGNORECASE,
187  * %E2K_FL_IGNORENONSPACE, or %E2K_FL_LOOSE.
188  *
189  * To compare a property's sort order to another string, use
190  * e2k_restriction_prop_string().
191  *
192  * Return value: the new restriction
193  **/
194 E2kRestriction *
195 e2k_restriction_content (const char *propname,
196                          E2kRestrictionFuzzyLevel fuzzy_level,
197                          const char *value)
198 {
199         E2kRestriction *ret = g_new0 (E2kRestriction, 1);
200
201         ret->type = E2K_RESTRICTION_CONTENT;
202         ret->res.content.fuzzy_level = fuzzy_level;
203         e2k_rule_prop_set (&ret->res.content.pv.prop, propname);
204         ret->res.content.pv.type = E2K_PROP_TYPE_STRING;
205         ret->res.content.pv.value = g_strdup (value);
206
207         return ret;
208 }
209
210 /**
211  * e2k_restriction_prop_bool:
212  * @propname: boolean property to compare against
213  * @relop: %E2K_RELOP_EQ or %E2K_RELOP_NE
214  * @value: %TRUE or %FALSE
215  *
216  * Creates a new restriction that is true for objects where the
217  * indicated property matches @relop and @value.
218  *
219  * Return value: the new restriction
220  **/
221 E2kRestriction *
222 e2k_restriction_prop_bool (const char *propname, E2kRestrictionRelop relop,
223                            gboolean value)
224 {
225         E2kRestriction *ret = g_new0 (E2kRestriction, 1);
226
227         ret->type = E2K_RESTRICTION_PROPERTY;
228         ret->res.property.relop = relop;
229         e2k_rule_prop_set (&ret->res.property.pv.prop, propname);
230         ret->res.property.pv.type = E2K_PROP_TYPE_BOOL;
231         ret->res.property.pv.value = GUINT_TO_POINTER (value);
232
233         return ret;
234 }
235
236 /**
237  * e2k_restriction_prop_int:
238  * @propname: integer property to compare against
239  * @relop: an #E2kRestrictionRelop
240  * @value: number to compare against
241  *
242  * Creates a new restriction that is true for objects where the
243  * indicated property matches @value according to @relop.
244  *
245  * Return value: the new restriction
246  **/
247 E2kRestriction *
248 e2k_restriction_prop_int (const char *propname, E2kRestrictionRelop relop,
249                           int value)
250 {
251         E2kRestriction *ret = g_new0 (E2kRestriction, 1);
252
253         ret->type = E2K_RESTRICTION_PROPERTY;
254         ret->res.property.relop = relop;
255         e2k_rule_prop_set (&ret->res.property.pv.prop, propname);
256         ret->res.property.pv.type = E2K_PROP_TYPE_INT;
257         ret->res.property.pv.value = GINT_TO_POINTER (value);
258
259         return ret;
260 }
261
262 /**
263  * e2k_restriction_prop_date:
264  * @propname: date/time property to compare against
265  * @relop: an #E2kRestrictionRelop
266  * @value: date/time to compare against (as returned by e2k_make_timestamp())
267  *
268  * Creates a new restriction that is true for objects where the
269  * indicated property matches @value according to @relop.
270  *
271  * Return value: the new restriction
272  **/
273 E2kRestriction *
274 e2k_restriction_prop_date (const char *propname, E2kRestrictionRelop relop,
275                            const char *value)
276 {
277         E2kRestriction *ret = g_new0 (E2kRestriction, 1);
278
279         ret->type = E2K_RESTRICTION_PROPERTY;
280         ret->res.property.relop = relop;
281         e2k_rule_prop_set (&ret->res.property.pv.prop, propname);
282         ret->res.property.pv.type = E2K_PROP_TYPE_DATE;
283         ret->res.property.pv.value = g_strdup (value);
284
285         return ret;
286 }
287
288 /**
289  * e2k_restriction_prop_string:
290  * @propname: text property to compare against
291  * @relop: an #E2kRestrictionRelop
292  * @value: text to compare against
293  *
294  * Creates a new restriction that is true for objects where the
295  * indicated property matches @value according to @relop.
296  *
297  * To do a substring match, use e2k_restriction_content().
298  *
299  * Return value: the new restriction
300  **/
301 E2kRestriction *
302 e2k_restriction_prop_string (const char *propname, E2kRestrictionRelop relop,
303                              const char *value)
304 {
305         E2kRestriction *ret = g_new0 (E2kRestriction, 1);
306
307         ret->type = E2K_RESTRICTION_PROPERTY;
308         ret->res.property.relop = relop;
309         e2k_rule_prop_set (&ret->res.property.pv.prop, propname);
310         ret->res.property.pv.type = E2K_PROP_TYPE_STRING;
311         ret->res.property.pv.value = g_strdup (value);
312
313         return ret;
314 }
315
316 /**
317  * e2k_restriction_prop_binary:
318  * @propname: binary property to compare against
319  * @relop: %E2K_RELOP_EQ or %E2K_RELOP_NE
320  * @data: data to compare against
321  * @len: length of @data
322  *
323  * Creates a new restriction that is true for objects where the
324  * indicated property matches @value according to @relop.
325  *
326  * Return value: the new restriction
327  **/
328 E2kRestriction *
329 e2k_restriction_prop_binary (const char *propname, E2kRestrictionRelop relop,
330                              gconstpointer data, int len)
331 {
332         E2kRestriction *ret = g_new0 (E2kRestriction, 1);
333
334         ret->type = E2K_RESTRICTION_PROPERTY;
335         ret->res.property.relop = relop;
336         e2k_rule_prop_set (&ret->res.property.pv.prop, propname);
337         ret->res.property.pv.type = E2K_PROP_TYPE_BINARY;
338         ret->res.property.pv.value = g_byte_array_new ();
339         g_byte_array_append (ret->res.property.pv.value, data, len);
340
341         return ret;
342 }
343
344 /**
345  * e2k_restriction_compare:
346  * @propname1: first property
347  * @relop: an #E2kRestrictionRelop
348  * @propname2: second property
349  *
350  * Creates a new restriction which is true for objects where
351  * @propname1 and @propname2 have the relationship described by
352  * @relop.
353  *
354  * Return value: the new restriction
355  **/
356 E2kRestriction *
357 e2k_restriction_compare (const char *propname1, E2kRestrictionRelop relop,
358                          const char *propname2)
359 {
360         E2kRestriction *ret = g_new0 (E2kRestriction, 1);
361
362         ret->type = E2K_RESTRICTION_COMPAREPROPS;
363         ret->res.compare.relop = relop;
364         e2k_rule_prop_set (&ret->res.compare.prop1, propname1);
365         e2k_rule_prop_set (&ret->res.compare.prop2, propname2);
366
367         return ret;
368 }
369
370 /**
371  * e2k_restriction_bitmask:
372  * @propname: integer property to compare
373  * @bitop: an #E2kRestrictionBitop
374  * @mask: mask of bits to compare against
375  *
376  * Creates a new restriction that is true for objects where the
377  * indicated bits of the value of @propname either are or aren't zero,
378  * as indicated by @bitop.
379  *
380  * This cannot be used for WebDAV SEARCH restrictions.
381  *
382  * Return value: the new restriction
383  **/
384 E2kRestriction *
385 e2k_restriction_bitmask (const char *propname, E2kRestrictionBitop bitop,
386                          guint32 mask)
387 {
388         E2kRestriction *ret = g_new0 (E2kRestriction, 1);
389
390         ret->type = E2K_RESTRICTION_BITMASK;
391         ret->res.bitmask.bitop = bitop;
392         e2k_rule_prop_set (&ret->res.bitmask.prop, propname);
393         ret->res.bitmask.mask = mask;
394
395         return ret;
396 }
397
398 /**
399  * e2k_restriction_size:
400  * @propname: property to compare
401  * @relop: an #E2kRestrictionRelop
402  * @size: the size to compare @propname to
403  *
404  * Creates a new restriction which is true for objects where the size
405  * of the value of @propname matches @size according to @relop.
406  *
407  * This cannot be used for WebDAV SEARCH restrictions.
408  *
409  * You probably do not want to use this. The standard idiom for
410  * checking the size of a message is to use e2k_restriction_prop_int()
411  * on its %PR_MESSAGE_SIZE property, not to use e2k_restriction_size()
412  * on its %PR_BODY.
413  *
414  * Return value: the new restriction
415  **/
416 E2kRestriction *
417 e2k_restriction_size (const char *propname, E2kRestrictionRelop relop,
418                       guint32 size)
419 {
420         E2kRestriction *ret = g_new0 (E2kRestriction, 1);
421
422         ret->type = E2K_RESTRICTION_SIZE;
423         ret->res.size.relop = relop;
424         e2k_rule_prop_set (&ret->res.size.prop, propname);
425         ret->res.size.size = size;
426
427         return ret;
428 }
429
430 /**
431  * e2k_restriction_exist:
432  * @propname: property to check
433  *
434  * Creates a new restriction which is true for objects that have
435  * a @propname property.
436  *
437  * This cannot be used for WebDAV SEARCH restrictions.
438  *
439  * Return value: the new restriction
440  **/
441 E2kRestriction *
442 e2k_restriction_exist (const char *propname)
443 {
444         E2kRestriction *ret = g_new0 (E2kRestriction, 1);
445
446         ret->type = E2K_RESTRICTION_EXIST;
447         e2k_rule_prop_set (&ret->res.exist.prop, propname);
448
449         return ret;
450 }
451
452 /**
453  * e2k_restriction_sub:
454  * @subtable: the WebDAV name of a MAPI property of type PT_OBJECT
455  * @rn: the restriction to apply against the values of @subtable
456  * @unref: see e2k_restriction_and()
457  *
458  * Creates a new restriction that is true for objects where @rn is
459  * true when applied to the value of @subtable on that object.
460  *
461  * @subtable is generally %PR_MESSAGE_RECIPIENTS (for finding messages
462  * whose recipients match a given restriction) or
463  * %PR_MESSAGE_ATTACHMENTS (for finding messages whose attachments
464  * match a given restriction).
465  *
466  * This cannot be used for WebDAV SEARCH restrictions.
467  *
468  * Return value: the new restriction
469  **/
470 E2kRestriction *
471 e2k_restriction_sub (const char *subtable, E2kRestriction *rn, gboolean unref)
472 {
473         E2kRestriction *ret = g_new0 (E2kRestriction, 1);
474
475         ret->type = E2K_RESTRICTION_SUBRESTRICTION;
476         e2k_rule_prop_set (&ret->res.sub.subtable, subtable);
477         ret->res.sub.rn = rn;
478         if (!unref)
479                 e2k_restriction_ref (rn);
480
481         return ret;
482 }
483
484 /**
485  * e2k_restriction_unref:
486  * @rn: a restriction
487  *
488  * Unrefs @rn. If there are no more references to @rn, it is freed.
489  **/
490 void
491 e2k_restriction_unref (E2kRestriction *rn)
492 {
493         int i;
494
495         if (rn->ref_count--)
496                 return;
497
498         switch (rn->type) {
499         case E2K_RESTRICTION_AND:
500         case E2K_RESTRICTION_OR:
501                 for (i = 0; i < rn->res.and.nrns; i++)
502                         e2k_restriction_unref (rn->res.and.rns[i]);
503                 g_free (rn->res.and.rns);
504                 break;
505
506         case E2K_RESTRICTION_NOT:
507                 e2k_restriction_unref (rn->res.not.rn);
508                 break;
509
510         case E2K_RESTRICTION_CONTENT:
511                 e2k_rule_free_propvalue (&rn->res.content.pv);
512                 break;
513
514         case E2K_RESTRICTION_PROPERTY:
515                 e2k_rule_free_propvalue (&rn->res.property.pv);
516                 break;
517
518         default:
519                 break;
520         }
521
522         g_free (rn);
523 }
524
525 /**
526  * e2k_restriction_ref:
527  * @rn: a restriction
528  *
529  * Refs @rn.
530  **/
531 void
532 e2k_restriction_ref (E2kRestriction *rn)
533 {
534         rn->ref_count++;
535 }
536
537
538 /* SQL export */
539
540 static gboolean rn_to_sql (E2kRestriction *rn, GString *sql, E2kRestrictionType inside);
541
542 static const char *sql_relops[] = { "<", "<=", ">", ">=", "=", "!=" };
543 static const int n_sql_relops = G_N_ELEMENTS (sql_relops);
544
545 static gboolean
546 rns_to_sql (E2kRestrictionType type, E2kRestriction **rns, int nrns, GString *sql)
547 {
548         int i;
549         gboolean need_op = FALSE;
550         gboolean rv = FALSE;
551
552         for (i = 0; i < nrns; i++) {
553                 if (need_op) {
554                         g_string_append (sql, type == E2K_RESTRICTION_AND ?
555                                          " AND " : " OR ");
556                         need_op = FALSE;
557                 }
558                 if (rn_to_sql (rns[i], sql, type)) {
559                         need_op = TRUE;
560                         rv = TRUE;
561                 }
562         }
563         return rv;
564 }
565
566 static void
567 append_sql_quoted (GString *sql, const char *string)
568 {
569         while (*string) {
570                 if (*string == '\'')
571                         g_string_append (sql, "''");
572                 else
573                         g_string_append_c (sql, *string);
574                 string++;
575         }
576 }
577
578 static gboolean
579 rn_to_sql (E2kRestriction *rn, GString *sql, E2kRestrictionType inside)
580 {
581         E2kPropValue *pv;
582
583         switch (rn->type) {
584         case E2K_RESTRICTION_AND:
585         case E2K_RESTRICTION_OR: {
586                 GString *subsql = g_string_new ("");
587                 gboolean rv;
588                 if ((rv = rns_to_sql (rn->type, rn->res.and.rns, rn->res.and.nrns, subsql))) {
589                         if (rn->type != inside)
590                                 g_string_append (sql, "(");
591                         g_string_append (sql, subsql->str);
592                         if (rn->type != inside)
593                                 g_string_append (sql, ")");
594                 }
595                 g_string_free (subsql, TRUE);
596
597                 return rv;
598         }
599
600         case E2K_RESTRICTION_NOT: {
601                 GString *subsql = g_string_new ("");
602                 gboolean rv;
603                 if ((rv = rn_to_sql (rn->res.not.rn, subsql, rn->type))) {
604                         g_string_append (sql, "NOT (");
605                         g_string_append (sql, subsql->str);
606                         g_string_append (sql, ")");
607                 }
608                 g_string_free (subsql, TRUE);
609
610                 return rv;
611         }
612
613         case E2K_RESTRICTION_CONTENT:
614                 pv = &rn->res.content.pv;
615                 g_string_append_printf (sql, "\"%s\" ", pv->prop.name);
616
617                 switch (E2K_FL_MATCH_TYPE (rn->res.content.fuzzy_level)) {
618                 case E2K_FL_SUBSTRING:
619                         g_string_append (sql, "LIKE '%");
620                         append_sql_quoted (sql, pv->value);
621                         g_string_append (sql, "%'");
622                         break;
623
624                 case E2K_FL_PREFIX:
625                         g_string_append (sql, "LIKE '");
626                         append_sql_quoted (sql, pv->value);
627                         g_string_append (sql, "%'");
628                         break;
629
630                 case E2K_FL_SUFFIX:
631                         g_string_append (sql, "LIKE '%");
632                         append_sql_quoted (sql, pv->value);
633                         g_string_append_c (sql, '\'');
634                         break;
635
636                 case E2K_FL_FULLSTRING:
637                 default:
638                         g_string_append (sql, "= '");
639                         append_sql_quoted (sql, pv->value);
640                         g_string_append_c (sql, '\'');
641                         break;
642                 }
643                 return TRUE;
644
645         case E2K_RESTRICTION_PROPERTY:
646                 if (rn->res.property.relop >= n_sql_relops)
647                         return FALSE;
648
649                 pv = &rn->res.property.pv;
650                 g_string_append_printf (sql, "\"%s\" %s ", pv->prop.name,
651                                         sql_relops[rn->res.property.relop]);
652
653                 switch (pv->type) {
654                 case E2K_PROP_TYPE_INT:
655                         g_string_append_printf (sql, "%d",
656                                                 GPOINTER_TO_UINT (pv->value));
657                         break;
658
659                 case E2K_PROP_TYPE_BOOL:
660                         g_string_append (sql, pv->value ? "True" : "False");
661                         break;
662
663                 case E2K_PROP_TYPE_DATE:
664                         g_string_append_printf (sql,
665                                                 "cast (\"%s\" as 'dateTime.tz')",
666                                                 (char *)pv->value);
667                         break;
668
669                 default:
670                         g_string_append_c (sql, '\'');
671                         append_sql_quoted (sql, pv->value);
672                         g_string_append_c (sql, '\'');
673                         break;
674                 }
675                 return TRUE;
676
677         case E2K_RESTRICTION_COMPAREPROPS:
678                 if (rn->res.compare.relop >= n_sql_relops)
679                         return FALSE;
680
681                 g_string_append_printf (sql, "\"%s\" %s \"%s\"",
682                                         rn->res.compare.prop1.name,
683                                         sql_relops[rn->res.compare.relop],
684                                         rn->res.compare.prop2.name);
685                 return TRUE;
686
687         case E2K_RESTRICTION_COMMENT:
688                 return TRUE;
689
690         case E2K_RESTRICTION_BITMASK:
691         case E2K_RESTRICTION_EXIST:
692         case E2K_RESTRICTION_SIZE:
693         case E2K_RESTRICTION_SUBRESTRICTION:
694         default:
695                 return FALSE;
696
697         }
698 }
699
700 /**
701  * e2k_restriction_to_sql:
702  * @rn: a restriction
703  *
704  * Converts @rn to an SQL WHERE clause to be used with the WebDAV
705  * SEARCH method. Note that certain restriction types cannot be used
706  * in SQL, as mentioned in their descriptions above.
707  *
708  * If the restriction matches all objects, the return value will
709  * be the empty string. Otherwise it will start with "WHERE ".
710  *
711  * Return value: the SQL WHERE clause, which the caller must free,
712  * or %NULL if @rn could not be converted to SQL.
713  **/
714 char *
715 e2k_restriction_to_sql (E2kRestriction *rn)
716 {
717         GString *sql;
718         char *ret;
719
720         sql = g_string_new (NULL);
721         if (!rn_to_sql (rn, sql, E2K_RESTRICTION_AND)) {
722                 g_string_free (sql, TRUE);
723                 return NULL;
724         }
725
726         if (sql->len)
727                 g_string_prepend (sql, "WHERE ");
728
729         ret = sql->str;
730         g_string_free (sql, FALSE);
731         return ret;
732 }
733
734
735 /* Binary import/export */
736
737 static gboolean
738 extract_restriction (guint8 **data, int *len, E2kRestriction **rn)
739 {
740         int type;
741
742         if (*len == 0)
743                 return FALSE;
744         type = (*data)[0];
745         (*data)++;
746         (*len)--;
747
748         switch (type) {
749         case E2K_RESTRICTION_AND:
750         case E2K_RESTRICTION_OR:
751         {
752                 E2kRestriction **rns;
753                 guint16 nrns;
754                 int i;
755
756                 if (!e2k_rule_extract_uint16 (data, len, &nrns))
757                         return FALSE;
758                 rns = g_new0 (E2kRestriction *, nrns);
759                 for (i = 0; i < nrns; i++) {
760                         if (!extract_restriction (data, len, &rns[i])) {
761                                 while (i--)
762                                         e2k_restriction_unref (rns[i]);
763                                 g_free (rns);
764                                 return FALSE;
765                         }
766                 }
767
768                 *rn = conjoin (type, nrns, rns, TRUE);
769                 return TRUE;
770         }
771
772         case E2K_RESTRICTION_NOT:
773         {
774                 E2kRestriction *subrn;
775
776                 if (!extract_restriction (data, len, &subrn))
777                         return FALSE;
778                 *rn = e2k_restriction_not (subrn, TRUE);
779                 return TRUE;
780         }
781
782         case E2K_RESTRICTION_CONTENT:
783         {
784                 guint32 fuzzy_level;
785                 E2kRuleProp prop;
786                 E2kPropValue pv;
787
788                 if (!e2k_rule_extract_uint32 (data, len, &fuzzy_level) ||
789                     !e2k_rule_extract_proptag (data, len, &prop) ||
790                     !e2k_rule_extract_propvalue (data, len, &pv))
791                         return FALSE;
792
793                 pv.prop = prop;
794
795                 *rn = g_new0 (E2kRestriction, 1);
796                 (*rn)->type = type;
797                 (*rn)->res.content.fuzzy_level = fuzzy_level;
798                 (*rn)->res.content.pv = pv;
799                 return TRUE;
800         }
801
802         case E2K_RESTRICTION_PROPERTY:
803         {
804                 guint8 relop;
805                 E2kRuleProp prop;
806                 E2kPropValue pv;
807
808                 if (!e2k_rule_extract_byte (data, len, &relop) ||
809                     !e2k_rule_extract_proptag (data, len, &prop) ||
810                     !e2k_rule_extract_propvalue (data, len, &pv))
811                         return FALSE;
812
813                 pv.prop = prop;
814
815                 *rn = g_new0 (E2kRestriction, 1);
816                 (*rn)->type = type;
817                 (*rn)->res.property.relop = relop;
818                 (*rn)->res.property.pv = pv;
819                 return TRUE;
820         }
821
822         case E2K_RESTRICTION_COMPAREPROPS:
823         {
824                 /* FIXME */
825                 return FALSE;
826         }
827
828         case E2K_RESTRICTION_BITMASK:
829         {
830                 guint8 bitop;
831                 guint32 mask;
832                 E2kRuleProp prop;
833
834                 if (!e2k_rule_extract_byte (data, len, &bitop) ||
835                     !e2k_rule_extract_proptag (data, len, &prop) ||
836                     !e2k_rule_extract_uint32 (data, len, &mask))
837                         return FALSE;
838
839                 *rn = g_new0 (E2kRestriction, 1);
840                 (*rn)->type = type;
841                 (*rn)->res.bitmask.bitop = bitop;
842                 (*rn)->res.bitmask.prop = prop;
843                 (*rn)->res.bitmask.mask = mask;
844                 return TRUE;
845         }
846
847         case E2K_RESTRICTION_SIZE:
848         {
849                 /* FIXME */
850                 return FALSE;
851         }
852
853         case E2K_RESTRICTION_EXIST:
854         {
855                 E2kRuleProp prop;
856
857                 if (!e2k_rule_extract_proptag (data, len, &prop))
858                         return FALSE;
859
860                 *rn = g_new0 (E2kRestriction, 1);
861                 (*rn)->type = type;
862                 (*rn)->res.exist.prop = prop;
863                 return TRUE;
864         }
865
866         case E2K_RESTRICTION_SUBRESTRICTION:
867         {
868                 E2kRuleProp subtable;
869                 E2kRestriction *subrn;
870
871                 if (!e2k_rule_extract_proptag (data, len, &subtable) ||
872                     !extract_restriction (data, len, &subrn))
873                         return FALSE;
874
875                 *rn = g_new0 (E2kRestriction, 1);
876                 (*rn)->type = type;
877                 (*rn)->res.sub.subtable = subtable;
878                 (*rn)->res.sub.rn = subrn;
879                 return TRUE;
880         }
881
882         case E2K_RESTRICTION_COMMENT:
883         {
884                 guint8 nprops, dummy;
885                 E2kPropValue *props;
886                 int i;
887
888                 if (!e2k_rule_extract_byte (data, len, &nprops))
889                         return FALSE;
890                 
891                 props = g_new0 (E2kPropValue, nprops);
892                 for (i = 0; i < nprops; i++) {
893                         if (!e2k_rule_extract_propvalue (data, len, &props[i])) {
894                                 while (i--)
895                                         e2k_rule_free_propvalue (&props[i]);
896                                 g_free (props);
897                                 return FALSE;
898                         }
899                 }
900
901                 *rn = g_new0 (E2kRestriction, 1);
902                 (*rn)->type = type;
903                 (*rn)->res.comment.nprops = nprops;
904                 (*rn)->res.comment.props = props;
905
906                 /* FIXME: There is always a "1" byte here, but I don't
907                  * know why.
908                  */
909                 if (!e2k_rule_extract_byte (data, len, &dummy) || dummy != 1) {
910                         e2k_restriction_unref (*rn);
911                         return FALSE;
912                 }
913
914                 if (!extract_restriction (data, len, &(*rn)->res.comment.rn)) {
915                         e2k_restriction_unref (*rn);
916                         return FALSE;
917                 }
918
919                 return TRUE;
920         }
921
922         default:
923                 return FALSE;
924         }
925 }
926
927 /**
928  * e2k_restriction_extract:
929  * @data: pointer to data pointer
930  * @len: pointer to data length
931  * @rn: pointer to variable to store the extracted restriction in
932  *
933  * Attempts to extract a restriction from *@data, which contains
934  * a binary-encoded restriction from a server-side rule.
935  *
936  * On success, *@rn will contain the extracted restriction, *@data
937  * will be advanced past the end of the restriction data, and *@len
938  * will be decremented accordingly.
939  *
940  * Return value: success or failure
941  **/
942 gboolean
943 e2k_restriction_extract (guint8 **data, int *len, E2kRestriction **rn)
944 {
945         guint32 rnlen;
946
947         if (!e2k_rule_extract_uint32 (data, len, &rnlen))
948                 return FALSE;
949         if (rnlen > *len)
950                 return FALSE;
951
952         if (rnlen == 1 && (*data)[0] == 0xFF) {
953                 (*data)++;
954                 (*len)--;
955                 *rn = NULL;
956                 return TRUE;
957         }
958
959         if (*len < 2)
960                 return FALSE;
961         if ((*data)[0] != 0 || (*data)[1] != 0)
962                 return FALSE;
963         (*data) += 2;
964         (*len) -= 2;
965
966         return extract_restriction (data, len, rn);
967 }
968
969 static void
970 append_restriction (GByteArray *ba, E2kRestriction *rn)
971 {
972         int i;
973
974         e2k_rule_append_byte (ba, rn->type);
975
976         switch (rn->type) {
977         case E2K_RESTRICTION_AND:
978         case E2K_RESTRICTION_OR:
979                 e2k_rule_append_uint16 (ba, rn->res.and.nrns);
980                 for (i = 0; i < rn->res.and.nrns; i++)
981                         append_restriction (ba, rn->res.and.rns[i]);
982                 break;
983
984         case E2K_RESTRICTION_NOT:
985                 append_restriction (ba, rn->res.not.rn);
986                 break;
987
988         case E2K_RESTRICTION_CONTENT:
989                 e2k_rule_append_uint32 (ba, rn->res.content.fuzzy_level);
990                 e2k_rule_append_proptag (ba, &rn->res.content.pv.prop);
991                 e2k_rule_append_propvalue (ba, &rn->res.content.pv);
992                 break;
993
994         case E2K_RESTRICTION_PROPERTY:
995                 e2k_rule_append_byte (ba, rn->res.property.relop);
996                 e2k_rule_append_proptag (ba, &rn->res.property.pv.prop);
997                 e2k_rule_append_propvalue (ba, &rn->res.property.pv);
998                 break;
999
1000         case E2K_RESTRICTION_COMPAREPROPS:
1001                 /* FIXME */
1002                 break;
1003
1004         case E2K_RESTRICTION_BITMASK:
1005                 e2k_rule_append_byte (ba, rn->res.bitmask.bitop);
1006                 e2k_rule_append_proptag (ba, &rn->res.bitmask.prop);
1007                 e2k_rule_append_uint32 (ba, rn->res.bitmask.mask);
1008                 break;
1009
1010         case E2K_RESTRICTION_SIZE:
1011                 break;
1012
1013         case E2K_RESTRICTION_EXIST:
1014                 e2k_rule_append_proptag (ba, &rn->res.exist.prop);
1015                 break;
1016
1017         case E2K_RESTRICTION_SUBRESTRICTION:
1018                 e2k_rule_append_proptag (ba, &rn->res.sub.subtable);
1019                 append_restriction (ba, rn->res.sub.rn);
1020                 break;
1021
1022         case E2K_RESTRICTION_COMMENT:
1023                 e2k_rule_append_byte (ba, rn->res.comment.nprops);
1024
1025                 for (i = 0; i < rn->res.comment.nprops; i++)
1026                         e2k_rule_append_propvalue (ba, &rn->res.comment.props[i]);
1027
1028                 /* FIXME: There is always a "1" byte here, but I don't
1029                  * know why.
1030                  */
1031                 e2k_rule_append_byte (ba, 1);
1032
1033                 append_restriction (ba, rn->res.comment.rn);
1034                 break;
1035
1036         default:
1037                 break;
1038         }
1039 }
1040
1041 /**
1042  * e2k_restriction_append:
1043  * @ba: a buffer into which a server-side rule is being constructed
1044  * @rn: the restriction to append to @ba
1045  *
1046  * Appends @rn to @ba as part of a server-side rule.
1047  **/
1048 void
1049 e2k_restriction_append (GByteArray *ba, E2kRestriction *rn)
1050 {
1051         int rnlen_offset, rnlen;
1052
1053         if (!rn) {
1054                 e2k_rule_append_uint32 (ba, 1);
1055                 e2k_rule_append_byte (ba, 0xFF);
1056                 return;
1057         }
1058
1059         /* Save space for the length field */
1060         rnlen_offset = ba->len;
1061         e2k_rule_append_uint32 (ba, 0);
1062
1063         /* FIXME: ??? */
1064         e2k_rule_append_uint16 (ba, 0);
1065
1066         append_restriction (ba, rn);
1067
1068         rnlen = ba->len - rnlen_offset - 4;
1069         e2k_rule_write_uint32 (ba->data + rnlen_offset, rnlen);
1070 }