1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 /* Copyright (C) 2002-2004 Novell, Inc.
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.
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.
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.
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"
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
41 fuzzy_level_from_name (const char *name, const char *map[],
42 int *fuzzy_level, gboolean *negated)
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);
58 static inline const char *
59 fuzzy_level_to_name (int fuzzy_level, gboolean negated, const char *map[])
61 fuzzy_level = E2K_FL_MATCH_TYPE (fuzzy_level);
63 fuzzy_level |= E2K_FL_NEGATE;
65 return map[fuzzy_level];
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 };
74 relop_from_name (const char *name, const char *map[],
75 E2kRestrictionRelop *relop)
79 for (i = 0; i < E2K_RELOP_RE; i++) {
80 if (map[i] && !strcmp (name, map[i])) {
90 static inline const char *
91 relop_to_name (E2kRestrictionRelop relop, gboolean negated, const char *map[])
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
99 relop = negate_map[relop];
104 /* Check if @rn encodes Outlook's "Message was sent only to me" rule */
106 restriction_is_only_to_me (E2kRestriction *rn)
110 if (rn->type != E2K_RESTRICTION_AND || rn->res.and.nrns != 3)
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)
120 sub = rn->res.and.rns[1];
121 if (sub->type != E2K_RESTRICTION_NOT)
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)
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)
139 /* Check if @rn encodes Outlook's "is a delegatable meeting request" rule */
141 restriction_is_delegation (E2kRestriction *rn)
145 if (rn->type != E2K_RESTRICTION_AND || rn->res.and.nrns != 3)
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)
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)
161 sub = rn->res.and.rns[2];
162 if (sub->type != E2K_RESTRICTION_OR || sub->res.or.nrns != 2)
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)
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)
182 new_value (xmlNode *part, const char *name, const char *type, const char *value)
186 node = xmlNewChild (part, NULL, "value", NULL);
187 xmlSetProp (node, "name", name);
188 xmlSetProp (node, "type", type);
190 xmlSetProp (node, "value", value);
196 new_value_int (xmlNode *part, const char *name, const char *type,
197 const char *value_name, long value)
202 node = xmlNewChild (part, NULL, "value", NULL);
203 xmlSetProp (node, "name", name);
204 xmlSetProp (node, "type", type);
206 str = g_strdup_printf ("%ld", value);
207 xmlSetProp (node, value_name, str);
214 new_part (const char *part_name)
218 part = xmlNewNode (NULL, "part");
219 xmlSetProp (part, "name", part_name);
224 match (const char *part_name, const char *value_name, const char *value_value,
225 const char *string_name, const char *string_value)
227 xmlNode *part, *value;
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);
238 message_is (const char *name, const char *type_name,
239 const char *kind, gboolean negated)
243 part = new_part (name);
244 new_value (part, type_name, "option", negated ? "is not" : "is");
245 new_value (part, "kind", "option", kind);
251 address_is (E2kRestriction *comment_rn, gboolean recipients, gboolean negated)
256 const char *relation, *display_name, *p;
257 char *addr, *full_addr;
261 rn = comment_rn->res.comment.rn;
262 if (rn->type != E2K_RESTRICTION_PROPERTY ||
263 rn->res.property.relop != E2K_RELOP_EQ)
265 pv = &rn->res.property.pv;
267 if ((recipients && pv->prop.proptag != E2K_PROPTAG_PR_SEARCH_KEY) ||
268 (!recipients && pv->prop.proptag != E2K_PROPTAG_PR_SENDER_SEARCH_KEY))
271 relation = relop_to_name (rn->res.property.relop, negated, is_types);
275 /* Extract the address part */
277 p = strchr ((char *)ba->data, ':');
279 addr = g_ascii_strdown (p + 1, -1);
281 addr = g_ascii_strdown ((char *)ba->data, -1);
283 /* Find the display name in the comment */
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;
294 full_addr = g_strdup_printf ("%s <%s>", display_name, addr);
296 full_addr = g_strdup_printf ("<%s>", addr);
299 part = match ("recipient", "recipient-type", relation,
300 "recipient", full_addr);
302 part = match ("sender", "sender-type", relation,
303 "sender", full_addr);
312 restriction_to_xml (E2kRestriction *rn, xmlNode *partset,
313 E2kRestrictionType wrap_type, gboolean negated)
315 xmlNode *part, *value, *node;
317 const char *match_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",
329 } else if (restriction_is_delegation (rn)) {
330 part = message_is ("special-message",
331 "special-message-type",
332 "delegated-meeting-request",
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.
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)
350 * This handles both cases.
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],
363 /* Otherwise, we have a rule that can't be expressed
364 * as "match all" or "match any".
368 case E2K_RESTRICTION_NOT:
369 return restriction_to_xml (rn->res.not.rn, partset,
370 wrap_type, !negated);
372 case E2K_RESTRICTION_CONTENT:
374 int fuzzy_level = E2K_FL_MATCH_TYPE (rn->res.content.fuzzy_level);
376 pv = &rn->res.content.pv;
378 switch (pv->prop.proptag) {
379 case E2K_PROPTAG_PR_BODY:
380 match_type = fuzzy_level_to_name (fuzzy_level, negated,
385 part = match ("body", "body-type", match_type,
389 case E2K_PROPTAG_PR_SUBJECT:
390 match_type = fuzzy_level_to_name (fuzzy_level, negated,
395 part = match ("subject", "subject-type", match_type,
396 "subject", pv->value);
399 case E2K_PROPTAG_PR_TRANSPORT_MESSAGE_HEADERS:
400 match_type = fuzzy_level_to_name (fuzzy_level, negated,
405 part = match ("full-headers", "full-headers-type",
406 match_type, "word", pv->value);
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",
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);
431 case E2K_RESTRICTION_PROPERTY:
433 E2kRestrictionRelop relop;
434 const char *relation;
436 relop = rn->res.property.relop;
437 if (relop >= E2K_RELOP_RE)
440 pv = &rn->res.property.pv;
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))
448 part = message_is ("message-to-me",
449 "message-to-me-type",
453 case E2K_PROPTAG_PR_MESSAGE_CC_ME:
454 if ((relop == E2K_RELOP_EQ && !pv->value) ||
455 (relop == E2K_RELOP_NE && pv->value))
458 part = message_is ("message-to-me",
459 "message-to-me-type",
463 case E2K_PROPTAG_PR_MESSAGE_DELIVERY_TIME:
464 case E2K_PROPTAG_PR_CLIENT_SUBMIT_TIME:
468 relation = relop_to_name (relop, negated, date_types);
472 if (pv->prop.proptag == E2K_PROPTAG_PR_MESSAGE_DELIVERY_TIME)
473 part = new_part ("received-date");
475 part = new_part ("sent-date");
477 value = new_value (part, "date-spec-type", "option", relation);
478 value = new_value (part, "versus", "datespec", NULL);
480 node = xmlNewChild (value, NULL, "datespec", NULL);
481 xmlSetProp (node, "type", "1");
483 timestamp = g_strdup_printf ("%lu", (unsigned long)e2k_parse_timestamp (pv->value));
484 xmlSetProp (node, "value", timestamp);
490 case E2K_PROPTAG_PR_MESSAGE_SIZE:
491 relation = relop_to_name (relop, negated, size_types);
495 part = new_part ("size");
497 new_value (part, "size-type", "option", relation);
498 new_value_int (part, "versus", "integer", "integer",
499 GPOINTER_TO_INT (pv->value) / 1024);
503 case E2K_PROPTAG_PR_IMPORTANCE:
504 relation = relop_to_name (relop, negated, is_types);
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));
514 case E2K_PROPTAG_PR_SENSITIVITY:
515 relation = relop_to_name (relop, negated, is_types);
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));
532 case E2K_RESTRICTION_COMMENT:
533 part = address_is (rn, FALSE, negated);
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)
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");
548 new_value (part, "match-type", "option",
549 negated ? "exist" : "not exist");
553 case E2K_RESTRICTION_SUBRESTRICTION:
554 if (rn->res.sub.subtable.proptag != E2K_PROPTAG_PR_MESSAGE_RECIPIENTS)
556 if (rn->res.sub.rn->type != E2K_RESTRICTION_COMMENT)
559 part = address_is (rn->res.sub.rn, TRUE, negated);
568 xmlAddChild (partset, part);
574 stringify_entryid (guint8 *data, int len)
580 string = g_string_new (NULL);
582 for (i = 0; i < len && i < 22; i++)
583 g_string_append_printf (string, "%02x", data[i]);
584 if (i < len && data[i]) {
586 g_string_append_printf (string, "%02x", data[i]);
590 g_string_free (string, FALSE);
595 action_to_xml (E2kAction *act, xmlNode *actionset)
597 xmlNode *part, *value;
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);
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);
623 case E2K_ACTION_DEFER:
624 part = new_part ("defer");
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");
633 case E2K_ACTION_BOUNCE_CODE_FORM_MISMATCH:
634 new_value (part, "bounce_code", "option", "form-mismatch");
636 case E2K_ACTION_BOUNCE_CODE_ACCESS_DENIED:
637 new_value (part, "bounce_code", "option", "permission");
642 case E2K_ACTION_FORWARD:
643 case E2K_ACTION_DELEGATE:
649 const char *display_name, *email;
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)
667 full_addr = g_strdup_printf ("%s <%s>", display_name, email);
669 full_addr = g_strdup_printf ("<%s>", email);
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);
676 xmlAddChild (actionset, part);
682 if (act->act.proptag.prop.proptag != E2K_PROPTAG_PR_IMPORTANCE)
685 part = new_part ("set-importance");
686 new_value_int (part, "importance", "option", "value",
687 GPOINTER_TO_INT (act->act.proptag.value));
690 case E2K_ACTION_DELETE:
691 part = new_part ("delete");
694 case E2K_ACTION_MARK_AS_READ:
695 part = new_part ("mark-read");
702 xmlAddChild (actionset, part);
707 rule_to_xml (E2kRule *rule, xmlNode *ruleset)
713 top = xmlNewChild (ruleset, NULL, "rule", NULL);
715 xmlSetProp (top, "source",
716 (rule->state & E2K_RULE_STATE_ONLY_WHEN_OOF) ?
718 xmlSetProp (top, "enabled", (rule->state & E2K_RULE_STATE_ENABLED) ? "1" : "0");
721 xmlNewTextChild (top, NULL, "title", rule->name);
723 set = xmlNewChild (top, NULL, "partset", NULL);
724 rn = rule->condition;
726 E2kRestrictionType wrap_type;
728 if (rn->type == E2K_RESTRICTION_OR) {
729 xmlSetProp (top, "grouping", "any");
730 wrap_type = E2K_RESTRICTION_OR;
732 xmlSetProp (top, "grouping", "all");
733 wrap_type = E2K_RESTRICTION_AND;
736 if (!restriction_to_xml (rn, set, wrap_type, FALSE)) {
737 g_warning ("could not express restriction as xml");
743 xmlSetProp (top, "grouping", "all");
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");
755 if (rule->state & E2K_RULE_STATE_EXIT_LEVEL)
756 xmlAddChild (set, new_part ("stop"));
763 * @rules: an #E2kRules
765 * Encodes @rules into an XML format like that used by the evolution
768 * Return value: the XML rules
771 e2k_rules_to_xml (E2kRules *rules)
774 xmlNode *top, *ruleset;
777 doc = xmlNewDoc (NULL);
778 top = xmlNewNode (NULL, "filteroptions");
779 xmlDocSetRootElement (doc, top);
781 ruleset = xmlNewChild (top, NULL, "ruleset", NULL);
783 for (i = 0; i < rules->rules->len; i++)
784 rule_to_xml (rules->rules->pdata[i], ruleset);