Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / servers / exchange / lib / e2k-rule-xml.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /* Copyright (C) 2002-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 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include <string.h>
25
26 #include "e2k-rule-xml.h"
27 #include "e2k-action.h"
28 #include "e2k-properties.h"
29 #include "e2k-propnames.h"
30 #include "e2k-proptags.h"
31 #include "e2k-utils.h"
32 #include "mapi.h"
33
34 static const char *contains_types[] = { NULL, "contains", NULL, NULL, NULL, "not contains", NULL, NULL };
35 static const char *subject_types[] = { "is", "contains", "starts with", NULL, "is not", "not contains", "not starts with", NULL };
36 #define E2K_FL_NEGATE 4
37 #define E2K_FL_MAX 8
38
39 #if 0
40 static gboolean
41 fuzzy_level_from_name (const char *name, const char *map[],
42                        int *fuzzy_level, gboolean *negated)
43 {
44         int i;
45
46         for (i = 0; i < E2K_FL_MAX; i++) {
47                 if (map[i] && !strcmp (name, map[i])) {
48                         *fuzzy_level = i & ~E2K_FL_NEGATE;
49                         *negated = (*fuzzy_level != i);
50                         return TRUE;
51                 }
52         }
53
54         return FALSE;
55 }
56 #endif
57
58 static inline const char *
59 fuzzy_level_to_name (int fuzzy_level, gboolean negated, const char *map[])
60 {
61         fuzzy_level = E2K_FL_MATCH_TYPE (fuzzy_level);
62         if (negated)
63                 fuzzy_level |= E2K_FL_NEGATE;
64
65         return map[fuzzy_level];
66 }
67
68 static const char *is_types[] = { NULL, NULL, NULL, NULL, "is", "is not" };
69 static const char *date_types[] = { "before", "before", "after", "after", NULL, NULL };
70 static const char *size_types[] = { "less than", "less than", "greater than", "greater than", NULL, NULL };
71
72 #if 0
73 static gboolean
74 relop_from_name (const char *name, const char *map[],
75                  E2kRestrictionRelop *relop)
76 {
77         int i;
78
79         for (i = 0; i < E2K_RELOP_RE; i++) {
80                 if (map[i] && !strcmp (name, map[i])) {
81                         *relop = i;
82                         return TRUE;
83                 }
84         }
85
86         return FALSE;
87 }
88 #endif
89
90 static inline const char *
91 relop_to_name (E2kRestrictionRelop relop, gboolean negated, const char *map[])
92 {
93         static const int negate_map[] = {
94                 E2K_RELOP_GE, E2K_RELOP_GT, E2K_RELOP_LE, E2K_RELOP_LT,
95                 E2K_RELOP_NE, E2K_RELOP_EQ
96         };
97
98         if (negated)
99                 relop = negate_map[relop];
100
101         return map[relop];
102 }
103
104 /* Check if @rn encodes Outlook's "Message was sent only to me" rule */
105 static gboolean
106 restriction_is_only_to_me (E2kRestriction *rn)
107 {
108         E2kRestriction *sub;
109
110         if (rn->type != E2K_RESTRICTION_AND || rn->res.and.nrns != 3)
111                 return FALSE;
112
113         sub = rn->res.and.rns[0];
114         if (sub->type != E2K_RESTRICTION_PROPERTY ||
115             sub->res.property.relop != E2K_RELOP_EQ ||
116             sub->res.property.pv.prop.proptag != E2K_PROPTAG_PR_MESSAGE_TO_ME ||
117             sub->res.property.pv.value == NULL)
118                 return FALSE;
119
120         sub = rn->res.and.rns[1];
121         if (sub->type != E2K_RESTRICTION_NOT)
122                 return FALSE;
123         sub = sub->res.not.rn;
124         if (sub->type != E2K_RESTRICTION_CONTENT ||
125             !(sub->res.content.fuzzy_level & E2K_FL_SUBSTRING) ||
126             sub->res.content.pv.prop.proptag != E2K_PROPTAG_PR_DISPLAY_TO ||
127             strcmp (sub->res.content.pv.value, ";") != 0)
128                 return FALSE;
129
130         sub = rn->res.and.rns[2];
131         if (sub->type != E2K_RESTRICTION_PROPERTY ||
132             sub->res.content.pv.prop.proptag != E2K_PROPTAG_PR_DISPLAY_CC ||
133             strcmp (sub->res.content.pv.value, "") != 0)
134                 return FALSE;
135
136         return TRUE;
137 }
138
139 /* Check if @rn encodes Outlook's "is a delegatable meeting request" rule */
140 static gboolean
141 restriction_is_delegation (E2kRestriction *rn)
142 {
143         E2kRestriction *sub;
144
145         if (rn->type != E2K_RESTRICTION_AND || rn->res.and.nrns != 3)
146                 return FALSE;
147
148         sub = rn->res.and.rns[0];
149         if (sub->type != E2K_RESTRICTION_CONTENT ||
150             E2K_FL_MATCH_TYPE (sub->res.content.fuzzy_level) != E2K_FL_PREFIX ||
151             sub->res.content.pv.prop.proptag != E2K_PROPTAG_PR_MESSAGE_CLASS ||
152             strcmp (sub->res.content.pv.value, "IPM.Schedule.Meeting") != 0)
153                 return FALSE;
154
155         sub = rn->res.and.rns[1];
156         if (sub->type != E2K_RESTRICTION_NOT ||
157             sub->res.not.rn->type != E2K_RESTRICTION_EXIST ||
158             sub->res.not.rn->res.exist.prop.proptag != E2K_PROPTAG_PR_DELEGATED_BY_RULE)
159                 return FALSE;
160
161         sub = rn->res.and.rns[2];
162         if (sub->type != E2K_RESTRICTION_OR || sub->res.or.nrns != 2)
163                 return FALSE;
164
165         sub = rn->res.and.rns[2]->res.or.rns[0];
166         if (sub->type != E2K_RESTRICTION_NOT ||
167             sub->res.not.rn->type != E2K_RESTRICTION_EXIST ||
168             sub->res.not.rn->res.exist.prop.proptag != E2K_PROPTAG_PR_SENSITIVITY)
169                 return FALSE;
170
171         sub = rn->res.and.rns[2]->res.or.rns[1];
172         if (sub->type != E2K_RESTRICTION_PROPERTY ||
173             sub->res.property.relop != E2K_RELOP_NE ||
174             sub->res.property.pv.prop.proptag != E2K_PROPTAG_PR_SENSITIVITY ||
175             GPOINTER_TO_INT (sub->res.property.pv.value) != MAPI_SENSITIVITY_PRIVATE)
176                 return FALSE;
177
178         return TRUE;
179 }
180
181 static xmlNode *
182 new_value (xmlNode *part, const char *name, const char *type, const char *value)
183 {
184         xmlNode *node;
185
186         node = xmlNewChild (part, NULL, "value", NULL);
187         xmlSetProp (node, "name", name);
188         xmlSetProp (node, "type", type);
189         if (value)
190                 xmlSetProp (node, "value", value);
191
192         return node;
193 }
194
195 static xmlNode *
196 new_value_int (xmlNode *part, const char *name, const char *type,
197                const char *value_name, long value)
198 {
199         xmlNode *node;
200         char *str;
201
202         node = xmlNewChild (part, NULL, "value", NULL);
203         xmlSetProp (node, "name", name);
204         xmlSetProp (node, "type", type);
205
206         str = g_strdup_printf ("%ld", value);
207         xmlSetProp (node, value_name, str);
208         g_free (str);
209
210         return node;
211 }
212
213 static xmlNode *
214 new_part (const char *part_name)
215 {
216         xmlNode *part;
217
218         part = xmlNewNode (NULL, "part");
219         xmlSetProp (part, "name", part_name);
220         return part;
221 }
222
223 static xmlNode *
224 match (const char *part_name, const char *value_name, const char *value_value,
225        const char *string_name, const char *string_value)
226 {
227         xmlNode *part, *value;
228
229         part = new_part (part_name);
230         value = new_value (part, value_name, "option", value_value);
231         value = new_value (part, string_name, "string", NULL);
232         xmlNewTextChild (value, NULL, "string", string_value);
233
234         return part;
235 }
236
237 static xmlNode *
238 message_is (const char *name, const char *type_name,
239             const char *kind, gboolean negated)
240 {
241         xmlNode *part;
242
243         part = new_part (name);
244         new_value (part, type_name, "option", negated ? "is not" : "is");
245         new_value (part, "kind", "option", kind);
246
247         return part;
248 }
249
250 static xmlNode *
251 address_is (E2kRestriction *comment_rn, gboolean recipients, gboolean negated)
252 {
253         xmlNode *part;
254         E2kRestriction *rn;
255         E2kPropValue *pv;
256         const char *relation, *display_name, *p;
257         char *addr, *full_addr;
258         GByteArray *ba;
259         int i;
260
261         rn = comment_rn->res.comment.rn;
262         if (rn->type != E2K_RESTRICTION_PROPERTY ||
263             rn->res.property.relop != E2K_RELOP_EQ)
264                 return NULL;
265         pv = &rn->res.property.pv;
266
267         if ((recipients && pv->prop.proptag != E2K_PROPTAG_PR_SEARCH_KEY) ||
268             (!recipients && pv->prop.proptag != E2K_PROPTAG_PR_SENDER_SEARCH_KEY))
269                 return NULL;
270
271         relation = relop_to_name (rn->res.property.relop, negated, is_types);
272         if (!relation)
273                 return NULL;
274
275         /* Extract the address part */
276         ba = pv->value;
277         p = strchr ((char *)ba->data, ':');
278         if (p)
279                 addr = g_ascii_strdown (p + 1, -1);
280         else
281                 addr = g_ascii_strdown ((char *)ba->data, -1);
282
283         /* Find the display name in the comment */
284         display_name = NULL;
285         for (i = 0; i < comment_rn->res.comment.nprops; i++) {
286                 pv = &comment_rn->res.comment.props[i];
287                 if (E2K_PROPTAG_TYPE (pv->prop.proptag) == E2K_PT_UNICODE) {
288                         display_name = pv->value;
289                         break;
290                 }
291         }
292
293         if (display_name)
294                 full_addr = g_strdup_printf ("%s <%s>", display_name, addr);
295         else
296                 full_addr = g_strdup_printf ("<%s>", addr);
297
298         if (recipients) {
299                 part = match ("recipient", "recipient-type", relation,
300                               "recipient", full_addr);
301         } else {
302                 part = match ("sender", "sender-type", relation,
303                               "sender", full_addr);
304         }
305
306         g_free (full_addr);
307         g_free (addr);
308         return part;
309 }
310
311 static gboolean
312 restriction_to_xml (E2kRestriction *rn, xmlNode *partset,
313                     E2kRestrictionType wrap_type, gboolean negated)
314 {
315         xmlNode *part, *value, *node;
316         E2kPropValue *pv;
317         const char *match_type;
318         int i;
319
320         switch (rn->type) {
321         case E2K_RESTRICTION_AND:
322         case E2K_RESTRICTION_OR:
323                 /* Check for special rules */
324                 if (restriction_is_only_to_me (rn)) {
325                         part = message_is ("message-to-me",
326                                            "message-to-me-type",
327                                            "only", negated);
328                         break;
329                 } else if (restriction_is_delegation (rn)) {
330                         part = message_is ("special-message",
331                                            "special-message-type",
332                                            "delegated-meeting-request",
333                                            negated);
334                         break;
335                 }
336
337                 /* If we are inside an "and" and hit another "and",
338                  * we can just remove the extra level:
339                  *    (and foo (and bar baz) quux) =>
340                  *    (and foo bar baz quux)
341                  * Likewise for "or"s.
342                  *
343                  * If we are inside an "and" and hit a "(not (or" (or
344                  * vice versa), we can use DeMorgan's Law and then
345                  * apply the above rule:
346                  *    (and foo (not (or bar baz)) quux) =>
347                  *    (and foo (and (not bar) (not baz)) quux) =>
348                  *    (and foo (not bar) (not baz) quux)
349                  *
350                  * This handles both cases.
351                  */
352                 if ((rn->type == wrap_type && !negated) ||
353                     (rn->type != wrap_type && negated)) {
354                         for (i = 0; i < rn->res.and.nrns; i++) {
355                                 if (!restriction_to_xml (rn->res.and.rns[i],
356                                                          partset, wrap_type,
357                                                          negated))
358                                         return FALSE;
359                         }
360                         return TRUE;
361                 }
362
363                 /* Otherwise, we have a rule that can't be expressed
364                  * as "match all" or "match any".
365                  */
366                 return FALSE;
367
368         case E2K_RESTRICTION_NOT:
369                 return restriction_to_xml (rn->res.not.rn, partset,
370                                            wrap_type, !negated);
371
372         case E2K_RESTRICTION_CONTENT:
373         {
374                 int fuzzy_level = E2K_FL_MATCH_TYPE (rn->res.content.fuzzy_level);
375
376                 pv = &rn->res.content.pv;
377
378                 switch (pv->prop.proptag) {
379                 case E2K_PROPTAG_PR_BODY:
380                         match_type = fuzzy_level_to_name (fuzzy_level, negated,
381                                                           contains_types);
382                         if (!match_type)
383                                 return FALSE;
384
385                         part = match ("body", "body-type", match_type,
386                                       "word", pv->value);
387                         break;
388
389                 case E2K_PROPTAG_PR_SUBJECT:
390                         match_type = fuzzy_level_to_name (fuzzy_level, negated,
391                                                           subject_types);
392                         if (!match_type)
393                                 return FALSE;
394
395                         part = match ("subject", "subject-type", match_type,
396                                       "subject", pv->value);
397                         break;
398
399                 case E2K_PROPTAG_PR_TRANSPORT_MESSAGE_HEADERS:
400                         match_type = fuzzy_level_to_name (fuzzy_level, negated,
401                                                           contains_types);
402                         if (!match_type)
403                                 return FALSE;
404
405                         part = match ("full-headers", "full-headers-type",
406                                       match_type, "word", pv->value);
407                         break;
408
409                 case E2K_PROPTAG_PR_MESSAGE_CLASS:
410                         if ((fuzzy_level == E2K_FL_FULLSTRING) &&
411                             !strcmp (pv->value, "IPM.Note.Rules.OofTemplate.Microsoft")) {
412                                 part = message_is ("special-message",
413                                                    "special-message-type",
414                                                    "oof", negated);
415                         } else if ((fuzzy_level == E2K_FL_PREFIX) &&
416                                    !strcmp (pv->value, "IPM.Schedule.Meeting")) {
417                                 part = message_is ("special-message",
418                                                    "special-message-type",
419                                                    "meeting-request", negated);
420                         } else
421                                 return FALSE;
422
423                         break;
424
425                 default:
426                         return FALSE;
427                 }
428                 break;
429         }
430
431         case E2K_RESTRICTION_PROPERTY:
432         {
433                 E2kRestrictionRelop relop;
434                 const char *relation;
435
436                 relop = rn->res.property.relop;
437                 if (relop >= E2K_RELOP_RE)
438                         return FALSE;
439
440                 pv = &rn->res.property.pv;
441
442                 switch (pv->prop.proptag) {
443                 case E2K_PROPTAG_PR_MESSAGE_TO_ME:
444                         if ((relop == E2K_RELOP_EQ && !pv->value) ||
445                             (relop == E2K_RELOP_NE && pv->value))
446                                 negated = !negated;
447
448                         part = message_is ("message-to-me",
449                                            "message-to-me-type",
450                                            "to", negated);
451                         break;
452
453                 case E2K_PROPTAG_PR_MESSAGE_CC_ME:
454                         if ((relop == E2K_RELOP_EQ && !pv->value) ||
455                             (relop == E2K_RELOP_NE && pv->value))
456                                 negated = !negated;
457
458                         part = message_is ("message-to-me",
459                                            "message-to-me-type",
460                                            "cc", negated);
461                         break;
462
463                 case E2K_PROPTAG_PR_MESSAGE_DELIVERY_TIME:
464                 case E2K_PROPTAG_PR_CLIENT_SUBMIT_TIME:
465                 {
466                         char *timestamp;
467
468                         relation = relop_to_name (relop, negated, date_types);
469                         if (!relation)
470                                 return FALSE;
471
472                         if (pv->prop.proptag == E2K_PROPTAG_PR_MESSAGE_DELIVERY_TIME)
473                                 part = new_part ("received-date");
474                         else
475                                 part = new_part ("sent-date");
476
477                         value = new_value (part, "date-spec-type", "option", relation);
478                         value = new_value (part, "versus", "datespec", NULL);
479
480                         node = xmlNewChild (value, NULL, "datespec", NULL);
481                         xmlSetProp (node, "type", "1");
482
483                         timestamp = g_strdup_printf ("%lu", (unsigned long)e2k_parse_timestamp (pv->value));
484                         xmlSetProp (node, "value", timestamp);
485                         g_free (timestamp);
486
487                         break;
488                 }
489
490                 case E2K_PROPTAG_PR_MESSAGE_SIZE:
491                         relation = relop_to_name (relop, negated, size_types);
492                         if (!relation)
493                                 return FALSE;
494
495                         part = new_part ("size");
496
497                         new_value (part, "size-type", "option", relation);
498                         new_value_int (part, "versus", "integer", "integer",
499                                        GPOINTER_TO_INT (pv->value) / 1024);
500
501                         break;
502
503                 case E2K_PROPTAG_PR_IMPORTANCE:
504                         relation = relop_to_name (relop, negated, is_types);
505                         if (!relation)
506                                 return FALSE;
507
508                         part = new_part ("importance");
509                         new_value (part, "importance-type", "option", relation);
510                         new_value_int (part, "importance", "option", "value",
511                                        GPOINTER_TO_INT (pv->value));
512                         break;
513
514                 case E2K_PROPTAG_PR_SENSITIVITY:
515                         relation = relop_to_name (relop, negated, is_types);
516                         if (!relation)
517                                 return FALSE;
518
519                         part = new_part ("sensitivity");
520                         xmlSetProp (part, "name", "sensitivity");
521                         new_value (part, "sensitivity-type", "option", relation);
522                         new_value_int (part, "sensitivity", "option", "value",
523                                        GPOINTER_TO_INT (pv->value));
524                         break;
525
526                 default:
527                         return FALSE;
528                 }
529                 break;
530         }
531
532         case E2K_RESTRICTION_COMMENT:
533                 part = address_is (rn, FALSE, negated);
534                 if (!part)
535                         return FALSE;
536                 break;
537
538         case E2K_RESTRICTION_BITMASK:
539                 if (rn->res.bitmask.prop.proptag != E2K_PROPTAG_PR_MESSAGE_FLAGS ||
540                     rn->res.bitmask.mask != MAPI_MSGFLAG_HASATTACH)
541                         return FALSE;
542
543                 part = new_part ("attachments");
544                 if (rn->res.bitmask.bitop == E2K_BMR_NEZ) {
545                         new_value (part, "match-type", "option",
546                                    negated ? "not exist" : "exist");
547                 } else {
548                         new_value (part, "match-type", "option",
549                                    negated ? "exist" : "not exist");
550                 }
551                 break;
552
553         case E2K_RESTRICTION_SUBRESTRICTION:
554                 if (rn->res.sub.subtable.proptag != E2K_PROPTAG_PR_MESSAGE_RECIPIENTS)
555                         return FALSE;
556                 if (rn->res.sub.rn->type != E2K_RESTRICTION_COMMENT)
557                         return FALSE;
558
559                 part = address_is (rn->res.sub.rn, TRUE, negated);
560                 if (!part)
561                         return FALSE;
562                 break;
563
564         default:
565                 return FALSE;
566         }
567
568         xmlAddChild (partset, part);
569         return TRUE;
570 }
571
572
573 static char *
574 stringify_entryid (guint8 *data, int len)
575 {
576         GString *string;
577         char *ret;
578         int i;
579
580         string = g_string_new (NULL);
581
582         for (i = 0; i < len && i < 22; i++)
583                 g_string_append_printf (string, "%02x", data[i]);
584         if (i < len && data[i]) {
585                 for (; i < len; i++)
586                         g_string_append_printf (string, "%02x", data[i]);
587         }
588
589         ret = string->str;
590         g_string_free (string, FALSE);
591         return ret;
592 }       
593
594 static gboolean
595 action_to_xml (E2kAction *act, xmlNode *actionset)
596 {
597         xmlNode *part, *value;
598         char *entryid;
599
600         switch (act->type) {
601         case E2K_ACTION_MOVE:
602         case E2K_ACTION_COPY:
603                 part = new_part (act->type == E2K_ACTION_MOVE ? "move-to-folder" : "copy-to-folder");
604                 value = new_value (part, "folder", "folder-source-key", NULL);
605                 entryid = stringify_entryid (
606                         act->act.xfer.folder_source_key->data + 1,
607                         act->act.xfer.folder_source_key->len - 1);
608                 xmlNewTextChild (value, NULL, "entryid", entryid);
609                 g_free (entryid);
610                 break;
611
612         case E2K_ACTION_REPLY:
613         case E2K_ACTION_OOF_REPLY:
614                 part = new_part (act->type == E2K_ACTION_REPLY ? "reply" : "oof-reply");
615                 value = new_value (part, "template", "message-entryid", NULL);
616                 entryid = stringify_entryid (
617                         act->act.reply.entryid->data,
618                         act->act.reply.entryid->len);
619                 xmlNewTextChild (value, NULL, "entryid", entryid);
620                 g_free (entryid);
621                 break;
622
623         case E2K_ACTION_DEFER:
624                 part = new_part ("defer");
625                 break;
626
627         case E2K_ACTION_BOUNCE:
628                 part = new_part ("bounce");
629                 switch (act->act.bounce_code) {
630                 case E2K_ACTION_BOUNCE_CODE_TOO_LARGE:
631                         new_value (part, "bounce_code", "option", "size");
632                         break;
633                 case E2K_ACTION_BOUNCE_CODE_FORM_MISMATCH:
634                         new_value (part, "bounce_code", "option", "form-mismatch");
635                         break;
636                 case E2K_ACTION_BOUNCE_CODE_ACCESS_DENIED:
637                         new_value (part, "bounce_code", "option", "permission");
638                         break;
639                 }
640                 break;
641
642         case E2K_ACTION_FORWARD:
643         case E2K_ACTION_DELEGATE:
644         {
645                 int i, j;
646                 E2kAddrList *list;
647                 E2kAddrEntry *entry;
648                 E2kPropValue *pv;
649                 const char *display_name, *email;
650                 char *full_addr;
651
652                 list = act->act.addr_list;
653                 for (i = 0; i < list->nentries; i++) {
654                         entry = &list->entry[i];
655                         display_name = email = NULL;
656                         for (j = 0; j < entry->nvalues; j++) {
657                                 pv = &entry->propval[j];
658                                 if (pv->prop.proptag == E2K_PROPTAG_PR_TRANSMITTABLE_DISPLAY_NAME)
659                                         display_name = pv->value;
660                                 else if (pv->prop.proptag == E2K_PROPTAG_PR_EMAIL_ADDRESS)
661                                         email = pv->value;
662                         }
663
664                         if (!email)
665                                 continue;
666                         if (display_name)
667                                 full_addr = g_strdup_printf ("%s <%s>", display_name, email);
668                         else
669                                 full_addr = g_strdup_printf ("<%s>", email);
670
671                         part = new_part (act->type == E2K_ACTION_FORWARD ? "forward" : "delegate");
672                         value = new_value (part, "recipient", "recipient", NULL);
673                         xmlNewTextChild (value, NULL, "recipient", full_addr);
674                         g_free (full_addr);
675
676                         xmlAddChild (actionset, part);
677                 }
678                 return TRUE;
679         }
680
681         case E2K_ACTION_TAG:
682                 if (act->act.proptag.prop.proptag != E2K_PROPTAG_PR_IMPORTANCE)
683                         return FALSE;
684
685                 part = new_part ("set-importance");
686                 new_value_int (part, "importance", "option", "value",
687                                GPOINTER_TO_INT (act->act.proptag.value));
688                 break;
689
690         case E2K_ACTION_DELETE:
691                 part = new_part ("delete");
692                 break;
693
694         case E2K_ACTION_MARK_AS_READ:
695                 part = new_part ("mark-read");
696                 break;
697
698         default:
699                 return FALSE;
700         }
701
702         xmlAddChild (actionset, part);
703         return TRUE;
704 }
705
706 static gboolean
707 rule_to_xml (E2kRule *rule, xmlNode *ruleset)
708 {
709         xmlNode *top, *set;
710         E2kRestriction *rn;
711         int i;
712
713         top = xmlNewChild (ruleset, NULL, "rule", NULL);
714
715         xmlSetProp (top, "source", 
716                     (rule->state & E2K_RULE_STATE_ONLY_WHEN_OOF) ?
717                     "oof" : "incoming");
718         xmlSetProp (top, "enabled", (rule->state & E2K_RULE_STATE_ENABLED) ? "1" : "0");
719
720         if (rule->name)
721                 xmlNewTextChild (top, NULL, "title", rule->name);
722
723         set = xmlNewChild (top, NULL, "partset", NULL);
724         rn = rule->condition;
725         if (rn) {
726                 E2kRestrictionType wrap_type;
727
728                 if (rn->type == E2K_RESTRICTION_OR) {
729                         xmlSetProp (top, "grouping", "any");
730                         wrap_type = E2K_RESTRICTION_OR;
731                 } else {
732                         xmlSetProp (top, "grouping", "all");
733                         wrap_type = E2K_RESTRICTION_AND;
734                 }
735
736                 if (!restriction_to_xml (rn, set, wrap_type, FALSE)) {
737                         g_warning ("could not express restriction as xml");
738                         xmlUnlinkNode (top);
739                         xmlFreeNode (top);
740                         return FALSE;
741                 }
742         } else
743                 xmlSetProp (top, "grouping", "all");
744
745         set = xmlNewChild (top, NULL, "actionset", NULL);
746         for (i = 0; i < rule->actions->len; i++) {
747                 if (!action_to_xml (rule->actions->pdata[i], set)) {
748                         g_warning ("could not express action as xml");
749                         xmlUnlinkNode (top);
750                         xmlFreeNode (top);
751                         return FALSE;
752                 }
753         }
754
755         if (rule->state & E2K_RULE_STATE_EXIT_LEVEL)
756                 xmlAddChild (set, new_part ("stop"));
757
758         return TRUE;
759 }
760
761 /**
762  * e2k_rules_to_xml:
763  * @rules: an #E2kRules
764  *
765  * Encodes @rules into an XML format like that used by the evolution
766  * filter code.
767  *
768  * Return value: the XML rules
769  **/
770 xmlDoc *
771 e2k_rules_to_xml (E2kRules *rules)
772 {
773         xmlDoc *doc;
774         xmlNode *top, *ruleset;
775         int i;
776
777         doc = xmlNewDoc (NULL);
778         top = xmlNewNode (NULL, "filteroptions");
779         xmlDocSetRootElement (doc, top);
780
781         ruleset = xmlNewChild (top, NULL, "ruleset", NULL);
782
783         for (i = 0; i < rules->rules->len; i++)
784                 rule_to_xml (rules->rules->pdata[i], ruleset);
785
786         return doc;
787 }