diff --git a/NEWS b/NEWS
index 6f526b790..1770ec45e 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,7 @@ New features
mail editor; will also now display progress of uploads
- new popup menu to download all attachments of a mail
- move & copy messages between different accounts
+ - support for the Sieve 'body' extension (mail filter based on the body content)
Enhancements
- we now automatically convert
into file attachments
@@ -17,6 +18,7 @@ Enhancements
- format time in attendees invitation window according to the user's locale
- improved IE11 support
- respect signature placement when forwarding a message
+ - respect Sieve server capabilities
Bug fixes
- don't load 'background' attribute (#2437)
diff --git a/SoObjects/Mailer/SOGoMailAccount.m b/SoObjects/Mailer/SOGoMailAccount.m
index 276bc208a..2beb53acb 100644
--- a/SoObjects/Mailer/SOGoMailAccount.m
+++ b/SoObjects/Mailer/SOGoMailAccount.m
@@ -311,10 +311,7 @@ static NSString *inboxFolderName = @"INBOX";
manager = [SOGoSieveManager sieveManagerForUser: [context activeUser]];
- return [manager updateFiltersForLogin: [[self imap4URL] user]
- authname: [[self imap4URL] user]
- password: [self imap4PasswordRenewed: NO]
- account: self];
+ return [manager updateFiltersForAccount: self];
}
diff --git a/SoObjects/SOGo/SOGoSieveManager.h b/SoObjects/SOGo/SOGoSieveManager.h
index 2f180426a..783a948d8 100644
--- a/SoObjects/SOGo/SOGoSieveManager.h
+++ b/SoObjects/SOGo/SOGoSieveManager.h
@@ -29,6 +29,7 @@
@class NSDictionary;
@class NSMutableArray;
@class NSString;
+@class NGSieveClient;
@class SOGoMailAccount;
@class SOGoUser;
@@ -45,10 +46,9 @@
- (NSString *) sieveScriptWithRequirements: (NSMutableArray *) newRequirements;
- (NSString *) lastScriptError;
-- (BOOL) updateFiltersForLogin: (NSString *) theLogin
- authname: (NSString *) theAuthName
- password: (NSString *) thePassword
- account: (SOGoMailAccount *) theAccount;
+- (NGSieveClient *) clientForAccount: (SOGoMailAccount *) theAccount;
+
+- (BOOL) updateFiltersForAccount: (SOGoMailAccount *) theAccount;
@end
diff --git a/SoObjects/SOGo/SOGoSieveManager.m b/SoObjects/SOGo/SOGoSieveManager.m
index 603e21e28..99a55bf03 100644
--- a/SoObjects/SOGo/SOGoSieveManager.m
+++ b/SoObjects/SOGo/SOGoSieveManager.m
@@ -42,6 +42,7 @@
typedef enum {
UIxFilterFieldTypeAddress,
UIxFilterFieldTypeHeader,
+ UIxFilterFieldTypeBody,
UIxFilterFieldTypeSize,
} UIxFilterFieldType;
@@ -50,6 +51,7 @@ static NSArray *sieveSizeOperators = nil;
static NSMutableDictionary *fieldTypes = nil;
static NSDictionary *sieveFields = nil;
static NSDictionary *sieveFlags = nil;
+static NSDictionary *typeRequirements = nil;
static NSDictionary *operatorRequirements = nil;
static NSMutableDictionary *methodRequirements = nil;
static NSString *sieveScriptName = @"sogo";
@@ -136,13 +138,13 @@ static NSString *sieveScriptName = @"sogo";
fieldTypes = [NSMutableDictionary new];
fields = [NSArray arrayWithObjects: @"to", @"cc", @"to_or_cc", @"from",
nil];
- [fieldTypes setObject: [NSNumber
- numberWithInt: UIxFilterFieldTypeAddress]
+ [fieldTypes setObject: [NSNumber numberWithInt: UIxFilterFieldTypeAddress]
forKeys: fields];
fields = [NSArray arrayWithObjects: @"header", @"subject", nil];
- [fieldTypes setObject: [NSNumber
- numberWithInt: UIxFilterFieldTypeHeader]
+ [fieldTypes setObject: [NSNumber numberWithInt: UIxFilterFieldTypeHeader]
forKeys: fields];
+ [fieldTypes setObject: [NSNumber numberWithInt: UIxFilterFieldTypeBody]
+ forKey: @"body"];
[fieldTypes setObject: [NSNumber numberWithInt: UIxFilterFieldTypeSize]
forKey: @"size"];
}
@@ -177,6 +179,14 @@ static NSString *sieveScriptName = @"sogo";
nil];
[sieveFlags retain];
}
+ if (!typeRequirements)
+ {
+ typeRequirements
+ = [NSDictionary dictionaryWithObjectsAndKeys:
+ @"body", [NSNumber numberWithInt: UIxFilterFieldTypeBody],
+ nil];
+ [typeRequirements retain];
+ }
if (!operatorRequirements)
{
operatorRequirements
@@ -252,7 +262,7 @@ static NSString *sieveScriptName = @"sogo";
andType: (UIxFilterFieldType *) type
{
NSNumber *fieldType;
- NSString *jsonField, *customHeader;
+ NSString *jsonField, *customHeader, *requirement;
jsonField = [rule objectForKey: @"field"];
if (jsonField)
@@ -270,10 +280,15 @@ static NSString *sieveScriptName = @"sogo";
scriptError = (@"Pseudo-header field 'header' without"
@" 'custom_header' parameter.");
}
- else if ([jsonField isEqualToString: @"size"])
+ else if ([jsonField isEqualToString: @"body"] ||
+ [jsonField isEqualToString: @"size"])
*field = nil;
else
*field = [sieveFields objectForKey: jsonField];
+
+ requirement = [typeRequirements objectForKey: fieldType];
+ if (requirement)
+ [requirements addObjectUniquely: requirement];
}
else
scriptError
@@ -332,6 +347,7 @@ static NSString *sieveScriptName = @"sogo";
if (type == UIxFilterFieldTypeSize)
rc = [sieveSizeOperators containsObject: operator];
else
+ // Header and Body types
rc = (![sieveSizeOperators containsObject: operator]
&& [sieveOperators containsObject: operator]);
@@ -370,17 +386,23 @@ static NSString *sieveScriptName = @"sogo";
sieveRule = [NSMutableString stringWithCapacity: 100];
if (revert)
[sieveRule appendString: @"not "];
+
if (type == UIxFilterFieldTypeAddress)
[sieveRule appendString: @"address "];
else if (type == UIxFilterFieldTypeHeader)
[sieveRule appendString: @"header "];
+ else if (type == UIxFilterFieldTypeBody)
+ [sieveRule appendString: @"body :text "];
else if (type == UIxFilterFieldTypeSize)
[sieveRule appendString: @"size "];
[sieveRule appendFormat: @":%@ ", operator];
+
if (type == UIxFilterFieldTypeSize)
[sieveRule appendFormat: @"%@K", value];
- else
+ else if (field)
[sieveRule appendFormat: @"%@ %@", field, value];
+ else
+ [sieveRule appendFormat: @"%@", value];
return sieveRule;
}
@@ -529,24 +551,21 @@ static NSString *sieveScriptName = @"sogo";
{
if ([match isEqualToString: @"all"] || [match isEqualToString: @"any"])
{
- sieveRules
- = [self _extractSieveRules: [newScript objectForKey: @"rules"]];
+ sieveRules = [self _extractSieveRules: [newScript objectForKey: @"rules"]];
if (sieveRules)
[sieveText appendFormat: @"if %@of (%@) {\r\n",
- match,
- [sieveRules componentsJoinedByString: @", "]];
+ match,
+ [sieveRules componentsJoinedByString: @", "]];
else
scriptError = [NSString stringWithFormat:
- @"Test '%@' used without any"
+ @"Test '%@' used without any"
@" specified rule",
match];
}
else
- scriptError = [NSString stringWithFormat: @"Bad test: %@",
- match];
+ scriptError = [NSString stringWithFormat: @"Bad test: %@", match];
}
- sieveActions = [self _extractSieveActions:
- [newScript objectForKey: @"actions"]];
+ sieveActions = [self _extractSieveActions: [newScript objectForKey: @"actions"]];
if ([sieveActions count])
[sieveText appendFormat: @" %@;\r\n",
[sieveActions componentsJoinedByString: @";\r\n "]];
@@ -604,34 +623,25 @@ static NSString *sieveScriptName = @"sogo";
//
//
//
-- (BOOL) updateFiltersForLogin: (NSString *) theLogin
- authname: (NSString *) theAuthName
- password: (NSString *) thePassword
- account: (SOGoMailAccount *) theAccount
+- (NGSieveClient *) clientForAccount: (SOGoMailAccount *) theAccount
{
- NSMutableArray *req;
- NSMutableString *script, *header;
- NSDictionary *result, *values;
- SOGoUserDefaults *ud;
+ NSDictionary *result;
+ NSString *login, *authname, *password;
SOGoDomainDefaults *dd;
NGSieveClient *client;
- NSString *filterScript, *v, *sieveServer, *sieveScheme, *sieveQuery, *imapServer;
+ NSString *sieveServer, *sieveScheme, *sieveQuery, *imapServer;
NSURL *url, *cUrl;
-
int sievePort;
- BOOL b, connected;
+ BOOL connected;
dd = [user domainDefaults];
- if (!([dd sieveScriptsEnabled] || [dd vacationEnabled] || [dd forwardEnabled]))
- return YES;
-
- req = [NSMutableArray arrayWithCapacity: 15];
- ud = [user userDefaults];
-
connected = YES;
- b = NO;
-
+ // Extract credentials from mail account
+ login = [[theAccount imap4URL] user];
+ authname = [[theAccount imap4URL] user];
+ password = [theAccount imap4PasswordRenewed: NO];
+
// We connect to our Sieve server and check capabilities, in order
// to generate the right script, based on capabilities
//
@@ -687,20 +697,20 @@ static NSString *sieveScriptName = @"sogo";
sieveScheme, sieveServer, sievePort, sieveQuery]];
client = [[NGSieveClient alloc] initWithURL: url];
-
+
if (!client) {
NSLog(@"Sieve connection failed on %@", [url description]);
- return NO;
+ return nil;
}
-
- if (!thePassword) {
+
+ if (!password) {
[client closeConnection];
- return NO;
+ return nil;
}
NS_DURING
{
- result = [client login: theLogin authname: theAuthName password: thePassword];
+ result = [client login: login authname: authname password: password];
}
NS_HANDLER
{
@@ -711,22 +721,50 @@ static NSString *sieveScriptName = @"sogo";
if (!connected)
{
NSLog(@"Sieve connection failed on %@", [url description]);
- return NO;
+ return nil;
}
if (![[result valueForKey:@"result"] boolValue]) {
NSLog(@"failure. Attempting with a renewed password (no authname supported)");
- thePassword = [theAccount imap4PasswordRenewed: YES];
- result = [client login: theLogin password: thePassword];
+ password = [theAccount imap4PasswordRenewed: YES];
+ result = [client login: login password: password];
}
-
+
if (![[result valueForKey:@"result"] boolValue]) {
NSLog(@"Could not login '%@' on Sieve server: %@: %@",
- theLogin, client, result);
+ login, client, result);
[client closeConnection];
- return NO;
+ return nil;
}
-
+
+ return client;
+}
+
+//
+//
+//
+- (BOOL) updateFiltersForAccount: (SOGoMailAccount *) theAccount
+{
+ NSMutableArray *req;
+ NSMutableString *script, *header;
+ NSDictionary *result, *values;
+ SOGoUserDefaults *ud;
+ SOGoDomainDefaults *dd;
+ NGSieveClient *client;
+ NSString *filterScript, *v;
+ BOOL b;
+
+ dd = [user domainDefaults];
+ if (!([dd sieveScriptsEnabled] || [dd vacationEnabled] || [dd forwardEnabled]))
+ return YES;
+
+ req = [NSMutableArray arrayWithCapacity: 15];
+ ud = [user userDefaults];
+
+ client = [self clientForAccount: theAccount];
+ if (!client)
+ return NO;
+
// We adjust the "methodRequirements" based on the server's
// capabilities. Cyrus exposes "imapflags" while Dovecot (and
// potentially others) expose "imap4flags" as specified in RFC5332
diff --git a/UI/PreferencesUI/UIxPreferences.m b/UI/PreferencesUI/UIxPreferences.m
index 612d1eb6c..a4fc1baa9 100644
--- a/UI/PreferencesUI/UIxPreferences.m
+++ b/UI/PreferencesUI/UIxPreferences.m
@@ -41,6 +41,7 @@
#import
#import
#import
+#import
#import
#import
#import
@@ -837,13 +838,23 @@ static NSArray *reminderValues = nil;
{
#warning sieve caps should be deduced from the server
static NSArray *capabilities = nil;
+ SOGoMailAccounts *folder;
+ SOGoMailAccount *account;
+ SOGoSieveManager *manager;
+ NGSieveClient *client;
if (!capabilities)
{
- capabilities = [NSArray arrayWithObjects: @"fileinto", @"reject",
- @"envelope", @"vacation", @"imapflags",
- @"notify", @"subaddress", @"relational",
- @"comparator-i;ascii-numeric", @"regex", nil];
+ folder = [[self clientObject] mailAccountsFolder: @"Mail"
+ inContext: context];
+ account = [folder lookupName: @"0" inContext: context acquire: NO];
+ manager = [SOGoSieveManager sieveManagerForUser: [context activeUser]];
+ client = [manager clientForAccount: account];
+
+ if (client)
+ capabilities = [client capabilities];
+ else
+ capabilities = [NSArray array];
[capabilities retain];
}
diff --git a/UI/WebServerResources/UIxFilterEditor.js b/UI/WebServerResources/UIxFilterEditor.js
index cfa8c3b38..16f74b7ca 100644
--- a/UI/WebServerResources/UIxFilterEditor.js
+++ b/UI/WebServerResources/UIxFilterEditor.js
@@ -63,7 +63,8 @@ function setupConstants() {
"cc": _("Cc"),
"to_or_cc": _("To or Cc"),
"size": _("Size (Kb)"),
- "header": _("Header") };
+ "header": _("Header"),
+ "body": _("Body") };
methodLabels = { "addflag": _("Flag the message with:"),
"discard": _("Discard the message"),
"fileinto": _("File the message in:"),
@@ -272,8 +273,10 @@ function ensureFieldRepresentation(container) {
}
function ensureFieldSelectRepresentation(container, fieldSpan) {
- var fields
- = [ "subject", "from", "to", "cc", "to_or_cc", "size", "header" ];
+ var fields = [ "subject", "from", "to", "cc", "to_or_cc", "size", "header" ];
+ if (sieveCapabilities.indexOf("body") > -1) {
+ fields.push("body");
+ }
var selects = fieldSpan.select("SELECT");
var select;
if (selects.length)
@@ -373,8 +376,7 @@ function ensureOperatorSelectRepresentation(container, operatorSpan) {
var operatorOption = createElement("option", null, null,
{ value: operator }, null,
select);
- operatorOption.appendChild(document
- .createTextNode(operatorLabels[operator]));
+ operatorOption.appendChild(document.createTextNode(operatorLabels[operator]));
}
operatorSpan.appendChild(select);
}