Add support for Sieve body extension

pull/17/head
Francis Lachapelle 2014-01-27 15:09:22 -05:00
parent 3b7a3c94d4
commit c160edf20a
6 changed files with 115 additions and 65 deletions

2
NEWS
View File

@ -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 <img src=data...> 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)

View File

@ -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];
}

View File

@ -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

View File

@ -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

View File

@ -41,6 +41,7 @@
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserDefaults.h>
#import <SOGo/SOGoDomainDefaults.h>
#import <SOGo/SOGoSieveManager.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <SOGo/SOGoUserFolder.h>
#import <SOGo/WOResourceManager+SOGo.h>
@ -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];
}

View File

@ -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);
}