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 gchar *contains_types[] = { NULL, "contains", NULL, NULL, NULL, "not contains", NULL, NULL };
35
static const gchar *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 gchar *name, const gchar *map[],
42
gint *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 gchar *
59
fuzzy_level_to_name (gint fuzzy_level, gboolean negated, const gchar *map[])
61
fuzzy_level = E2K_FL_MATCH_TYPE (fuzzy_level);
63
fuzzy_level |= E2K_FL_NEGATE;
65
return map[fuzzy_level];
68
static const gchar *is_types[] = { NULL, NULL, NULL, NULL, "is", "is not" };
69
static const gchar *date_types[] = { "before", "before", "after", "after", NULL, NULL };
70
static const gchar *gsizeypes[] = { "less than", "less than", "greater than", "greater than", NULL, NULL };
74
relop_from_name (const gchar *name, const gchar *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 gchar *
91
relop_to_name (E2kRestrictionRelop relop, gboolean negated, const gchar *map[])
93
static const gint 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,
185
const xmlChar *value)
189
node = xmlNewChild (part, NULL, (xmlChar *) "value", NULL);
190
xmlSetProp (node, (xmlChar *) "name", name);
191
xmlSetProp (node, (xmlChar *) "type", type);
193
xmlSetProp (node, (xmlChar *) "value", value);
199
new_value_int (xmlNode *part,
202
const xmlChar *value_name,
208
node = xmlNewChild (part, NULL, (xmlChar *) "value", NULL);
209
xmlSetProp (node, (xmlChar *) "name", name);
210
xmlSetProp (node, (xmlChar *) "type", type);
212
str = g_strdup_printf ("%ld", value);
213
xmlSetProp (node, (xmlChar *) value_name, (xmlChar *) str);
220
new_part (const xmlChar *part_name)
224
part = xmlNewNode (NULL, (xmlChar *) "part");
225
xmlSetProp (part, (xmlChar *) "name", part_name);
230
match (const xmlChar *part_name,
231
const xmlChar *value_name,
232
const xmlChar *value_value,
233
const xmlChar *string_name,
234
const xmlChar *string_value)
236
xmlNode *part, *value;
238
part = new_part (part_name);
240
part, value_name, (xmlChar *) "option", value_value);
241
value = new_value (part, string_name, (xmlChar *) "string", NULL);
242
xmlNewTextChild (value, NULL, (xmlChar *) "string", string_value);
248
message_is (const xmlChar *name,
249
const xmlChar *type_name,
255
part = new_part (name);
257
part, type_name, (xmlChar *) "option",
258
negated ? (xmlChar *) "is not" : (xmlChar *) "is");
259
new_value (part, (xmlChar *) "kind", (xmlChar *) "option", kind);
265
address_is (E2kRestriction *comment_rn, gboolean recipients, gboolean negated)
270
const gchar *relation, *display_name, *p;
271
gchar *addr, *full_addr;
275
rn = comment_rn->res.comment.rn;
276
if (rn->type != E2K_RESTRICTION_PROPERTY ||
277
rn->res.property.relop != E2K_RELOP_EQ)
279
pv = &rn->res.property.pv;
281
if ((recipients && pv->prop.proptag != E2K_PROPTAG_PR_SEARCH_KEY) ||
282
(!recipients && pv->prop.proptag != E2K_PROPTAG_PR_SENDER_SEARCH_KEY))
285
relation = relop_to_name (rn->res.property.relop, negated, is_types);
289
/* Extract the address part */
291
p = strchr ((gchar *)ba->data, ':');
293
addr = g_ascii_strdown (p + 1, -1);
295
addr = g_ascii_strdown ((gchar *)ba->data, -1);
297
/* Find the display name in the comment */
299
for (i = 0; i < comment_rn->res.comment.nprops; i++) {
300
pv = &comment_rn->res.comment.props[i];
301
if (E2K_PROPTAG_TYPE (pv->prop.proptag) == E2K_PT_UNICODE) {
302
display_name = pv->value;
308
full_addr = g_strdup_printf ("%s <%s>", display_name, addr);
310
full_addr = g_strdup_printf ("<%s>", addr);
314
(xmlChar *) "recipient",
315
(xmlChar *) "recipient-type",
316
(xmlChar *) relation,
317
(xmlChar *) "recipient",
318
(xmlChar *) full_addr);
321
(xmlChar *) "sender",
322
(xmlChar *) "sender-type",
323
(xmlChar *) relation,
324
(xmlChar *) "sender",
325
(xmlChar *) full_addr);
334
restriction_to_xml (E2kRestriction *rn, xmlNode *partset,
335
E2kRestrictionType wrap_type, gboolean negated)
337
xmlNode *part, *value, *node;
339
const gchar *match_type;
343
case E2K_RESTRICTION_AND:
344
case E2K_RESTRICTION_OR:
345
/* Check for special rules */
346
if (restriction_is_only_to_me (rn)) {
348
(xmlChar *) "message-to-me",
349
(xmlChar *) "message-to-me-type",
350
(xmlChar *) "only", negated);
352
} else if (restriction_is_delegation (rn)) {
354
(xmlChar *) "special-message",
355
(xmlChar *) "special-message-type",
356
(xmlChar *) "delegated-meeting-request",
361
/* If we are inside an "and" and hit another "and",
362
* we can just remove the extra level:
363
* (and foo (and bar baz) quux) =>
364
* (and foo bar baz quux)
365
* Likewise for "or"s.
367
* If we are inside an "and" and hit a "(not (or" (or
368
* vice versa), we can use DeMorgan's Law and then
369
* apply the above rule:
370
* (and foo (not (or bar baz)) quux) =>
371
* (and foo (and (not bar) (not baz)) quux) =>
372
* (and foo (not bar) (not baz) quux)
374
* This handles both cases.
376
if ((rn->type == wrap_type && !negated) ||
377
(rn->type != wrap_type && negated)) {
378
for (i = 0; i < rn->res.and.nrns; i++) {
379
if (!restriction_to_xml (rn->res.and.rns[i],
387
/* Otherwise, we have a rule that can't be expressed
388
* as "match all" or "match any".
392
case E2K_RESTRICTION_NOT:
393
return restriction_to_xml (rn->res.not.rn, partset,
394
wrap_type, !negated);
396
case E2K_RESTRICTION_CONTENT:
398
gint fuzzy_level = E2K_FL_MATCH_TYPE (rn->res.content.fuzzy_level);
400
pv = &rn->res.content.pv;
402
switch (pv->prop.proptag) {
403
case E2K_PROPTAG_PR_BODY:
404
match_type = fuzzy_level_to_name (fuzzy_level, negated,
411
(xmlChar *) "body-type",
412
(xmlChar *) match_type,
414
(xmlChar *) pv->value);
417
case E2K_PROPTAG_PR_SUBJECT:
418
match_type = fuzzy_level_to_name (fuzzy_level, negated,
424
(xmlChar *) "subject",
425
(xmlChar *) "subject-type",
426
(xmlChar *) match_type,
427
(xmlChar *) "subject",
428
(xmlChar *) pv->value);
431
case E2K_PROPTAG_PR_TRANSPORT_MESSAGE_HEADERS:
432
match_type = fuzzy_level_to_name (fuzzy_level, negated,
438
(xmlChar *) "full-headers",
439
(xmlChar *) "full-headers-type",
440
(xmlChar *) match_type,
442
(xmlChar *) pv->value);
445
case E2K_PROPTAG_PR_MESSAGE_CLASS:
446
if ((fuzzy_level == E2K_FL_FULLSTRING) &&
447
!strcmp (pv->value, "IPM.Note.Rules.OofTemplate.Microsoft")) {
449
(xmlChar *) "special-message",
450
(xmlChar *) "special-message-type",
451
(xmlChar *) "oof", negated);
452
} else if ((fuzzy_level == E2K_FL_PREFIX) &&
453
!strcmp (pv->value, "IPM.Schedule.Meeting")) {
455
(xmlChar *) "special-message",
456
(xmlChar *) "special-message-type",
457
(xmlChar *) "meeting-request", negated);
469
case E2K_RESTRICTION_PROPERTY:
471
E2kRestrictionRelop relop;
472
const gchar *relation;
474
relop = rn->res.property.relop;
475
if (relop >= E2K_RELOP_RE)
478
pv = &rn->res.property.pv;
480
switch (pv->prop.proptag) {
481
case E2K_PROPTAG_PR_MESSAGE_TO_ME:
482
if ((relop == E2K_RELOP_EQ && !pv->value) ||
483
(relop == E2K_RELOP_NE && pv->value))
487
(xmlChar *) "message-to-me",
488
(xmlChar *) "message-to-me-type",
489
(xmlChar *) "to", negated);
492
case E2K_PROPTAG_PR_MESSAGE_CC_ME:
493
if ((relop == E2K_RELOP_EQ && !pv->value) ||
494
(relop == E2K_RELOP_NE && pv->value))
498
(xmlChar *) "message-to-me",
499
(xmlChar *) "message-to-me-type",
500
(xmlChar *) "cc", negated);
503
case E2K_PROPTAG_PR_MESSAGE_DELIVERY_TIME:
504
case E2K_PROPTAG_PR_CLIENT_SUBMIT_TIME:
508
relation = relop_to_name (relop, negated, date_types);
512
if (pv->prop.proptag == E2K_PROPTAG_PR_MESSAGE_DELIVERY_TIME)
513
part = new_part ((xmlChar *) "received-date");
515
part = new_part ((xmlChar *) "sent-date");
519
(xmlChar *) "date-spec-type",
520
(xmlChar *) "option",
521
(xmlChar *) relation);
524
(xmlChar *) "versus",
525
(xmlChar *) "datespec", NULL);
528
value, NULL, (xmlChar *) "datespec", NULL);
534
timestamp = g_strdup_printf ("%lu", (gulong)e2k_parse_timestamp (pv->value));
538
(xmlChar *) timestamp);
543
case E2K_PROPTAG_PR_MESSAGE_SIZE:
544
relation = relop_to_name (relop, negated, gsizeypes);
548
part = new_part ((xmlChar *) "size");
551
(xmlChar *) "size-type",
552
(xmlChar *) "option",
553
(xmlChar *) relation);
556
(xmlChar *) "versus",
557
(xmlChar *) "integer",
558
(xmlChar *) "integer",
559
GPOINTER_TO_INT (pv->value) / 1024);
562
case E2K_PROPTAG_PR_IMPORTANCE:
563
relation = relop_to_name (relop, negated, is_types);
567
part = new_part ((xmlChar *) "importance");
570
(xmlChar *) "importance-type",
571
(xmlChar *) "option",
572
(xmlChar *) relation);
575
(xmlChar *) "importance",
576
(xmlChar *) "option",
578
GPOINTER_TO_INT (pv->value));
581
case E2K_PROPTAG_PR_SENSITIVITY:
582
relation = relop_to_name (relop, negated, is_types);
586
part = new_part ((xmlChar *) "sensitivity");
590
(xmlChar *) "sensitivity");
593
(xmlChar *) "sensitivity-type",
594
(xmlChar *) "option",
595
(xmlChar *) relation);
598
(xmlChar *) "sensitivity",
599
(xmlChar *) "option",
601
GPOINTER_TO_INT (pv->value));
610
case E2K_RESTRICTION_COMMENT:
611
part = address_is (rn, FALSE, negated);
616
case E2K_RESTRICTION_BITMASK:
617
if (rn->res.bitmask.prop.proptag != E2K_PROPTAG_PR_MESSAGE_FLAGS ||
618
rn->res.bitmask.mask != MAPI_MSGFLAG_HASATTACH)
621
part = new_part ((xmlChar *) "attachments");
622
if (rn->res.bitmask.bitop == E2K_BMR_NEZ) {
625
(xmlChar *) "match-type",
626
(xmlChar *) "option",
628
(xmlChar *) "not exist" :
629
(xmlChar *) "exist");
633
(xmlChar *) "match-type",
634
(xmlChar *) "option",
636
(xmlChar *) "exist" :
637
(xmlChar *) "not exist");
641
case E2K_RESTRICTION_SUBRESTRICTION:
642
if (rn->res.sub.subtable.proptag != E2K_PROPTAG_PR_MESSAGE_RECIPIENTS)
644
if (rn->res.sub.rn->type != E2K_RESTRICTION_COMMENT)
647
part = address_is (rn->res.sub.rn, TRUE, negated);
656
xmlAddChild (partset, part);
661
stringify_entryid (guint8 *data, gint len)
667
string = g_string_new (NULL);
669
for (i = 0; i < len && i < 22; i++)
670
g_string_append_printf (string, "%02x", data[i]);
671
if (i < len && data[i]) {
673
g_string_append_printf (string, "%02x", data[i]);
677
g_string_free (string, FALSE);
682
action_to_xml (E2kAction *act, xmlNode *actionset)
684
xmlNode *part, *value;
688
case E2K_ACTION_MOVE:
689
case E2K_ACTION_COPY:
691
act->type == E2K_ACTION_MOVE ?
692
(xmlChar *) "move-to-folder" :
693
(xmlChar *) "copy-to-folder");
696
(xmlChar *) "folder",
697
(xmlChar *) "folder-source-key", NULL);
698
entryid = stringify_entryid (
699
act->act.xfer.folder_source_key->data + 1,
700
act->act.xfer.folder_source_key->len - 1);
703
(xmlChar *) "entryid",
704
(xmlChar *) entryid);
708
case E2K_ACTION_REPLY:
709
case E2K_ACTION_OOF_REPLY:
711
act->type == E2K_ACTION_REPLY ?
712
(xmlChar *) "reply" :
713
(xmlChar *) "oof-reply");
716
(xmlChar *) "template",
717
(xmlChar *) "message-entryid", NULL);
718
entryid = stringify_entryid (
719
act->act.reply.entryid->data,
720
act->act.reply.entryid->len);
723
(xmlChar *) "entryid",
724
(xmlChar *) entryid);
728
case E2K_ACTION_DEFER:
729
part = new_part ((xmlChar *) "defer");
732
case E2K_ACTION_BOUNCE:
733
part = new_part ((xmlChar *) "bounce");
734
switch (act->act.bounce_code) {
735
case E2K_ACTION_BOUNCE_CODE_TOO_LARGE:
738
(xmlChar *) "bounce_code",
739
(xmlChar *) "option",
742
case E2K_ACTION_BOUNCE_CODE_FORM_MISMATCH:
745
(xmlChar *) "bounce_code",
746
(xmlChar *) "option",
747
(xmlChar *) "form-mismatch");
749
case E2K_ACTION_BOUNCE_CODE_ACCESS_DENIED:
752
(xmlChar *) "bounce_code",
753
(xmlChar *) "option",
754
(xmlChar *) "permission");
759
case E2K_ACTION_FORWARD:
760
case E2K_ACTION_DELEGATE:
766
const gchar *display_name, *email;
769
list = act->act.addr_list;
770
for (i = 0; i < list->nentries; i++) {
771
entry = &list->entry[i];
772
display_name = email = NULL;
773
for (j = 0; j < entry->nvalues; j++) {
774
pv = &entry->propval[j];
775
if (pv->prop.proptag == E2K_PROPTAG_PR_TRANSMITTABLE_DISPLAY_NAME)
776
display_name = pv->value;
777
else if (pv->prop.proptag == E2K_PROPTAG_PR_EMAIL_ADDRESS)
784
full_addr = g_strdup_printf ("%s <%s>", display_name, email);
786
full_addr = g_strdup_printf ("<%s>", email);
789
act->type == E2K_ACTION_FORWARD ?
790
(xmlChar *) "forward" :
791
(xmlChar *) "delegate");
794
(xmlChar *) "recipient",
795
(xmlChar *) "recipient", NULL);
798
(xmlChar *) "recipient",
799
(xmlChar *) full_addr);
802
xmlAddChild (actionset, part);
808
if (act->act.proptag.prop.proptag != E2K_PROPTAG_PR_IMPORTANCE)
811
part = new_part ((xmlChar *) "set-importance");
814
(xmlChar *) "importance",
815
(xmlChar *) "option",
817
GPOINTER_TO_INT (act->act.proptag.value));
820
case E2K_ACTION_DELETE:
821
part = new_part ((xmlChar *) "delete");
824
case E2K_ACTION_MARK_AS_READ:
825
part = new_part ((xmlChar *) "mark-read");
832
xmlAddChild (actionset, part);
837
rule_to_xml (E2kRule *rule, xmlNode *ruleset)
843
top = xmlNewChild (ruleset, NULL, (xmlChar *) "rule", NULL);
847
(xmlChar *) "source",
848
(rule->state & E2K_RULE_STATE_ONLY_WHEN_OOF) ?
849
(xmlChar *) "oof" : (xmlChar *) "incoming");
852
(xmlChar *) "enabled",
853
(rule->state & E2K_RULE_STATE_ENABLED) ?
854
(xmlChar *) "1" : (xmlChar *) "0");
860
(xmlChar *) rule->name);
862
set = xmlNewChild (top, NULL, (xmlChar *) "partset", NULL);
863
rn = rule->condition;
865
E2kRestrictionType wrap_type;
867
if (rn->type == E2K_RESTRICTION_OR) {
870
(xmlChar *) "grouping",
872
wrap_type = E2K_RESTRICTION_OR;
876
(xmlChar *) "grouping",
878
wrap_type = E2K_RESTRICTION_AND;
881
if (!restriction_to_xml (rn, set, wrap_type, FALSE)) {
882
g_warning ("could not express restriction as xml");
888
xmlSetProp (top, (xmlChar *) "grouping", (xmlChar *) "all");
890
set = xmlNewChild (top, NULL, (xmlChar *) "actionset", NULL);
891
for (i = 0; i < rule->actions->len; i++) {
892
if (!action_to_xml (rule->actions->pdata[i], set)) {
893
g_warning ("could not express action as xml");
900
if (rule->state & E2K_RULE_STATE_EXIT_LEVEL)
901
xmlAddChild (set, new_part ((xmlChar *) "stop"));
908
* @rules: an #E2kRules
910
* Encodes @rules into an XML format like that used by the evolution
913
* Return value: the XML rules
916
e2k_rules_to_xml (E2kRules *rules)
919
xmlNode *top, *ruleset;
922
doc = xmlNewDoc (NULL);
923
top = xmlNewNode (NULL, (xmlChar *) "filteroptions");
924
xmlDocSetRootElement (doc, top);
926
ruleset = xmlNewChild (top, NULL, (xmlChar *) "ruleset", NULL);
928
for (i = 0; i < rules->rules->len; i++)
929
rule_to_xml (rules->rules->pdata[i], ruleset);