feat(mail): initial support for ms-tnef (winmail.dat) body part

Fixes #2242
Fixes #4503
pull/301/head
Francis Lachapelle 2021-08-13 11:12:37 -04:00 committed by GitHub
parent 98dad1d2dc
commit 045f134321
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 3639 additions and 34 deletions

View File

@ -30,6 +30,7 @@ Mailer_OBJC_FILES += \
SOGoImageMailBodyPart.m \
SOGoMessageMailBodyPart.m \
SOGoCalendarMailBodyPart.m \
SOGoTNEFMailBodyPart.m \
SOGoVCardMailBodyPart.m \
\
SOGoMailForward.m \

View File

@ -157,8 +157,7 @@ static BOOL debugOn = NO;
{
if (!partInfo)
{
partInfo
= [[self mailObject] lookupInfoForBodyPart: [self bodyPartPath]];
partInfo = [[self mailObject] lookupInfoForBodyPart: [self bodyPartPath]];
[partInfo retain];
}
@ -546,7 +545,7 @@ static BOOL debugOn = NO;
return self;
/* hard coded for now */
switch ([pe length]) {
case 3:
if ([pe isEqualToString:@"gif"] ||
@ -588,6 +587,8 @@ static BOOL debugOn = NO;
classString = @"SOGoVCardMailBodyPart";
else if ([mimeType isEqualToString: @"message/rfc822"])
classString = @"SOGoMessageMailBodyPart";
else if ([mimeType isEqualToString: @"application/ms-tnef"])
classString = @"SOGoTNEFMailBodyPart";
else
{
classString = @"SOGoMailBodyPart";

View File

@ -0,0 +1,53 @@
/*
Copyright (C) 2021 Inverse inc.
This file is part of SOGo.
SOGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
SOGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with SOGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#ifndef __Mailer_SOGoTNEFMailBodyPart_H__
#define __Mailer_SOGoTNEFMailBodyPart_H__
#import "SOGoMailBodyPart.h"
@class NGMimeBodyPart;
@interface SOGoTNEFMailBodyPart : SOGoMailBodyPart
{
BOOL debugOn;
NSData *part;
NSString *filename;
NGMimeMultipartBody *bodyParts;
}
- (NGMimeMultipartBody *) bodyParts;
- (void) setFilename: (NSString *) newFilename;
- (void) setPart: (NGMimeBodyPart *) newPart;
- (void) setPartInfo: (id) newPartInfo;
- (void) decodeBLOB;
- (NGMimeBodyPart *) bodyPartForData: (NSData *) _data
withType: (NSString *) _type
andSubtype: (NSString *) _subtype;
- (NGMimeBodyPart *) bodyPartForAttachment: (NSData *) _data
withName: (NSString *) _name
andType: (NSString *) _type
andSubtype: (NSString *) _subtype
andContentId: (NSString *) _cid;
@end
#endif /* __Mailer_SOGoTNEFMailBodyPart_H__ */

View File

@ -0,0 +1,957 @@
/*
Copyright (C) 2021 Inverse inc.
This file is part of SOGo.
SOGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
The following code is a derivative work of the code from the Yerase's TNEF
Stream Reader which is licensed GPLv2.
*/
#import <Foundation/NSArray.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSValue.h>
#import <NGExtensions/NGHashMap.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGMime/NGMimeBodyPart.h>
#import <NGMime/NGMimeFileData.h>
#import <NGMime/NGMimeHeaderFields.h>
#import <NGMime/NGMimeMultipartBody.h>
#import <SOGo/RTFHandler.h>
#import <SOGo/SOGoBuild.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <SoObjects/Mailer/NSData+SMIME.h>
#import <ytnef.h>
#import "SOGoTNEFMailBodyPart.h"
#define UPR_TO_ATTENDEES_STRING 0x823B
#define UPR_CC_ATTENDEES_STRING 0x823C
#define UPR_ALL_ATTENDEES_STRING 0x8238
/*
SOGoTNEFMailBodyPart
A specialized SOGoMailBodyPart subclass for application/ms-tnef attachments. Can
be used to attach special SoMethods.
See the superclass for more information on part objects.
*/
unsigned char GetRruleCount(unsigned char a, unsigned char b) {
return ((a << 8) | b);
}
char *GetRruleDayname(unsigned char a) {
static char daystring[25];
*daystring = 0;
if (a & 0x01) {
strcat(daystring, "SU,");
}
if (a & 0x02) {
strcat(daystring, "MO,");
}
if (a & 0x04) {
strcat(daystring, "TU,");
}
if (a & 0x08) {
strcat(daystring, "WE,");
}
if (a & 0x10) {
strcat(daystring, "TH,");
}
if (a & 0x20) {
strcat(daystring, "FR,");
}
if (a & 0x40) {
strcat(daystring, "SA,");
}
if (strlen(daystring)) {
daystring[strlen(daystring) - 1] = 0;
}
return (daystring);
}
unsigned char GetRruleMonthNum(unsigned char a, unsigned char b) {
switch (a) {
case 0x00:
switch (b) {
case 0x00:
// Jan
return (1);
case 0xA3:
// May
return (5);
case 0xAE:
// Nov
return (11);
}
break;
case 0x60:
switch (b) {
case 0xAE:
// Feb
return (2);
case 0x51:
// Jun
return (6);
}
break;
case 0xE0:
switch (b) {
case 0x4B:
// Mar
return (3);
case 0x56:
// Sep
return (9);
}
break;
case 0x40:
switch (b) {
case 0xFA:
// Apr
return (4);
}
break;
case 0x20:
if (b == 0xFA) {
// Jul
return (7);
}
break;
case 0x80:
if (b == 0xA8) {
// Aug
return (8);
}
break;
case 0xA0:
if (b == 0xFF) {
// Oct
return (10);
}
break;
case 0xC0:
if (b == 0x56) {
return (12);
}
}
// Error
return (0);
}
@implementation SOGoTNEFMailBodyPart
/* Overwritten methods */
- (id) init
{
if ((self = [super init]))
{
debugOn = [[SOGoSystemDefaults sharedSystemDefaults] tnefDecoderDebugEnabled];
part = nil;
filename = nil;
bodyParts = [[NGMimeMultipartBody alloc] init];
}
return self;
}
- (id) initWithName: (NSString *) _name
inContainer: (id) _container
{
self = [super initWithName: _name inContainer: _container];
[self decodeBLOB];
return self;
}
- (void) dealloc
{
[part release];
[filename release];
[bodyParts release];
[super dealloc];
}
- (id) lookupName: (NSString *) _key
inContext: (id) _ctx
acquire: (BOOL) _flag
{
NSArray *parts;
int i;
if ([self isBodyPartKey: _key])
{
// _key is an integer
parts = [bodyParts parts];
i = [_key intValue] - 1;
if (i > -1 && i < [parts count])
{
[self setPart: [parts objectAtIndex: i]];
return self;
}
}
else if ([_key isEqualToString: [self filename]])
{
return self;
}
else if ([_key isEqualToString: @"asAttachment"])
{
[self setAsAttachment];
return self;
}
/* Fallback to super class */
return [super lookupName: _key inContext: _ctx acquire: _flag];
}
- (NSData *) fetchBLOB
{
if (part)
return [part body];
return [super fetchBLOB];
}
- (NSString *) filename
{
if (filename)
return filename;
else if (part)
return nil; // don't try to fetch the filename from the IMAP body structure
else
return [super filename];
}
- (id) partInfo
{
if (partInfo)
return partInfo;
else if (part)
return nil; // don't try to fetch the info from the IMAP body structure
else
return [super partInfo];
}
/* New methods */
- (NGMimeMultipartBody *) bodyParts
{
return bodyParts;
}
- (void) decodeBLOB
{
NSData *data;
NSEnumerator *list;
NSString *messageClass;
NSString *partName, *type, *subtype;
NSString *value, *attendee;
RTFHandler *handler;
DWORD signature;
DDWORD *classification;
dtr datetime;
TNEFStruct tnef;
variableLength *attachmentName;
variableLength *filedata;
variableLength buf;
[self setPart: nil];
partName = nil;
data = [self fetchBLOB];
memcpy(&signature, [data bytes], sizeof(DWORD));
if (TNEFCheckForSignature(signature) == 0)
{
TNEFInitialize(&tnef);
tnef.Debug = 0;
if (TNEFParseMemory((unsigned char *)[data bytes], [data length], &tnef) != -1)
{
messageClass = [NSString stringWithCString: tnef.messageClass];
if (debugOn)
{
NSLog(@"TNEF message class: %@", messageClass);
MAPIPrint(&tnef.MapiProperties);
}
if ([messageClass isEqualToString: @"IPM.Microsoft Mail.Note"])
{
if (tnef.subject.size > 0)
{
filedata = MAPIFindProperty(&(tnef.MapiProperties), PROP_TAG(PT_BINARY, PR_BODY_HTML));
if (filedata != MAPI_UNDEFINED)
{
partName = [NSString stringWithFormat: @"%s.html", tnef.subject.data];
data = [NSData dataWithBytes: filedata->data length: filedata->size];
}
else
{
filedata = MAPIFindProperty(&(tnef.MapiProperties), PROP_TAG(PT_BINARY, PR_RTF_COMPRESSED));
if (filedata != MAPI_UNDEFINED)
{
buf.data = DecompressRTF(filedata, &(buf.size));
if (buf.data != NULL)
{
partName = [NSString stringWithFormat: @"%s.html", tnef.subject.data];
data = [NSData dataWithBytes: buf.data length: buf.size];
handler = [[RTFHandler alloc] initWithData: data];
AUTORELEASE(handler);
data = [handler parse]; // RTF to HTML
}
}
}
if ([data length])
{
[self bodyPartForData: data
withType: @"text"
andSubtype: @"html"];
}
}
}
else if ([messageClass isEqualToString: @"IPM.Microsoft Schedule.MtgRespA"] || // tentative response
[messageClass isEqualToString: @"IPM.Microsoft Schedule.MtgRespP"] || // positive (accepted) response
[messageClass isEqualToString: @"IPM.Microsoft Schedule.MtgRespN"] || // negative (declined) response
[messageClass isEqualToString: @"IPM.Microsoft Schedule.MtgReq"]) // request (invitation)
{
// Meeting object -- construct text/calendar part
// Parse HTML body, if any
filedata = MAPIFindProperty(&(tnef.MapiProperties), PROP_TAG(PT_BINARY, PR_BODY_HTML));
if (filedata != MAPI_UNDEFINED && filedata->size > 0)
{
partName = [NSString stringWithFormat: @"%s.html", tnef.subject.data];
data = [NSData dataWithBytes: filedata->data length: filedata->size];
[self bodyPartForData: data
withType: @"text"
andSubtype: @"html"];
}
// Create ics attachment
NSMutableString *vcalendar = [NSMutableString stringWithString: @"BEGIN:VCALENDAR\n"];
BOOL isRequest = [messageClass isEqualToString: @"IPM.Microsoft Schedule.MtgReq"];
if (isRequest)
[vcalendar appendString: @"METHOD:REQUEST\n"];
else
[vcalendar appendString: @"METHOD:REPLY\n"];
[vcalendar appendFormat: @"PRODID:-//Inverse inc./SOGo %@//EN\n", SOGoVersion];
[vcalendar appendString: @"VERSION:2.0\n"];
[vcalendar appendString: @"BEGIN:VEVENT\n"];
// UID
// TODO: Probably wrong, probably irrelevant
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_BINARY, 0x3));
if (filedata == MAPI_UNDEFINED)
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_BINARY, 0x23));
if (filedata != MAPI_UNDEFINED && filedata->size > 1)
{
int i;
[vcalendar appendString: @"UID:"];
for (i = 0; i < filedata->size; i++)
{
[vcalendar appendFormat: @"%02X", (unsigned char)filedata->data[i]];
}
[vcalendar appendString: @"\n"];
}
// Sequence
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_LONG, 0x8201));
if (filedata != MAPI_UNDEFINED)
{
[vcalendar appendFormat: @"SEQUENCE:%i\n", (int)*filedata->data];
}
// Attendee email
if (isRequest)
{
// Organizer
filedata = MAPIFindProperty(&(tnef.MapiProperties), PROP_TAG(PT_BINARY, PR_SENDER_SEARCH_KEY));
if (filedata == MAPI_UNDEFINED)
{
filedata = MAPIFindProperty(&(tnef.MapiProperties), PROP_TAG(PT_UNICODE, PR_SENT_REPRESENTING_EMAIL_ADDRESS));
}
if (filedata != MAPI_UNDEFINED && filedata->size > 1)
{
NSArray *components;
NSString *email, *cn;
email = [NSString stringWithUTF8String: (const char *)filedata->data];
components = [email componentsSeparatedByString: @":"];
email = [components objectAtIndex: 0];
if ([components count] > 1)
cn = [components objectAtIndex: 1];
else
{
filedata = MAPIFindProperty(&(tnef.MapiProperties), PROP_TAG(PT_UNICODE, PR_SENT_REPRESENTING_NAME));
if (filedata != MAPI_UNDEFINED && filedata->size > 1)
cn = [NSString stringWithUTF8String: (const char *)filedata->data];
else
cn = email;
}
[vcalendar appendFormat: @"ORGANIZER;cn=\"%@\":mailto:%@\n", cn, email];
}
// Attendees
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_STRING8, UPR_TO_ATTENDEES_STRING));
if (filedata == MAPI_UNDEFINED)
{
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_UNICODE, UPR_TO_ATTENDEES_STRING));
}
if (filedata == MAPI_UNDEFINED)
{
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_STRING8, UPR_ALL_ATTENDEES_STRING));
}
if (filedata == MAPI_UNDEFINED)
{
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_UNICODE, UPR_ALL_ATTENDEES_STRING));
}
if (filedata != MAPI_UNDEFINED && filedata->size > 1)
{
// Required attendees
value = [NSString stringWithUTF8String: (const char *)filedata->data];
list = [[value componentsSeparatedByString: @";"] objectEnumerator];
while ((attendee = [list nextObject]))
{
attendee = [attendee stringByTrimmingSpaces];
[vcalendar appendFormat: @"ATTENDEE;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE;CN=\"%@\":MAILTO:%@\n",
attendee, attendee];
}
// Optional attendees
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_STRING8, UPR_CC_ATTENDEES_STRING));
if (filedata == MAPI_UNDEFINED)
{
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_UNICODE, UPR_CC_ATTENDEES_STRING));
}
if (filedata != MAPI_UNDEFINED && filedata->size > 1)
{
value = [NSString stringWithUTF8String: (const char *)filedata->data];
if ([value length])
{
list = [[value componentsSeparatedByString: @";"] objectEnumerator];
while ((attendee = [list nextObject]))
{
attendee = [attendee stringByTrimmingSpaces];
[vcalendar appendFormat: @"ATTENDEE;PARTSTAT=NEEDS-ACTION;ROLE=OPT-PARTICIPANT;RSVP=TRUE;CN=\"%@\":MAILTO:%@\n",
attendee, attendee];
}
}
}
}
}
else
{
// Meeting response
filedata = MAPIFindProperty(&(tnef.MapiProperties), PROP_TAG(PT_UNICODE, PR_SENT_REPRESENTING_EMAIL_ADDRESS));
if (filedata == MAPI_UNDEFINED)
{
filedata = MAPIFindProperty(&(tnef.MapiProperties), PROP_TAG(PT_UNICODE, PR_SENDER_SMTP_ADDRESS));
}
if (filedata != MAPI_UNDEFINED && filedata->size > 1)
{
NSString *email, *cn, *partstat;
email = [NSString stringWithUTF8String: (const char *)filedata->data];
filedata = MAPIFindProperty(&(tnef.MapiProperties), PROP_TAG(PT_UNICODE, PR_SENT_REPRESENTING_NAME));
if (filedata == MAPI_UNDEFINED)
{
filedata = MAPIFindProperty(&(tnef.MapiProperties), PROP_TAG(PT_UNICODE, PR_SENDER_NAME));
}
if (filedata != MAPI_UNDEFINED && filedata->size > 1)
cn = [NSString stringWithUTF8String: (const char *)filedata->data];
else
cn = email;
switch ([messageClass characterAtIndex: [messageClass length] - 1])
{
case 'N':
partstat = @"DECLINED";
break;
case 'A':
partstat = @"TENTATIVE";
break;
default:
partstat = @"ACCEPTED";
}
[vcalendar appendFormat: @"ATTENDEE;PARTSTAT=%@;CN=\"%@\":MAILTO:%@\n", partstat, cn, email];
}
}
// Summary
filedata = MAPIFindProperty(&(tnef.MapiProperties), PROP_TAG(PT_STRING8, PR_CONVERSATION_TOPIC));
if (filedata == MAPI_UNDEFINED)
{
filedata = MAPIFindProperty(&(tnef.MapiProperties), PROP_TAG(PT_UNICODE, PR_CONVERSATION_TOPIC));
}
if (filedata != MAPI_UNDEFINED && filedata->size > 1)
{
[vcalendar appendFormat: @"SUMMARY:%@\n", [NSString stringWithUTF8String: (const char *)filedata->data]];
}
// Description
filedata = MAPIFindProperty(&(tnef.MapiProperties), PROP_TAG(PT_STRING8, 0x3fd9));
if (filedata == MAPI_UNDEFINED)
{
filedata = MAPIFindProperty(&(tnef.MapiProperties), PROP_TAG(PT_UNICODE, 0x3fd9));
}
if (filedata != MAPI_UNDEFINED && filedata->size > 1)
{
[vcalendar appendFormat: @"DESCRIPTION:%@\n", [NSString stringWithUTF8String: (const char *)filedata->data]];
}
// Location
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_STRING8, 0x0002));
if (filedata == MAPI_UNDEFINED)
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_STRING8, 0x8208));
if (filedata == MAPI_UNDEFINED)
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_UNICODE, 0x0002));
if (filedata == MAPI_UNDEFINED)
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_UNICODE, 0x8208));
if (filedata != MAPI_UNDEFINED && filedata->size > 1)
{
[vcalendar appendFormat: @"LOCATION: %@\n", [NSString stringWithUTF8String: (const char *)filedata->data]];
}
// Date start
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_SYSTIME, 0x820d));
if (filedata == MAPI_UNDEFINED)
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_SYSTIME, 0x8516));
if (filedata != MAPI_UNDEFINED && filedata->size > 1)
{
MAPISysTimetoDTR(filedata->data, &datetime);
[vcalendar appendFormat: @"DTSTART:%04i%02i%02iT%02i%02i%02iZ\n",
datetime.wYear, datetime.wMonth, datetime.wDay, datetime.wHour, datetime.wMinute, datetime.wSecond];
}
// Date end
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_SYSTIME, 0x820e));
if (filedata == MAPI_UNDEFINED)
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_SYSTIME, 0x8517));
if (filedata != MAPI_UNDEFINED && filedata->size > 1)
{
MAPISysTimetoDTR(filedata->data, &datetime);
[vcalendar appendFormat: @"DTEND:%04i%02i%02iT%02i%02i%02iZ\n",
datetime.wYear, datetime.wMonth, datetime.wDay, datetime.wHour, datetime.wMinute, datetime.wSecond];
}
// Date stamp
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_SYSTIME, 0x8202));
if (filedata == MAPI_UNDEFINED)
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_SYSTIME, 0x001a));
if (filedata != MAPI_UNDEFINED && filedata->size > 1)
{
MAPISysTimetoDTR(filedata->data, &datetime);
[vcalendar appendFormat: @"DTSTAMP:%04i%02i%02iT%02i%02i%02iZ\n",
datetime.wYear, datetime.wMonth, datetime.wDay, datetime.wHour, datetime.wMinute, datetime.wSecond];
}
// Classification
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_BOOLEAN, 0x8506));
if (filedata != MAPI_UNDEFINED)
{
classification = (DDWORD *)filedata->data;
if (*classification == 1)
[vcalendar appendString: @"CLASS:PRIVATE\n"];
else
[vcalendar appendString: @"CLASS:PUBLIC\n"];
}
// Repeating rule
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_BINARY, 0x8216));
if (filedata != MAPI_UNDEFINED && filedata->size >= 0x1F)
{
NSMutableString *rrule = [NSMutableString string];
unsigned char *recurData = filedata->data;
// [vcalendar appendString: @"RRULE:FREQ="];
if (recurData[0x04] == 0x0A)
{
[rrule appendString: @"DAILY"];
if (recurData[0x16] == 0x23 || recurData[0x16] == 0x22 || recurData[0x16] == 0x21)
{
filedata = MAPIFindUserProp(&(tnef.MapiProperties), PROP_TAG(PT_I2, 0x0011));
if (filedata != MAPI_UNDEFINED)
[rrule appendFormat: @";INTERVAL=%d", *(filedata->data)];
if (recurData[0x16] == 0x22 || recurData[0x16] == 0x21)
[rrule appendFormat: @";COUNT=%d", GetRruleCount(recurData[0x1B], recurData[0x1A])];
}
else if (recurData[0x16] == 0x3E)
{
[rrule appendString: @";BYDAY=MO,TU,WE,TH,FR"];
if (recurData[0x1A] == 0x22 || recurData[0x1A] == 0x21)
[rrule appendFormat: @";COUNT=%d", GetRruleCount(recurData[0x1F], recurData[0x1E])];
}
}
else if (recurData[0x04] == 0x0B)
{
[rrule appendFormat: @"WEEKLY;INTERVAL=%d;BYDAY=%s", recurData[0x0E], GetRruleDayname(recurData[0x16])];
if (recurData[0x1A] == 0x22 || recurData[0x1A] == 0x21)
[rrule appendFormat: @";COUNT=%d", GetRruleCount(recurData[0x1F], recurData[0x1E])];
}
else if (recurData[0x04] == 0x0C)
{
[rrule appendString: @"MONTHLY"];
if (recurData[0x06] == 0x02)
{
[rrule appendFormat: @";INTERVAL=%d;BYMONTHDAY=%d", recurData[0x0E], recurData[0x16]];
if (recurData[0x1A] == 0x22 || recurData[0x1A] == 0x21)
[rrule appendFormat: @";COUNT=%d", GetRruleCount(recurData[0x1F], recurData[0x1E])];
}
else if (recurData[0x06] == 0x03)
{
[rrule appendFormat: @";BYDAY=%s;BYSETPOS=%d;INTERVAL=%d",
GetRruleDayname(recurData[0x16]),
recurData[0x1A] == 0x05 ? -1 : recurData[0x1A],
recurData[0x0E]];
if (recurData[0x1E] == 0x22 || recurData[0x1E] == 0x21)
[rrule appendFormat: @";COUNT=%d", GetRruleCount(recurData[0x23], recurData[0x22])];
}
}
else if (recurData[0x04] == 0x0D)
{
[rrule appendFormat: @"YEARLY;BYMONTH=%d", GetRruleMonthNum(recurData[0x0A], recurData[0x0B])];
if (recurData[0x06] == 0x02)
[rrule appendFormat: @";BYMONTHDAY=%d", recurData[0x16]];
else if (recurData[0x06] == 0x03)
[rrule appendFormat: @";BYDAY=%s;BYSETPOS=%d",
GetRruleDayname(recurData[0x16]),
recurData[0x1A] == 0x05 ? -1 : recurData[0x1A]];
if (recurData[0x1E] == 0x22 || recurData[0x1E] == 0x21)
[rrule appendFormat: @";COUNT=%d", GetRruleCount(recurData[0x23], recurData[0x22])];
}
if ([rrule length])
[vcalendar appendFormat: @"RRULE:FREQ=%@\n", rrule];
}
[vcalendar appendString: @"END:VEVENT\n"];
[vcalendar appendString: @"END:VCALENDAR"];
if (debugOn)
NSLog(@"TNEF reconstructed vCalendar:\n%@", vcalendar);
[self bodyPartForData: [vcalendar dataUsingEncoding: NSUTF8StringEncoding]
withType: @"text"
andSubtype: @"calendar"];
}
// Other classes to handle:
//
// IPM.Contact
// IPM.Task
// IPM.Microsoft Schedule.MtgCncl
// IPM.Appointment
//
Attachment *p;
BOOL isObject, isRealAttachment;
p = tnef.starting_attach.next;
while (p != NULL)
{
if (p->FileData.size > 0)
{
isObject = YES;
// See if the contents are stored as "attached data" inside the MAPI blocks.
filedata = MAPIFindProperty(&(p->MAPI), PROP_TAG(PT_OBJECT, PR_ATTACH_DATA_OBJ));
if (filedata == MAPI_UNDEFINED)
{
// Nope, standard TNEF stuff.
filedata = &(p->FileData);
isObject = NO;
}
// See if this is an embedded TNEF stream.
isRealAttachment = YES;
TNEFStruct emb_tnef;
DWORD object_signature;
if (isObject)
{
// This is an "embedded object", so skip the 16-byte identifier first.
memcpy(&object_signature, filedata->data + 16, sizeof(DWORD));
if (TNEFCheckForSignature(object_signature) == 0) {
TNEFInitialize(&emb_tnef);
emb_tnef.Debug = tnef.Debug;
if (TNEFParseMemory(filedata->data + 16, filedata->size - 16, &emb_tnef) != -1)
{
isRealAttachment = NO;
}
TNEFFree(&emb_tnef);
}
}
else
{
memcpy(&object_signature, filedata->data, sizeof(DWORD));
if (TNEFCheckForSignature(object_signature) == 0) {
TNEFInitialize(&emb_tnef);
emb_tnef.Debug = tnef.Debug;
if (TNEFParseMemory(filedata->data, filedata->size, &emb_tnef) != -1)
{
isRealAttachment = NO;
}
TNEFFree(&emb_tnef);
}
}
if (isRealAttachment)
{
// Ok, it's not an embedded stream, so now we process it.
attachmentName = MAPIFindProperty(&(p->MAPI), PROP_TAG(PT_STRING8, PR_ATTACH_LONG_FILENAME));
if (attachmentName == MAPI_UNDEFINED)
{
attachmentName = MAPIFindProperty(&(p->MAPI), PROP_TAG(PT_STRING8, PR_DISPLAY_NAME));
if (attachmentName == MAPI_UNDEFINED)
{
attachmentName = MAPIFindProperty(&(p->MAPI), PROP_TAG(PT_STRING8, PR_ATTACH_TRANSPORT_NAME));
if (attachmentName == MAPI_UNDEFINED)
{
attachmentName = &(p->Title);
}
}
}
// MAPIPrint(&p->MAPI);
variableLength *prop;
if (attachmentName->size > 1)
{
partName = [NSString stringWithUTF8String: (const char *)attachmentName->data];
type = @"application";
subtype = @"octet-stream";
prop = MAPIFindProperty(&(p->MAPI), PROP_TAG(PT_UNICODE, PR_ATTACH_MIME_TAG));
if (prop != MAPI_UNDEFINED)
{
NSString *mime = [NSString stringWithUTF8String: (const char *)prop->data];
NSArray *pair = [mime componentsSeparatedByString: @"/"];
if ([pair count] == 2)
{
type = [pair objectAtIndex: 0];
subtype = [pair objectAtIndex: 1];
}
else
{
[self warnWithFormat: @"Unexpected MIME type %@", mime];
}
}
else
{
prop = MAPIFindProperty(&(p->MAPI), PROP_TAG(PT_STRING8, PR_ATTACH_EXTENSION));
if (prop != MAPI_UNDEFINED)
{
NSString *ext = [NSString stringWithUTF8String: (const char *)prop->data];
if ([ext caseInsensitiveCompare: @".txt"] == NSOrderedSame)
{
type = @"text";
subtype = @"plain";
}
else
{
[self warnWithFormat: @"Unidentified extension %@", ext];
}
}
}
NSString *cid = partName;
prop = MAPIFindProperty(&(p->MAPI), PROP_TAG(PT_UNICODE, 0x3712)); // PR_CONTENT_IDENTIFIER?
if (prop != MAPI_UNDEFINED)
{
cid = [NSString stringWithUTF8String: (const char *)prop->data];
}
NSData *attachment;
if (isObject)
attachment = [NSData dataWithBytes: filedata->data + 16 length: filedata->size - 16];
else
attachment = [NSData dataWithBytes: filedata->data length: filedata->size];
[self bodyPartForAttachment: attachment
withName: partName
andType: type
andSubtype: subtype
andContentId: cid];
// count++;
}
} // if isRealAttachment
} // if size>0
p = p->next;
}
}
TNEFFree(&tnef);
}
}
- (void) setPart: (NGMimeBodyPart *) newPart
{
ASSIGN (part, newPart);
if (newPart)
{
[self setFilename: [[newPart bodyInfo] filename]];
[self setPartInfo: [newPart bodyInfo]];
}
else
{
[self setFilename: nil];
[self setPartInfo: nil];
}
}
- (void) setFilename: (NSString *) newFilename
{
ASSIGN (filename, newFilename);
}
- (void) setPartInfo: (id) newPartInfo
{
ASSIGN (partInfo, newPartInfo);
}
- (NSString *) contentDispositionForAttachmentWithName: (NSString *) _name
andSize: (NSNumber *) _size
andContentType: (NSString *) _type
{
NSString *cdtype, *cd;
if (([_type caseInsensitiveCompare: @"image"] == NSOrderedSame) ||
([_type caseInsensitiveCompare: @"message"] == NSOrderedSame))
cdtype = @"inline";
else
cdtype = @"attachment";
cd = [NSString stringWithFormat: @"%@; filename=\"%@\"; size=%i;",
cdtype, [_name stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""], [_size intValue]];
return cd;
}
- (NGMimeBodyPart *) bodyPartForData: (NSData *) _data
withType: (NSString *) _type
andSubtype: (NSString *) _subtype
{
NGMutableHashMap *map;
NGMimeBodyPart *bodyPart;
NSData *content;
id body;
if (_data == nil) return nil;
/* check attachment */
/* prepare header of body part */
map = [[[NGMutableHashMap alloc] initWithCapacity: 4] autorelease];
// Content-Type
[map setObject: [NSString stringWithFormat: @"%@/%@", _type, _subtype]
forKey: @"content-type"];
/* prepare body content */
content = [NSData dataWithBytes: [_data bytes] length: [_data length]];
[map setObject: [NSNumber numberWithInt: [content length]]
forKey: @"content-length"];
/* Note: the -init method will create a temporary file! */
body = [[[NGMimeFileData alloc] initWithBytes: [content bytes]
length: [content length]] autorelease];
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader: map] autorelease];
[bodyPart setBody: body];
[bodyParts addBodyPart: bodyPart];
return bodyPart;
}
- (NGMimeBodyPart *) bodyPartForAttachment: (NSData *) _data
withName: (NSString *) _name
andType: (NSString *) _type
andSubtype: (NSString *) _subtype
andContentId: (NSString *) _cid
{
NGMutableHashMap *map;
NGMimeBodyPart *bodyPart;
NSData *content;
NSString *s;
id body;
if (_name == nil) return nil;
/* prepare header of body part */
map = [[[NGMutableHashMap alloc] initWithCapacity: 4] autorelease];
// Content-Type
[map setObject: [NSString stringWithFormat: @"%@/%@", _type, _subtype]
forKey: @"content-type"];
// Content-Id
[map setObject: _cid
forKey: @"content-id"];
// Content-Disposition
s = [self contentDispositionForAttachmentWithName: _name
andSize: [NSNumber numberWithLong: [_data length]]
andContentType: _type];
NGMimeContentDispositionHeaderField *o;
o = [[NGMimeContentDispositionHeaderField alloc] initWithString: s];
[map setObject: o forKey: @"content-disposition"];
[o release];
/* prepare body content */
content = [NSData dataWithBytes: [_data bytes] length: [_data length]];
[map setObject: [NSNumber numberWithInt: [content length]]
forKey: @"content-length"];
/* Note: the -init method will create a temporary file! */
body = [[NGMimeFileData alloc] initWithBytes: [content bytes]
length: [content length]];
bodyPart = [[[NGMimeBodyPart alloc] initWithHeader: map] autorelease];
[bodyPart setBody: body];
[body release];
[bodyParts addBodyPart: bodyPart];
return bodyPart;
}
@end /* SOGoTNEFMailBodyPart */

View File

@ -59,6 +59,9 @@
SOGoMessageMailBodyPart = {
superclass = "SOGoMailBodyPart";
};
SOGoTNEFMailBodyPart = {
superclass = "SOGoMailBodyPart";
};
SOGoCalendarMailBodyPart = {
superclass = "SOGoMailBodyPart";
};

View File

@ -21,6 +21,7 @@ SOGo_HEADER_FILES = \
EOBitmaskQualifier.h \
EOQualifier+SOGoCacheObject.h \
GCSSpecialQueries+SOGoCacheObject.h \
RTFHandler.h \
SOGoCache.h \
SOGoCacheGCSFolder.h \
SOGoCacheGCSObject.h \
@ -100,6 +101,7 @@ SOGo_OBJC_FILES = \
EOBitmaskQualifier.m \
EOQualifier+SOGoCacheObject.m \
GCSSpecialQueries+SOGoCacheObject.m \
RTFHandler.m \
SOGoCache.m \
SOGoCacheGCSFolder.m \
SOGoCacheGCSObject.m \

View File

@ -0,0 +1,141 @@
/*
Copyright (C) 2005-2012 Inverse inc.
This file is part of SOGo.
SOGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
SOGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#include <Foundation/NSArray.h>
#include <Foundation/NSData.h>
#include <Foundation/NSMapTable.h>
#include <Foundation/NSObject.h>
#include <Foundation/NSString.h>
//
//
//
@class RTFFontTable;
@interface RTFHandler : NSObject
{
NSMapTable *_charsets;
NSMutableData *_html;
NSData *_data;
const char *_bytes;
int _current_pos;
int _len;
}
- (id) initWithData: (NSData *) theData;
- (NSMutableData *) parse;
- (RTFFontTable *) parseFontTable;
- (void) mangleInternalStateWithBytesPtr: (const char*) newBytes
andCurrentPos: (int) newCurrentPos;
@end
//
//
//
@interface RTFStack: NSObject
{
NSMutableArray *a;
}
- (void) push: (id) theObject;
- (id) pop;
@end
//
//
//
@interface RTFFormattingOptions : NSObject
{
@public
BOOL bold;
BOOL italic;
BOOL underline;
BOOL strikethrough;
int font_index;
int color_index;
int start_pos;
const unsigned short *charset;
}
@end
//
//
//
@interface RTFFontInfo : NSObject
{
@public
NSString *family;
unsigned char charset;
NSString *name;
unsigned int pitch;
unsigned int index;
}
- (NSString *) description;
@end
//
// \fX - font, index in font table
//
@interface RTFFontTable : NSObject
{
@public
NSMapTable *fontInfos;
}
- (void) addFontInfo: (RTFFontInfo *) theFontInfo
atIndex: (unsigned int ) theIndex;
- (RTFFontInfo *) fontInfoAtIndex: (unsigned int ) theIndex;
- (NSString *) description;
@end
//
//
//
@interface RTFColorDef : NSObject
{
@public
unsigned char red;
unsigned char green;
unsigned char blue;
}
@end
//
// {\colortbl\red0\green0\blue0;\red128\green0\blue0;\red255\green0\blue0;}
//
// \cfX - color/foreground - index
// \cbX - color/background - index
//
//
@interface RTFColorTable : NSObject
{
@public
NSMutableArray *colorDefs;
}
- (void) addColorDef: (RTFColorDef *) theColorDef;
@end

File diff suppressed because it is too large Load Diff

View File

@ -26,6 +26,7 @@
SOGoMemcachedHost = "localhost";
SOGoUIxDebugEnabled = NO;
SOGoTnefDecoderDebugEnabled = NO;
SOGoLDAPContactInfoAttribute = "description";

View File

@ -1,6 +1,6 @@
/* SOGoSystemDefaults.h - this file is part of SOGo
*
* Copyright (C) 2009-2016 Inverse inc.
* Copyright (C) 2009-2021 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -65,6 +65,7 @@
- (BOOL) uixDebugEnabled;
- (BOOL) easDebugEnabled;
- (BOOL) tnefDecoderDebugEnabled;
- (BOOL) xsrfValidationEnabled;
- (NSString *) pageTitle;

View File

@ -1,6 +1,6 @@
/* SOGoSystemDefaults.m - this file is part of SOGo
*
* Copyright (C) 2009-2015 Inverse inc.
* Copyright (C) 2009-2021 Inverse inc.
* Copyright (C) 2012 Jeroen Dekkers <jeroen@dekkers.ch>
*
* This file is free software; you can redistribute it and/or modify
@ -434,6 +434,11 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict,
return [self boolForKey: @"SOGoEASDebugEnabled"];
}
- (BOOL) tnefDecoderDebugEnabled
{
return [self boolForKey: @"SOGoTnefDecoderDebugEnabled"];
}
- (BOOL) xsrfValidationEnabled
{
id o;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,21 @@
{\rtf1\ansi\deff3\adeflang1025
{\fonttbl{\f0\froman\fprq2\fcharset0 Times New Roman;}{\f1\froman\fprq2\fcharset2 Symbol;}{\f2\fswiss\fprq2\fcharset0 Arial;}{\f3\froman\fprq2\fcharset0 Times New Roman;}{\f4\fs
wiss\fprq2\fcharset0 Arial;}{\f5\fnil\fprq2\fcharset1 Ubuntu;}{\f6\fnil\fprq2\fcharset0 DejaVu Sans;}{\f7\fnil\fprq2\fcharset0 Lohit Hindi;}{\f8\fnil\fprq0\fcharset1 Lohit Hindi
;}}
{\colortbl;\red0\green0\blue0;\red128\green128\blue128;}
{\stylesheet{\s0\snext0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\dbch\af6\langfe2052\dbch\af7\afs24\alang1081\loch\f3\fs24\lang1033 Normal;}
{\s15\sbasedon0\snext16\sb240\sa120\keepn\dbch\af6\dbch\af7\afs28\loch\f4\fs28 Heading;}
{\s16\sbasedon0\snext16\sb0\sa120 Text Body;}
{\s17\sbasedon16\snext17\sb0\sa120\dbch\af8 List;}
{\s18\sbasedon0\snext18\sb120\sa120\noline\i\dbch\af8\afs24\ai\fs24 Caption;}
{\s19\sbasedon0\snext19\noline\dbch\af8 Index;}
}{\info{\creatim\yr2014\mo8\dy4\hr16\min16}{\revtim\yr0\mo0\dy0\hr0\min0}{\printim\yr0\mo0\dy0\hr0\min0}{\comment LibreOffice}{\vern67175170}}\deftab709
\viewscale100
{\*\pgdsctbl
{\pgdsc0\pgdscuse451\pgwsxn11906\pghsxn16838\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\pgdscnxt0 Default Style;}}
\formshade\paperh16838\paperw11906\margl1134\margr1134\margt1134\margb1134\sectd\sbknone\sectunlocked1\pgndec\pgwsxn11906\pghsxn16838\marglsxn1134\margrsxn1134\margtsxn1134\marg
bsxn1134\ftnbj\ftnstart1\ftnrstcont\ftnnar\aenddoc\aftnrstcont\aftnstart1\aftnnrlc
\pgndec\pard\plain \s0\nowidctlpar{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\cf0\kerning1\dbch\af6\langfe2052\dbch\af7\afs24\alang1081\loch\f3\fs24\lang1033{\rtlch \ltrch\loch\l
och\f5000
foobar}
\par }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -34,6 +34,8 @@ $(TEST_TOOL)_OBJC_FILES += \
TestNGMailAddressParser.m \
TestNGInternetSocketAddress.m \
\
TestRTFHandler.m \
\
# I don't know how to link against -l:SOGoBackend \
undefined reference to `__objc_class_name_SOGoMailFolder'

View File

@ -0,0 +1,306 @@
/* TestRTFHandler.m
*
* Copyright (C) 2014 Zentyal
*
* Author: Jesús García Sáez <jgarcia@zentyal.org>
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/* This file is encoded in utf-8. */
#import "SOGoTest.h"
#import <Foundation/NSFileManager.h>
#import <Foundation/NSException.h>
#import <SOGo/RTFHandler.h>
@interface TestRTFHandler : SOGoTest
@end
@implementation TestRTFHandler
- (NSString *) rtf2html: (NSData *) rtf
{
NSString *html;
if (!rtf)
return nil;
RTFHandler *handler = [[RTFHandler alloc] initWithData: rtf];
NSMutableData *data = [handler parse];
if (data == nil)
{
NSString *error = [NSString stringWithFormat: @"Couldn't parse RTF data:\n %s",
(char *)[rtf bytes]];
testWithMessage(NO, error);
}
html = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
if (html == nil)
{
NSString *error = [NSString stringWithFormat: @"Couldn't convert parsed data to UTF8 string"];
testWithMessage(NO, error);
}
return html;
}
- (NSData *) dataWithContentsOfFixture: (NSString*) name
{
NSString *file_path = [NSString stringWithFormat: @"Fixtures/%@", name];
if(![[NSFileManager defaultManager] fileExistsAtPath: file_path]) {
NSString *error = [NSString stringWithFormat: @"File %@ doesn't exist", file_path];
testWithMessage(NO, error);
}
return [NSData dataWithContentsOfFile: file_path];
}
- (NSData *) dataWithContentsOfZentyalCrash: (unsigned int) number
{
NSString *fixture = [NSString stringWithFormat: @"zentyal_crash_%u.rtf", number];
return [self dataWithContentsOfFixture: fixture];
}
- (void) checkDoesNotCrash: (unsigned int) number
{
// FIXME fork
[self rtf2html: [self dataWithContentsOfZentyalCrash: number]];
}
- (void) checkHTMLConversionOfRTFFile: (NSString*) file
againstExpectedHTML: (NSString*) expected
{
NSData *in = nil;
NSString *out = nil, *error = nil;
in = [self dataWithContentsOfFixture: file];
out = [self rtf2html: in];
error = [NSString stringWithFormat:
@"Html from rtf result is not what we expected.\n>> Actual:\n%@\n>> Expected:\n%@\n", out, expected];
testWithMessage([out isEqualToString: expected], error);
}
- (void) checkFonTableParsingOfRTFFile: (NSString*) file
againstExpectedTable: (NSString*) expected
{
NSData *in = nil;
char *in_bytes;
char *table_pointer;
int newCurrentPos;
RTFHandler *handler;
RTFFontTable *out_table;
NSString *out_description, *error = nil;
in = [self dataWithContentsOfFixture: file];
in_bytes = (char *) [in bytes];
table_pointer = strstr(in_bytes, "{\\fonttbl");
if (table_pointer == NULL)
{
[NSException raise: @"NSInvalidArgumentException"
format: @"No font table in RTF file"];
}
newCurrentPos = table_pointer - in_bytes;
handler = [[RTFHandler alloc] initWithData: in];
[handler mangleInternalStateWithBytesPtr: table_pointer
andCurrentPos: newCurrentPos];
out_table = [handler parseFontTable];
out_description = [out_table description];
if ([out_description isEqualToString: expected])
{
testWithMessage(YES, @"no error");
}
else
{
error = [NSString stringWithFormat:
@"Font table is not what we expected.\n>> Actual:\n%@-----\n>> Expected:\n%@-----\n", out_description, expected];
testWithMessage(NO, error);
}
}
- (void) test_zentyal_crash_2058
{
[self checkDoesNotCrash: 2058];
// Output is not correct... but the original issue was segfault
}
- (void) test_zentyal_crash_2089
{
NSData *in = nil;
NSString *out = nil, *error = nil, *expected = nil;
in = [self dataWithContentsOfZentyalCrash: 2089];
expected =@"<html><meta charset='utf-8'><body><font face=\"Calibri\"><font color=\"#000000\">Lorem Ipsum</font><font color=\"#000000\"><br></font></body></html>";
out = [self rtf2html: in];
error = [NSString stringWithFormat:
@"Html from rtf result `%@` is not what we expected", out];
testWithMessage([out isEqualToString: expected], error);
}
- (void) test_zentyal_crash_6330
{
[self checkDoesNotCrash: 6330];
}
- (void) test_zentyal_crash_8346
{
[self checkDoesNotCrash: 8346];
}
- (void) test_mini_russian_font_table
{
NSString *file =@"mini_russian.rtf";
NSMutableString *expected = [NSMutableString stringWithFormat: @"Number of fonts: 84\n"];
[expected appendString: @"0 name=Times New Roman family=roman charset=0 pitch=2\n"];
[expected appendString: @"31500 name=Times New Roman family=roman charset=0 pitch=2\n"];
[expected appendString: @"31501 name=Times New Roman family=roman charset=0 pitch=2\n"];
[expected appendString: @"31502 name=Cambria family=roman charset=0 pitch=2\n"];
[expected appendString: @"37 name=Calibri family=swiss charset=0 pitch=2\n"];
[expected appendString: @"31503 name=Times New Roman family=roman charset=0 pitch=2\n"];
[expected appendString: @"39 name=Times New Roman CE family=roman charset=238 pitch=2\n"];
[expected appendString: @"31504 name=Times New Roman family=roman charset=0 pitch=2\n"];
[expected appendString: @"40 name=Times New Roman Cyr family=roman charset=204 pitch=2\n"];
[expected appendString: @"31505 name=Times New Roman family=roman charset=0 pitch=2\n"];
[expected appendString: @"31506 name=Calibri family=swiss charset=0 pitch=2\n"];
[expected appendString: @"42 name=Times New Roman Greek family=roman charset=161 pitch=2\n"];
[expected appendString: @"31507 name=Times New Roman family=roman charset=0 pitch=2\n"];
[expected appendString: @"31508 name=Times New Roman CE family=roman charset=238 pitch=2\n"];
[expected appendString: @"43 name=Times New Roman Tur family=roman charset=162 pitch=2\n"];
[expected appendString: @"31509 name=Times New Roman Cyr family=roman charset=204 pitch=2\n"];
[expected appendString: @"44 name=Times New Roman (Hebrew) family=roman charset=177 pitch=2\n"];
[expected appendString: @"45 name=Times New Roman (Arabic) family=roman charset=178 pitch=2\n"];
[expected appendString: @"31511 name=Times New Roman Greek family=roman charset=161 pitch=2\n"];
[expected appendString: @"46 name=Times New Roman Baltic family=roman charset=186 pitch=2\n"];
[expected appendString: @"31512 name=Times New Roman Tur family=roman charset=162 pitch=2\n"];
[expected appendString: @"47 name=Times New Roman (Vietnamese) family=roman charset=163 pitch=2\n"];
[expected appendString: @"31513 name=Times New Roman (Hebrew) family=roman charset=177 pitch=2\n"];
[expected appendString: @"31514 name=Times New Roman (Arabic) family=roman charset=178 pitch=2\n"];
[expected appendString: @"31515 name=Times New Roman Baltic family=roman charset=186 pitch=2\n"];
[expected appendString: @"31516 name=Times New Roman (Vietnamese) family=roman charset=163 pitch=2\n"];
[expected appendString: @"31518 name=Times New Roman CE family=roman charset=238 pitch=2\n"];
[expected appendString: @"31519 name=Times New Roman Cyr family=roman charset=204 pitch=2\n"];
[expected appendString: @"31521 name=Times New Roman Greek family=roman charset=161 pitch=2\n"];
[expected appendString: @"31522 name=Times New Roman Tur family=roman charset=162 pitch=2\n"];
[expected appendString: @"31523 name=Times New Roman (Hebrew) family=roman charset=177 pitch=2\n"];
[expected appendString: @"31524 name=Times New Roman (Arabic) family=roman charset=178 pitch=2\n"];
[expected appendString: @"31525 name=Times New Roman Baltic family=roman charset=186 pitch=2\n"];
[expected appendString: @"31526 name=Times New Roman (Vietnamese) family=roman charset=163 pitch=2\n"];
[expected appendString: @"31528 name=Cambria CE family=roman charset=238 pitch=2\n"];
[expected appendString: @"31529 name=Cambria Cyr family=roman charset=204 pitch=2\n"];
[expected appendString: @"31531 name=Cambria Greek family=roman charset=161 pitch=2\n"];
[expected appendString: @"31532 name=Cambria Tur family=roman charset=162 pitch=2\n"];
[expected appendString: @"31535 name=Cambria Baltic family=roman charset=186 pitch=2\n"];
[expected appendString: @"31536 name=Cambria (Vietnamese) family=roman charset=163 pitch=2\n"];
[expected appendString: @"31538 name=Times New Roman CE family=roman charset=238 pitch=2\n"];
[expected appendString: @"31539 name=Times New Roman Cyr family=roman charset=204 pitch=2\n"];
[expected appendString: @"31541 name=Times New Roman Greek family=roman charset=161 pitch=2\n"];
[expected appendString: @"31542 name=Times New Roman Tur family=roman charset=162 pitch=2\n"];
[expected appendString: @"31543 name=Times New Roman (Hebrew) family=roman charset=177 pitch=2\n"];
[expected appendString: @"31544 name=Times New Roman (Arabic) family=roman charset=178 pitch=2\n"];
[expected appendString: @"31545 name=Times New Roman Baltic family=roman charset=186 pitch=2\n"];
[expected appendString: @"31546 name=Times New Roman (Vietnamese) family=roman charset=163 pitch=2\n"];
[expected appendString: @"31548 name=Times New Roman CE family=roman charset=238 pitch=2\n"];
[expected appendString: @"31549 name=Times New Roman Cyr family=roman charset=204 pitch=2\n"];
[expected appendString: @"31551 name=Times New Roman Greek family=roman charset=161 pitch=2\n"];
[expected appendString: @"31552 name=Times New Roman Tur family=roman charset=162 pitch=2\n"];
[expected appendString: @"31553 name=Times New Roman (Hebrew) family=roman charset=177 pitch=2\n"];
[expected appendString: @"31554 name=Times New Roman (Arabic) family=roman charset=178 pitch=2\n"];
[expected appendString: @"31555 name=Times New Roman Baltic family=roman charset=186 pitch=2\n"];
[expected appendString: @"31556 name=Times New Roman (Vietnamese) family=roman charset=163 pitch=2\n"];
[expected appendString: @"31558 name=Times New Roman CE family=roman charset=238 pitch=2\n"];
[expected appendString: @"31559 name=Times New Roman Cyr family=roman charset=204 pitch=2\n"];
[expected appendString: @"31561 name=Times New Roman Greek family=roman charset=161 pitch=2\n"];
[expected appendString: @"31562 name=Times New Roman Tur family=roman charset=162 pitch=2\n"];
[expected appendString: @"31563 name=Times New Roman (Hebrew) family=roman charset=177 pitch=2\n"];
[expected appendString: @"31564 name=Times New Roman (Arabic) family=roman charset=178 pitch=2\n"];
[expected appendString: @"31565 name=Times New Roman Baltic family=roman charset=186 pitch=2\n"];
[expected appendString: @"31566 name=Times New Roman (Vietnamese) family=roman charset=163 pitch=2\n"];
[expected appendString: @"31568 name=Calibri CE family=swiss charset=238 pitch=2\n"];
[expected appendString: @"31569 name=Calibri Cyr family=swiss charset=204 pitch=2\n"];
[expected appendString: @"31571 name=Calibri Greek family=swiss charset=161 pitch=2\n"];
[expected appendString: @"31572 name=Calibri Tur family=swiss charset=162 pitch=2\n"];
[expected appendString: @"31575 name=Calibri Baltic family=swiss charset=186 pitch=2\n"];
[expected appendString: @"31576 name=Calibri (Vietnamese) family=swiss charset=163 pitch=2\n"];
[expected appendString: @"31578 name=Times New Roman CE family=roman charset=238 pitch=2\n"];
[expected appendString: @"31579 name=Times New Roman Cyr family=roman charset=204 pitch=2\n"];
[expected appendString: @"31581 name=Times New Roman Greek family=roman charset=161 pitch=2\n"];
[expected appendString: @"31582 name=Times New Roman Tur family=roman charset=162 pitch=2\n"];
[expected appendString: @"31583 name=Times New Roman (Hebrew) family=roman charset=177 pitch=2\n"];
[expected appendString: @"31584 name=Times New Roman (Arabic) family=roman charset=178 pitch=2\n"];
[expected appendString: @"409 name=Calibri CE family=swiss charset=238 pitch=2\n"];
[expected appendString: @"31585 name=Times New Roman Baltic family=roman charset=186 pitch=2\n"];
[expected appendString: @"410 name=Calibri Cyr family=swiss charset=204 pitch=2\n"];
[expected appendString: @"31586 name=Times New Roman (Vietnamese) family=roman charset=163 pitch=2\n"];
[expected appendString: @"412 name=Calibri Greek family=swiss charset=161 pitch=2\n"];
[expected appendString: @"413 name=Calibri Tur family=swiss charset=162 pitch=2\n"];
[expected appendString: @"416 name=Calibri Baltic family=swiss charset=186 pitch=2\n"];
[expected appendString: @"417 name=Calibri (Vietnamese) family=swiss charset=163 pitch=2\n"];
[self checkFonTableParsingOfRTFFile: file
againstExpectedTable: expected];
}
- (void) test_mini_russian
{
NSString *file =@"mini_russian.rtf";
NSString *expected=@"<html><meta charset='utf-8'><body><font face=\"Calibri\"><font face=\"Calibri Cyr\"><font color=\"#000000\">XXзык польски, польщизнаXX</font></font></font></body></html>";
[self checkHTMLConversionOfRTFFile: file
againstExpectedHTML: expected];
}
- (void) test_escapes
{
NSString *file =@"escapes.rtf";
NSString *expected=@"<html><meta charset='utf-8'><body><font face=\"Calibri\"><font color=\"#000000\">x341x351x372x355x363x361x</font><font color=\"#000000\">S SS-S\\S</font><font color=\"#000000\">UUřU</font><font color=\"#000000\"><br></font></font></body></html>";
[self checkHTMLConversionOfRTFFile: file
againstExpectedHTML: expected];
}
- (void) test_spanish_accents
{
NSString *file =@"spanish_accents.rtf";
NSString *expected=@"<html><meta charset='utf-8'><body><font face=\"Calibri\"><font color=\"#000000\">xñxáxéxíxóxú</font><font color=\"#000000\"><br></font></font></body></html>";
[self checkHTMLConversionOfRTFFile: file
againstExpectedHTML: expected];
}
- (void) test_cyr_event_ru_editor
{
NSString *file =@"cyr_event_ru_editor.rtf";
NSString *expected=@"<html><meta charset='utf-8'><body><font face=\"Calibri\"><font face=\"Calibri Cyr\"><font color=\"#000000\">йчсмй</font></font><font color=\"#000000\"><br></font></font></body></html>";
[self checkHTMLConversionOfRTFFile: file
againstExpectedHTML: expected];
}
- (void) test_bad_hex_and_cr
{
NSString *file =@"bad_hex_and_cr.rtf";
NSString *expected=@"<html><meta charset='utf-8'><body><font face=\"Calibri\"><font face=\"Calibri Cyr\"><font color=\"#000000\">Good hex:H Bad1Hex: Bad2Hex: Ignored Carriadge Return</font></font></font></body></html>";
[self checkHTMLConversionOfRTFFile: file
againstExpectedHTML: expected];
}
@end

View File

@ -25,6 +25,7 @@ MailPartViewers_OBJC_FILES += \
UIxMailPartAlternativeViewer.m \
UIxMailPartMessageViewer.m \
UIxMailPartICalViewer.m \
UIxMailPartTNEFViewer.m \
\
UIxMailPartICalActions.m

View File

@ -13,6 +13,8 @@ ADDITIONAL_CPPFLAGS += -DHAVE_OPENSSL=1
BUNDLE_LIBS += -lcrypto
endif
SOGo_LIBRARIES_DEPEND_UPON += -lytnef
ADDITIONAL_CPPFLAGS += \
-Wall -DCOMPILE_FOR_GSTEP_MAKE=1 \
-DUIX_MAILER_MAJOR_VERSION="@\"$(MAJOR_VERSION)\"" \

View File

@ -0,0 +1,33 @@
/* UIxMailPartTNEFViewer.h - this file is part of SOGo
*
* Copyright (C) 2021 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef UIXMAILPARTTNEFVIEWER_H
#define UIXMAILPARTTNEFVIEWER_H
#import "UIxMailPartMixedViewer.h"
@interface UIxMailPartTNEFViewer : UIxMailPartMixedViewer
{
}
@end
#endif /* UIXMAILPARTTNEFVIEWER_H */

View File

@ -0,0 +1,129 @@
/*
Copyright (C) 2021 Inverse inc.
This file is part of SOGo.
SOGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
SOGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with SOGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#import <Foundation/NSDictionary.h>
#import <Foundation/NSValue.h>
#import <NGMime/NGMimeBodyPart.h>
#import <NGMime/NGMimeHeaderFields.h>
#import <NGMime/NGMimeMultipartBody.h>
#import <NGMime/NGMimeType.h>
#import <SoObjects/Mailer/NSString+Mail.h>
#import <SoObjects/Mailer/SOGoTNEFMailBodyPart.h>
#import "UIxMailRenderingContext.h"
#import "UIxMailPartTNEFViewer.h"
@implementation UIxMailPartTNEFViewer
- (void) _attachmentIdsFromBodyPart: (id) thePart
partPath: (NSString *) thePartPath
{
if ([thePart isKindOfClass: [NGMimeBodyPart class]])
{
NSString *cid, *filename, *mimeType;
mimeType = [[thePart contentType] stringValue];
cid = [thePart contentId];
filename = [(NGMimeContentDispositionHeaderField *)[thePart headerForKey: @"content-disposition"] filename];
if (!filename)
filename = [mimeType asPreferredFilenameUsingPath: nil];
if (filename)
{
[(id)attachmentIds setObject: [NSString stringWithFormat: @"%@%@%@",
[[self clientObject] baseURLInContext: [self context]],
thePartPath,
filename]
forKey: [NSString stringWithFormat: @"<%@>", cid]];
}
}
else if ([thePart isKindOfClass: [NGMimeMultipartBody class]])
{
int i;
for (i = 0; i < [[thePart parts] count]; i++)
{
[self _attachmentIdsFromBodyPart: [[thePart parts] objectAtIndex: i]
partPath: [NSString stringWithFormat: @"%@%d/", thePartPath, i+1]];
}
}
}
- (id) contentViewerComponent
{
id info;
info = [self childInfo];
return [[[self context] mailRenderingContext] viewerForBodyInfo: info];
}
- (id) renderedPart
{
NSArray *parts;
NSInteger i, max;
NSMutableArray *renderedParts;
SOGoTNEFMailBodyPart *tnefPart;
id viewer, info;
tnefPart = (SOGoTNEFMailBodyPart *)[self clientPart];
parts = [[tnefPart bodyParts] parts];
max = [parts count];
renderedParts = [NSMutableArray arrayWithCapacity: max];
// Populate the list of attachments ids
for (i = 0; i < max; i++)
{
NGMimeBodyPart *part;
part = [parts objectAtIndex: i];
[self _attachmentIdsFromBodyPart: part
partPath: [NSString stringWithFormat: @"%@/%d/", [tnefPart bodyPartIdentifier], i+1]];
}
// Render each part
for (i = 0; i < max; i++)
{
NGMimeBodyPart *part = [parts objectAtIndex: i];
[self setChildIndex: i];
[self setChildInfo: [part bodyInfo]];
info = [self childInfo];
viewer = [[[self context] mailRenderingContext] viewerForBodyInfo: info];
[viewer setBodyInfo: info];
[viewer setPartPath: [self childPartPath]];
[viewer setAttachmentIds: attachmentIds];
[viewer setFlatContent: [part body]];
[renderedParts addObject: [viewer renderedPart]];
}
return [NSDictionary dictionaryWithObjectsAndKeys:
[self className], @"type",
renderedParts, @"content",
nil];
}
@end /* UIxMailPartTNEFViewer */

View File

@ -69,6 +69,7 @@
- (SOGoMailBodyPart *) clientPart;
- (id) renderedPart;
- (NSDictionary *) attachmentIds;
- (void) setAttachmentIds: (NSDictionary *) newAttachmentIds;
- (NSData *)flatContent;

View File

@ -189,9 +189,19 @@
[self className], @"type",
type, @"contentType",
[[self generateResponse] contentAsString], @"content",
[self filenameForDisplay], @"filename",
[self preferredPathExtension], @"extension",
[[self sizeFormatter] stringForObjectValue: [bodyInfo objectForKey: @"size"]], @"size",
[self pathToAttachment], @"viewURL",
[self pathForDownload], @"downloadURL",
nil];
}
- (NSDictionary *) attachmentIds
{
return attachmentIds;
}
//
// Attachment IDs are used to replace CID from HTML content
// with their MIME parts when viewing an HTML mail with

View File

@ -174,6 +174,11 @@ static BOOL showNamedTextAttachmentsInline = NO;
return [viewer pageWithName: @"UIxMailPartICalViewer"];
}
- (WOComponent *) tnefViewer
{
return [viewer pageWithName: @"UIxMailPartTNEFViewer"];
}
/* main viewer selection */
- (WOComponent *) viewerForBodyInfo: (id) _info
@ -271,6 +276,11 @@ static BOOL showNamedTextAttachmentsInline = NO;
}
}
if ([st isEqualToString: @"ms-tnef"])
{
return [self tnefViewer];
}
#if 0 /* the link viewer looks better than plain text ;-) */
if ([st isEqualToString: @"pgp-signature"]) // TODO: real PGP viewer
return [self textViewer];

View File

@ -180,11 +180,6 @@ static NSString *mailETag = nil;
return attachmentAttrs;
}
- (BOOL) hasAttachments
{
return [[self attachmentAttrs] count] > 0 ? YES : NO;
}
- (NSFormatter *) sizeFormatter
{
return [UIxMailSizeFormatter sharedMailSizeFormatter];
@ -199,14 +194,6 @@ static NSString *mailETag = nil;
return [formatter stringForObjectValue: [[self clientObject] date]];
}
- (NSString *) attachmentsText
{
if ([[self attachmentAttrs] count] > 1)
return [self labelForKey: @"files"];
else
return [self labelForKey: @"file"];
}
/* viewers */
//
@ -287,6 +274,7 @@ static NSString *mailETag = nil;
SOGoMailObject *co;
UIxEnvelopeAddressFormatter *addressFormatter;
UIxMailRenderingContext *mctx;
id viewer, renderedPart;
co = [self clientObject];
addressFormatter = [context mailEnvelopeAddressFormatter];
@ -338,11 +326,13 @@ static NSString *mailETag = nil;
andJSONRepresentation: data];
}
viewer = [self contentViewerComponent]; // set attachmentIds for common parts
renderedPart = [viewer renderedPart]; // set attachmentIds for encrypted & TNEF parts
data = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[self attachmentAttrs], @"attachmentAttrs",
[self shouldAskReceipt], @"shouldAskReceipt",
[NSNumber numberWithBool: [self mailIsDraft]], @"isDraft",
[[self contentViewerComponent] renderedPart], @"parts",
renderedPart, @"parts",
nil];
if ([self formattedDate])
[data setObject: [self formattedDate] forKey: @"date"];

View File

@ -0,0 +1,9 @@
<?xml version="1.0" standalone="yes"?>
<div xmlns="http://www.w3.org/1999/xhtml"
xmlns:var="http://www.skyrix.com/od/binding"
xmlns:const="http://www.skyrix.com/od/constant"
xmlns:rsrc="OGo:url"
xmlns:label="OGo:label"
>
<!-- decoded parts from winmail.dat -->
</div>

View File

@ -361,9 +361,23 @@
};
}
}
_.forEach(part.content, function(mixedPart) {
_visit(mixedPart);
var winmail = _.find(part.content, function(mixedPart) {
// Ignore empty content -- that could mean a decoding error server-side.
return mixedPart.type == 'UIxMailPartTnefViewer' && mixedPart.content.length > 0;
});
if (winmail && !_.find(part.content, function(mixedPart) {
return mixedPart.type == 'UIxMailPartAlternativeViewer';
})) {
// If there's no alternate part in the message, show the winmail.dat attachment only.
// Otherwise, show all parts.
_visit(winmail);
}
else {
_.forEach(part.content, function(mixedPart) {
_visit(mixedPart);
});
}
}
else {
if (angular.isUndefined(part.safeContent)) {

View File

@ -80,12 +80,21 @@
$mdPanel = ImageGallery.$mdPanel,
partSrc = angular.element(this.message.$content()[partIndex].content).find('img')[0].src;
var images = _.filter(this.message.attachmentAttrs, function(attrs) {
return attrs.mimetype.indexOf('image/') === 0 && attrs.mimetype.indexOf('svg+xml') < 0;
});
var _findImages = function (parts, images) {
_.forEach(parts, function (part) {
if (part.type == 'UIxMailPartImageViewer') {
images.push(part);
}
else if (typeof part.content != 'string') {
_findImages(part.content, images);
}
});
};
var images = [];
_findImages(this.message.$content(), images);
var selectedIndex = _.findIndex(images, function(image) {
return image.url.indexOf(partSrc) >= 0;
return partSrc.indexOf(image.viewURL) >= 0;
});
// Add a class to the body in order to modify the panel backdrop opacity
@ -128,7 +137,7 @@
' <div md-truncate class="md-flex" ng-bind="$panelCtrl.selectedImage.filename"></div>',
' <md-button class="md-icon-button"',
' aria-label="' + l('Save Attachment') + '"',
' ng-href="{{$panelCtrl.selectedImage.urlAsAttachment}}">',
' ng-href="{{$panelCtrl.selectedImage.downloadURL}}">',
' <md-icon>file_download</md-icon>',
' </md-button>',
' </div>',
@ -137,7 +146,7 @@
' ng-disabled="$panelCtrl.selectedIndex == 0">',
' <md-icon>navigate_before</md-icon>',
' </md-button>',
' <img class="sg-image" ng-src="{{$panelCtrl.selectedImage.url}}">',
' <img class="sg-image" ng-src="{{$panelCtrl.selectedImage.viewURL}}">',
' <md-button class="md-icon-button" ng-click="$panelCtrl.nextImage()"',
' ng-disabled="$panelCtrl.selectedIndex == $panelCtrl.lastIndex">',
' <md-icon>navigate_next</md-icon>',
@ -145,7 +154,7 @@
' </div>',
' <div class="sg-image-thumbnails">',
' <div class="sg-image-thumbnail" ng-repeat="image in ::$panelCtrl.images">',
' <img class="sg-hide" ng-src="{{::image.url}}" ng-click="$panelCtrl.selectImage($index)">',
' <img class="sg-hide" ng-src="{{::image.viewURL}}" ng-click="$panelCtrl.selectImage($index)">',
' </div>',
' </div>',
'</sg-image-gallery>'

2
configure vendored
View File

@ -397,7 +397,7 @@ EOF
}
checkDependencies() {
cfgwrite "BASE_LIBS := `gnustep-config --base-libs` -lzip"
cfgwrite "BASE_LIBS := `gnustep-config --base-libs` -lzip -lytnef"
if test "x$ARG_ENABLE_SAML2" = "x1"; then
checkLinking "lasso" required;
if test $? = 0; then

View File

@ -1,14 +1,14 @@
Source: sogo
Priority: optional
Maintainer: Inverse Support <support@inverse.ca>
Build-Depends: debhelper (>= 7.0.15), gobjc | objc-compiler, libgnustep-base-dev, libsope-appserver4.9-dev, libsope-core4.9-dev, libsope-gdl1-4.9-dev, libsope-ldap4.9-dev, libsope-mime4.9-dev, libsope-xml4.9-dev, libmemcached-dev, libxml2-dev, libsbjson-dev, libssl-dev, libcurl4-openssl-dev | libcurl4-gnutls-dev, libwbxml2-dev (>= 0.11.2), liblasso3-dev (>= 2.3.5), libzip-dev, libsodium-dev (>= 1.0.9) | base-files (<= 9.4ubuntu4)
Build-Depends: debhelper (>= 7.0.15), gobjc | objc-compiler, libgnustep-base-dev, libsope-appserver4.9-dev, libsope-core4.9-dev, libsope-gdl1-4.9-dev, libsope-ldap4.9-dev, libsope-mime4.9-dev, libsope-xml4.9-dev, libmemcached-dev, libxml2-dev, libsbjson-dev, libssl-dev, libcurl4-openssl-dev | libcurl4-gnutls-dev, libwbxml2-dev (>= 0.11.2), liblasso3-dev (>= 2.3.5), libytnef0-dev (>= 1.9.3), libzip-dev, libsodium-dev (>= 1.0.9) | base-files (<= 9.4ubuntu4)
Section: web
Standards-Version: 3.9.1
Package: sogo
Section: web
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, sope4.9-libxmlsaxdriver, sope4.9-db-connector, gnustep-make, libcurl3 | libcurl4, libzip1 | libzip2 | libzip4 | libzip5, liblasso3 (>= 2.3.5)
Depends: ${shlibs:Depends}, ${misc:Depends}, sope4.9-libxmlsaxdriver, sope4.9-db-connector, gnustep-make, libcurl3 | libcurl4, libytnef0 (>= 1.9.3), libzip1 | libzip2 | libzip4 | libzip5, liblasso3 (>= 2.3.5)
Recommends: memcached, apache2 | nginx | httpd
Description: a modern and scalable groupware
SOGo is a groupware server built around OpenGroupware.org (OGo) and

View File

@ -14,9 +14,9 @@ Group: Productivity/Groupware
Source: SOGo-%{sogo_version}.tar.gz
Prefix: /usr
AutoReqProv: off
Requires: gnustep-base >= 1.23, sope%{sope_major_version}%{sope_minor_version}-core, httpd, sope%{sope_major_version}%{sope_minor_version}-core, sope%{sope_major_version}%{sope_minor_version}-appserver, sope%{sope_major_version}%{sope_minor_version}-ldap, sope%{sope_major_version}%{sope_minor_version}-cards >= %{sogo_version}, sope%{sope_major_version}%{sope_minor_version}-gdl1-contentstore >= %{sogo_version}, sope%{sope_major_version}%{sope_minor_version}-sbjson, lasso, libmemcached, memcached, libcurl, libzip, liboath, libsodium
Requires: gnustep-base >= 1.23, sope%{sope_major_version}%{sope_minor_version}-core, httpd, sope%{sope_major_version}%{sope_minor_version}-core, sope%{sope_major_version}%{sope_minor_version}-appserver, sope%{sope_major_version}%{sope_minor_version}-ldap, sope%{sope_major_version}%{sope_minor_version}-cards >= %{sogo_version}, sope%{sope_major_version}%{sope_minor_version}-gdl1-contentstore >= %{sogo_version}, sope%{sope_major_version}%{sope_minor_version}-sbjson, lasso, libmemcached, memcached, libcurl, libzip, liboath, libsodium, ytnef
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}
BuildRequires: gcc-objc gnustep-base gnustep-make sope%{sope_major_version}%{sope_minor_version}-appserver-devel sope%{sope_major_version}%{sope_minor_version}-core-devel sope%{sope_major_version}%{sope_minor_version}-ldap-devel sope%{sope_major_version}%{sope_minor_version}-mime-devel sope%{sope_major_version}%{sope_minor_version}-xml-devel sope%{sope_major_version}%{sope_minor_version}-gdl1-devel sope%{sope_major_version}%{sope_minor_version}-sbjson-devel lasso-devel libmemcached-devel sed libcurl-devel libzip-devel liboath-devel libsodium-devel
BuildRequires: gcc-objc gnustep-base gnustep-make sope%{sope_major_version}%{sope_minor_version}-appserver-devel sope%{sope_major_version}%{sope_minor_version}-core-devel sope%{sope_major_version}%{sope_minor_version}-ldap-devel sope%{sope_major_version}%{sope_minor_version}-mime-devel sope%{sope_major_version}%{sope_minor_version}-xml-devel sope%{sope_major_version}%{sope_minor_version}-gdl1-devel sope%{sope_major_version}%{sope_minor_version}-sbjson-devel lasso-devel libmemcached-devel sed libcurl-devel libzip-devel liboath-devel libsodium-devel ytnef
%define saml2_cfg_opts "--enable-saml2"
%define mfa_cfg_opts "--enable-mfa"