mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-08-28 17:57:38 +00:00
feat(mail): initial support for ms-tnef (winmail.dat) body part
Fixes #2242 Fixes #4503
This commit is contained in:
@@ -30,6 +30,7 @@ Mailer_OBJC_FILES += \
|
||||
SOGoImageMailBodyPart.m \
|
||||
SOGoMessageMailBodyPart.m \
|
||||
SOGoCalendarMailBodyPart.m \
|
||||
SOGoTnefMailBodyPart.m \
|
||||
SOGoVCardMailBodyPart.m \
|
||||
\
|
||||
SOGoMailForward.m \
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright (C) 2005-2017 Inverse inc.
|
||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||
|
||||
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
|
||||
{
|
||||
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__ */
|
||||
@@ -0,0 +1,504 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#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 <SoObjects/Mailer/NSData+SMIME.h>
|
||||
|
||||
#import <ytnef.h>
|
||||
|
||||
#import "SOGoTnefMailBodyPart.h"
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
@implementation SOGoTnefMailBodyPart
|
||||
|
||||
/* Overwritten methods */
|
||||
|
||||
- (id) init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
part = nil;
|
||||
filename = nil;
|
||||
bodyParts = [[NGMimeMultipartBody alloc] init];
|
||||
[bodyParts retain];
|
||||
}
|
||||
|
||||
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;
|
||||
NSString *partName, *type, *subtype;
|
||||
variableLength *attachmentName;
|
||||
variableLength *filedata;
|
||||
|
||||
[self setPart: nil];
|
||||
partName = nil;
|
||||
data = [self fetchBLOB];
|
||||
|
||||
DWORD signature;
|
||||
memcpy(&signature, [data bytes], sizeof(DWORD));
|
||||
if (TNEFCheckForSignature(signature) == 0)
|
||||
{
|
||||
TNEFStruct tnef;
|
||||
|
||||
TNEFInitialize(&tnef);
|
||||
tnef.Debug = 0;
|
||||
if (TNEFParseMemory((unsigned char *)[data bytes], [data length], &tnef) != -1)
|
||||
{
|
||||
// unsigned int count = 0;
|
||||
|
||||
if (strcmp((char *)tnef.messageClass, "IPM.Microsoft Mail.Note") == 0)
|
||||
{
|
||||
if (tnef.subject.size > 0)
|
||||
{
|
||||
filedata = MAPIFindProperty(&(tnef.MapiProperties), PROP_TAG(PT_BINARY, PR_BODY_HTML));
|
||||
if (filedata != MAPI_UNDEFINED)
|
||||
{
|
||||
// count++;
|
||||
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)
|
||||
{
|
||||
RTFHandler *handler;
|
||||
variableLength buf;
|
||||
buf.data = DecompressRTF(filedata, &(buf.size));
|
||||
if (buf.data != NULL)
|
||||
{
|
||||
// count++;
|
||||
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"];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Other classes to handle:
|
||||
//
|
||||
// IPM.Contact
|
||||
// IPM.Task
|
||||
// IPM.Microsoft Schedule.MtgReq
|
||||
// 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 signature;
|
||||
|
||||
if (isObject)
|
||||
{
|
||||
// This is an "embedded object", so skip the 16-byte identifier first.
|
||||
memcpy(&signature, filedata->data + 16, sizeof(DWORD));
|
||||
if (TNEFCheckForSignature(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(&signature, filedata->data, sizeof(DWORD));
|
||||
if (TNEFCheckForSignature(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: 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: 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: 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: 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 */
|
||||
@@ -59,6 +59,9 @@
|
||||
SOGoMessageMailBodyPart = {
|
||||
superclass = "SOGoMailBodyPart";
|
||||
};
|
||||
SOGoTnefMailBodyPart = {
|
||||
superclass = "SOGoMailBodyPart";
|
||||
};
|
||||
SOGoCalendarMailBodyPart = {
|
||||
superclass = "SOGoMailBodyPart";
|
||||
};
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
@@ -25,6 +25,7 @@ MailPartViewers_OBJC_FILES += \
|
||||
UIxMailPartAlternativeViewer.m \
|
||||
UIxMailPartMessageViewer.m \
|
||||
UIxMailPartICalViewer.m \
|
||||
UIxMailPartTnefViewer.m \
|
||||
\
|
||||
UIxMailPartICalActions.m
|
||||
|
||||
|
||||
@@ -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)\"" \
|
||||
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -69,6 +69,7 @@
|
||||
- (SOGoMailBodyPart *) clientPart;
|
||||
- (id) renderedPart;
|
||||
|
||||
- (NSDictionary *) attachmentIds;
|
||||
- (void) setAttachmentIds: (NSDictionary *) newAttachmentIds;
|
||||
|
||||
- (NSData *)flatContent;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -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>'
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user