Add support for Sieve body extension
parent
3b7a3c94d4
commit
c160edf20a
2
NEWS
2
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 <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)
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue