diff --git a/SoObjects/Mailer/GNUmakefile b/SoObjects/Mailer/GNUmakefile index dd371363d8..9845246d5a 100644 --- a/SoObjects/Mailer/GNUmakefile +++ b/SoObjects/Mailer/GNUmakefile @@ -30,6 +30,7 @@ Mailer_OBJC_FILES += \ SOGoImageMailBodyPart.m \ SOGoMessageMailBodyPart.m \ SOGoCalendarMailBodyPart.m \ + SOGoTnefMailBodyPart.m \ SOGoVCardMailBodyPart.m \ \ SOGoMailForward.m \ diff --git a/SoObjects/Mailer/SOGoMailBodyPart.m b/SoObjects/Mailer/SOGoMailBodyPart.m index 032bdba69a..8843bd4aa4 100644 --- a/SoObjects/Mailer/SOGoMailBodyPart.m +++ b/SoObjects/Mailer/SOGoMailBodyPart.m @@ -157,8 +157,7 @@ - (id) partInfo { if (!partInfo) { - partInfo - = [[self mailObject] lookupInfoForBodyPart: [self bodyPartPath]]; + partInfo = [[self mailObject] lookupInfoForBodyPart: [self bodyPartPath]]; [partInfo retain]; } @@ -546,7 +545,7 @@ + (Class) bodyPartClassForKey: (NSString *) _key return self; /* hard coded for now */ - + switch ([pe length]) { case 3: if ([pe isEqualToString:@"gif"] || @@ -588,6 +587,8 @@ + (Class) bodyPartClassForMimeType: (NSString *) mimeType classString = @"SOGoVCardMailBodyPart"; else if ([mimeType isEqualToString: @"message/rfc822"]) classString = @"SOGoMessageMailBodyPart"; + else if ([mimeType isEqualToString: @"application/ms-tnef"]) + classString = @"SOGoTnefMailBodyPart"; else { classString = @"SOGoMailBodyPart"; diff --git a/SoObjects/Mailer/SOGoTnefMailBodyPart.h b/SoObjects/Mailer/SOGoTnefMailBodyPart.h new file mode 100644 index 0000000000..754e9b359c --- /dev/null +++ b/SoObjects/Mailer/SOGoTnefMailBodyPart.h @@ -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__ */ diff --git a/SoObjects/Mailer/SOGoTnefMailBodyPart.m b/SoObjects/Mailer/SOGoTnefMailBodyPart.m new file mode 100644 index 0000000000..547311c49b --- /dev/null +++ b/SoObjects/Mailer/SOGoTnefMailBodyPart.m @@ -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 +#import +#import + +#import +#import + +#import +#import +#import +#import + +#import + +#import + +#import + +#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 */ diff --git a/SoObjects/Mailer/product.plist b/SoObjects/Mailer/product.plist index c477a1d73d..8a23f4d390 100644 --- a/SoObjects/Mailer/product.plist +++ b/SoObjects/Mailer/product.plist @@ -59,6 +59,9 @@ SOGoMessageMailBodyPart = { superclass = "SOGoMailBodyPart"; }; + SOGoTnefMailBodyPart = { + superclass = "SOGoMailBodyPart"; + }; SOGoCalendarMailBodyPart = { superclass = "SOGoMailBodyPart"; }; diff --git a/SoObjects/SOGo/GNUmakefile b/SoObjects/SOGo/GNUmakefile index f7785b6208..2950e55072 100644 --- a/SoObjects/SOGo/GNUmakefile +++ b/SoObjects/SOGo/GNUmakefile @@ -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 \ diff --git a/SoObjects/SOGo/RTFHandler.h b/SoObjects/SOGo/RTFHandler.h new file mode 100644 index 0000000000..9ae3a78beb --- /dev/null +++ b/SoObjects/SOGo/RTFHandler.h @@ -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 +#include +#include +#include +#include + +// +// +// +@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 diff --git a/SoObjects/SOGo/RTFHandler.m b/SoObjects/SOGo/RTFHandler.m new file mode 100644 index 0000000000..2b6c98f86c --- /dev/null +++ b/SoObjects/SOGo/RTFHandler.m @@ -0,0 +1,1481 @@ +/* + Copyright (C) 2005-2013 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 "RTFHandler.h" +#include +#include + +// +// Useful macros +// +#define ADVANCE self->_bytes++; self->_current_pos++; +#define ADVANCE_N(N) self->_bytes += (N); self->_current_pos += (N); +#define REWIND self->_bytes--; self->_current_pos--; + +#define DEFAULT_CHARSET 1 +#define FONTNAME_LEN_MAX 100 + +// +// Charset definitions. See http://msdn.microsoft.com/en-us/goglobal/bb964654 for all details. +// +const unsigned short ansicpg1250[256] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, 0x0000, 0x201a, 0x0000, 0x201e, 0x2026, 0x2020, 0x2021, 0x0000, 0x2030, 0x0160, 0x2039, 0x015a, 0x0164, 0x017d, 0x0179, + 0x0000, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x0000, 0x2122, 0x0161, 0x203a, 0x015b, 0x0165, 0x017e, 0x017a, + 0x00a0, 0x02c7, 0x02d8, 0x0141, 0x00a4, 0x0104, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x015e, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x017b, + 0x00b0, 0x00b1, 0x02db, 0x0142, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x0105, 0x015f, 0x00bb, 0x013d, 0x02dd, 0x013e, 0x017c, + 0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7, 0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e, + 0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7, 0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df, + 0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7, 0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f, + 0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7, 0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9 }; + +const unsigned short ansicpg1251[256] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0402, 0x0403, 0x201a, 0x0453, 0x201e, 0x2026, 0x2020, 0x2021, 0x20ac, 0x2030, 0x0409, 0x2039, 0x040a, 0x040c, 0x040b, 0x040f, + 0x0452, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x0000, 0x2122, 0x0459, 0x203a, 0x045a, 0x045c, 0x045b, 0x045f, + 0x00a0, 0x040e, 0x045e, 0x0408, 0x00a4, 0x0490, 0x00a6, 0x00a7, 0x0401, 0x00a9, 0x0404, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x0407, + 0x00b0, 0x00b1, 0x0406, 0x0456, 0x0491, 0x00b5, 0x00b6, 0x00b7, 0x0451, 0x2116, 0x0454, 0x00bb, 0x0458, 0x0405, 0x0455, 0x0457, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f }; + +const unsigned short ansicpg1252[256] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, 0x0000, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x0000, 0x017d, 0x0000, + 0x0000, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x0000, 0x017e, 0x0178, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff }; + +const unsigned short ansicpg1253[256] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, 0x0000, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x0000, 0x2030, 0x0000, 0x2039, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x0000, 0x2122, 0x0000, 0x203a, 0x0000, 0x0000, 0x0000, 0x0000, + 0x00a0, 0x0385, 0x0386, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x0000, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x2015, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x0384, 0x00b5, 0x00b6, 0x00b7, 0x0388, 0x0389, 0x038a, 0x00bb, 0x038c, 0x00bd, 0x038e, 0x038f, + 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, + 0x03a0, 0x03a1, 0x0000, 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7, 0x03a8, 0x03a9, 0x03aa, 0x03ab, 0x03ac, 0x03ad, 0x03ae, 0x03af, + 0x03b0, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, + 0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, 0x03c8, 0x03c9, 0x03ca, 0x03cb, 0x03cc, 0x03cd, 0x03ce, 0x0000 }; + +const unsigned short ansicpg1254[256] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, 0x0000, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x0000, 0x0000, 0x0000, + 0x0000, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x0000, 0x0000, 0x0178, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x011e, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x0130, 0x015e, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x011f, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x0131, 0x015f, 0x00ff }; + +const unsigned short ansicpg1255[256] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, 0x0000, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030, 0x0000, 0x2039, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x02dc, 0x2122, 0x0000, 0x203a, 0x0000, 0x0000, 0x0000, 0x0000, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x20aa, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00d7, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00f7, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x05b0, 0x05b1, 0x05b2, 0x05b3, 0x05b4, 0x05b5, 0x05b6, 0x05b7, 0x05b8, 0x05b9, 0x0000, 0x05bb, 0x05bc, 0x05bd, 0x05be, 0x05bf, + 0x05c0, 0x05c1, 0x05c2, 0x05c3, 0x05f0, 0x05f1, 0x05f2, 0x05f3, 0x05f4, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x05d0, 0x05d1, 0x05d2, 0x05d3, 0x05d4, 0x05d5, 0x05d6, 0x05d7, 0x05d8, 0x05d9, 0x05da, 0x05db, 0x05dc, 0x05dd, 0x05de, 0x05df, + 0x05e0, 0x05e1, 0x05e2, 0x05e3, 0x05e4, 0x05e5, 0x05e6, 0x05e7, 0x05e8, 0x05e9, 0x05ea, 0x0000, 0x0000, 0x200e, 0x200f, 0x0000 }; + +const unsigned short ansicpg1256[256] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, 0x067e, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030, 0x0679, 0x2039, 0x0152, 0x0686, 0x0698, 0x0688, + 0x06af, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x06a9, 0x2122, 0x0691, 0x203a, 0x0153, 0x200c, 0x200d, 0x06ba, + 0x00a0, 0x060c, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x06be, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x061b, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x061f, + 0x06c1, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, 0x0628, 0x0629, 0x062a, 0x062b, 0x062c, 0x062d, 0x062e, 0x062f, + 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x00d7, 0x0637, 0x0638, 0x0639, 0x063a, 0x0640, 0x0641, 0x0642, 0x0643, + 0x00e0, 0x0644, 0x00e2, 0x0645, 0x0646, 0x0647, 0x0648, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x0649, 0x064a, 0x00ee, 0x00ef, + 0x064b, 0x064c, 0x064d, 0x064e, 0x00f4, 0x064f, 0x0650, 0x00f7, 0x0651, 0x00f9, 0x0652, 0x00fb, 0x00fc, 0x200e, 0x200f, 0x06d2 }; + +const unsigned short ansicpg1257[256] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, 0x0000, 0x201a, 0x0000, 0x201e, 0x2026, 0x2020, 0x2021, 0x0000, 0x2030, 0x0000, 0x2039, 0x0000, 0x00a8, 0x02c7, 0x00b8, + 0x0000, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x0000, 0x2122, 0x0000, 0x203a, 0x0000, 0x00af, 0x02db, 0x0000, + 0x00a0, 0x0000, 0x00a2, 0x00a3, 0x00a4, 0x0000, 0x00a6, 0x00a7, 0x00d8, 0x00a9, 0x0156, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00c6, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00f8, 0x00b9, 0x0157, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00e6, + 0x0104, 0x012e, 0x0100, 0x0106, 0x00c4, 0x00c5, 0x0118, 0x0112, 0x010c, 0x00c9, 0x0179, 0x0116, 0x0122, 0x0136, 0x012a, 0x013b, + 0x0160, 0x0143, 0x0145, 0x00d3, 0x014c, 0x00d5, 0x00d6, 0x00d7, 0x0172, 0x0141, 0x015a, 0x016a, 0x00dc, 0x017b, 0x017d, 0x00df, + 0x0105, 0x012f, 0x0101, 0x0107, 0x00e4, 0x00e5, 0x0119, 0x0113, 0x010d, 0x00e9, 0x017a, 0x0117, 0x0123, 0x0137, 0x012b, 0x013c, + 0x0161, 0x0144, 0x0146, 0x00f3, 0x014d, 0x00f5, 0x00f6, 0x00f7, 0x0173, 0x0142, 0x015b, 0x016b, 0x00fc, 0x017c, 0x017e, 0x02d9 }; + +const unsigned short ansicpg1258[256] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, 0x0000, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030, 0x0000, 0x2039, 0x0152, 0x0000, 0x0000, 0x0000, + 0x0000, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x02dc, 0x2122, 0x0000, 0x203a, 0x0153, 0x0000, 0x0000, 0x0178, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x0300, 0x00cd, 0x00ce, 0x00cf, + 0x0110, 0x00d1, 0x0309, 0x00d3, 0x00d4, 0x01a0, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x01af, 0x0303, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x0301, 0x00ed, 0x00ee, 0x00ef, + 0x0111, 0x00f1, 0x0323, 0x00f3, 0x00f4, 0x01a1, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x01b0, 0x20ab, 0x00ff }; + +const unsigned short ansicpg874[256] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, 0x0000, 0x0000, 0x0000, 0x0000, 0x2026, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x00a0, 0x0e01, 0x0e02, 0x0e03, 0x0e04, 0x0e05, 0x0e06, 0x0e07, 0x0e08, 0x0e09, 0x0e0a, 0x0e0b, 0x0e0c, 0x0e0d, 0x0e0e, 0x0e0f, + 0x0e10, 0x0e11, 0x0e12, 0x0e13, 0x0e14, 0x0e15, 0x0e16, 0x0e17, 0x0e18, 0x0e19, 0x0e1a, 0x0e1b, 0x0e1c, 0x0e1d, 0x0e1e, 0x0e1f, + 0x0e20, 0x0e21, 0x0e22, 0x0e23, 0x0e24, 0x0e25, 0x0e26, 0x0e27, 0x0e28, 0x0e29, 0x0e2a, 0x0e2b, 0x0e2c, 0x0e2d, 0x0e2e, 0x0e2f, + 0x0e30, 0x0e31, 0x0e32, 0x0e33, 0x0e34, 0x0e35, 0x0e36, 0x0e37, 0x0e38, 0x0e39, 0x0e3a, 0x0000, 0x0000, 0x0000, 0x0000, 0x0e3f, + 0x0e40, 0x0e41, 0x0e42, 0x0e43, 0x0e44, 0x0e45, 0x0e46, 0x0e47, 0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, 0x0e4d, 0x0e4e, 0x0e4f, + 0x0e50, 0x0e51, 0x0e52, 0x0e53, 0x0e54, 0x0e55, 0x0e56, 0x0e57, 0x0e58, 0x0e59, 0x0e5a, 0x0e5b, 0x0000, 0x0000, 0x0000, 0x0000 }; + +// +// +// +@implementation RTFStack + +- (id) init +{ + if ((self = [super init])) + { + a = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void) dealloc +{ + [a release]; + [super dealloc]; +} + +- (void) push: (id) theObject +{ + [a addObject: theObject]; +} + +- (id) pop +{ + id o = nil; + + if ([a count]) + { + o = [[[a lastObject] retain] autorelease]; + [a removeLastObject]; + } + + return o; +} + +- (id) top +{ + id o = nil; + + if ([a count]) + { + o = [[[a lastObject] retain] autorelease]; + } + + return o; +} + +@end + +// +// +// +@implementation RTFFormattingOptions +@end + +// +// +// +@implementation RTFFontInfo + +- (id) init +{ + if ((self = [super init])) + { + + } + + charset = DEFAULT_CHARSET; + return self; +} + +- (void) dealloc +{ + [family release]; + [name release]; + [super dealloc]; +} + +- (NSString *) description +{ + NSString *description; + description = [NSString stringWithFormat: + @"%u name=%@ family=%@ charset=%u pitch=%u", + index, name, family, charset, pitch + ]; + return description; +} + +@end + +// +// +// +@implementation RTFFontTable + +- (id) init +{ + if ((self = [super init])) + { + fontInfos = NSCreateMapTable(NSObjectMapKeyCallBacks, NSObjectMapValueCallBacks, 128); + } + return self; +} + +- (void) dealloc +{ + NSFreeMapTable(fontInfos); + [super dealloc]; +} + +- (void) addFontInfo: (RTFFontInfo *) theFontInfo + atIndex: (unsigned int) theIndex +{ + NSNumber *key; + + key = [NSNumber numberWithInt: theIndex]; + NSMapInsert(fontInfos, key, (void*) theFontInfo); +} + +- (RTFFontInfo *) fontInfoAtIndex: (unsigned int) theIndex +{ + NSNumber *key; + + key = [NSNumber numberWithInt: theIndex]; + return NSMapGet(fontInfos, key); +} + +- (NSString *) description +{ + NSMutableString *description; + NSEnumerator *enumerator; + RTFFontInfo *fontInfo; + + description = [NSMutableString stringWithFormat: @"Number of fonts: %u\n", [fontInfos count]]; + + enumerator = [fontInfos objectEnumerator]; + while ((fontInfo = [enumerator nextObject])) + { + [description appendString: [fontInfo description]]; + [description appendString: @"\n"]; + } + + return description; +} + +@end + +// +// +// +@implementation RTFColorDef + +@end + +// +// +// +@implementation RTFColorTable + +- (id) init +{ + if ((self = [super init])) + { + colorDefs = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void) dealloc +{ + [colorDefs release]; + [super dealloc]; +} + +- (void) addColorDef: (RTFColorDef *) theColorDef +{ + [colorDefs addObject: theColorDef]; +} + +- (RTFColorDef *) colorDefAtIndex: (unsigned int) theIndex +{ + return [colorDefs objectAtIndex: theIndex]; +} + +@end + +// +// +// +@implementation RTFHandler + +static NSMapTable *_charsets = nil; +static NSMapTable *_cws = nil; +typedef enum { + CW_UNKNOWN = 0, + CW_ANSICPG, + CW_B, + CW_CF, + CW_COLORTBL, + CW_F, + CW_FONTTBL, + CW_I, + CW_PAR, + CW_PICT, + CW_SOFTLINE, + CW_STRIKE, + CW_STYLESHEET, + CW_TAB, + CW_U, + CW_UL, + CW_ULNONE +} commandWordId; + +static NSMapTable *_fontCws = nil; +typedef enum { + FONTCW_UNKNOWN = 0, + FONTCW_F, + FONTCW_FBIDI, + FONTCW_FCHARSET, + FONTCW_FDECOR, + FONTCW_FMODERN, + FONTCW_FNIL, + FONTCW_FPRQ, + FONTCW_FROMAN, + FONTCW_FSCRIPT, + FONTCW_FSWISS, + FONTCW_FTECH +} fontCommandWordId; + +static void _init_charsets_table() +{ + _charsets = NSCreateMapTable(NSObjectMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 23); + // 238 — Eastern European - cpg1250 + NSMapInsert(_charsets, @"ansicpg1250", ansicpg1250); + NSMapInsert(_charsets, [NSNumber numberWithUnsignedChar: 238], ansicpg1250); + // 204 — Russian - cpg1251 + NSMapInsert(_charsets, @"ansicpg1251", ansicpg1251); + NSMapInsert(_charsets, [NSNumber numberWithUnsignedChar: 204], ansicpg1251); + // 0 - Latin 1 - cpg1252 - also know as ANSI + NSMapInsert(_charsets, [NSNumber numberWithUnsignedChar: 0], ansicpg1252); + NSMapInsert(_charsets, @"ansicpg1252", ansicpg1252); + // 161 - Greek cpg1253 + NSMapInsert(_charsets, @"ansicpg1253", ansicpg1253); + NSMapInsert(_charsets, [NSNumber numberWithUnsignedChar: 161], ansicpg1253); + // 162 — Turkish - cpg1254 + NSMapInsert(_charsets, @"ansicpg1254", ansicpg1254); + NSMapInsert(_charsets, [NSNumber numberWithUnsignedChar: 162], ansicpg1254); + // 177 — Hebrew Traditional - cpg1255 + // also 181 - Hebrew user + NSMapInsert(_charsets, @"ansicpg1255", ansicpg1255); + NSMapInsert(_charsets, [NSNumber numberWithUnsignedChar: 177], ansicpg1255); + NSMapInsert(_charsets, [NSNumber numberWithUnsignedChar: 181], ansicpg1255); + // 178 — Arabic - cpg1256 + // also 179 - Arabic traditional + // also 180 - Arabic User + NSMapInsert(_charsets, @"ansicpg1256", ansicpg1256); + NSMapInsert(_charsets, [NSNumber numberWithUnsignedChar: 178], ansicpg1256); + NSMapInsert(_charsets, [NSNumber numberWithUnsignedChar: 179], ansicpg1256); + NSMapInsert(_charsets, [NSNumber numberWithUnsignedChar: 180], ansicpg1256); + // 186 — Baltic - pg 1257 + NSMapInsert(_charsets, @"ansicpg1257", ansicpg1257); + NSMapInsert(_charsets, [NSNumber numberWithUnsignedChar: 186], ansicpg1257); + // 163 — Vietnamese - pg1259 + NSMapInsert(_charsets, @"ansicpg1258", ansicpg1258); + NSMapInsert(_charsets, [NSNumber numberWithUnsignedChar: 163], ansicpg1258); + // 222 — Thai - cpg874 + NSMapInsert(_charsets, @"ansicpg874", ansicpg874); + NSMapInsert(_charsets, [NSNumber numberWithUnsignedChar: 222], ansicpg874); + + // TODO: check differences between traditional/user/no-qualified for Arabic and Hebrew + // TODO: missing codepage for the following codes: + // 2 — Symbol + // 3 — Invalid + // 77 — Mac + // 128 — Shift Jis + // 129 — Hangul + // 130 — Johab + // 134 — GB2312 + // 136 — Big5 + // 254 — PC 437 + // 255 — OEM +} + +static void _init_cws_table() +{ + _cws = NSCreateMapTable(NSObjectMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 16); + NSMapInsert(_cws, @"ansicpg", (void *) CW_ANSICPG); + NSMapInsert(_cws, @"b", (void *) CW_B); + NSMapInsert(_cws, @"cf", (void *) CW_CF); + NSMapInsert(_cws, @"colortbl", (void *) CW_COLORTBL); + NSMapInsert(_cws, @"f", (void *) CW_F); + NSMapInsert(_cws, @"fonttbl", (void *) CW_FONTTBL); + NSMapInsert(_cws, @"i", (void *) CW_I); + NSMapInsert(_cws, @"par", (void *) CW_PAR); + NSMapInsert(_cws, @"pict", (void *) CW_PICT); + NSMapInsert(_cws, @"softline", (void *) CW_SOFTLINE); + NSMapInsert(_cws, @"strike", (void *) CW_STRIKE); + NSMapInsert(_cws, @"stylesheet", (void *) CW_STYLESHEET); + NSMapInsert(_cws, @"tab", (void *) CW_TAB); + NSMapInsert(_cws, @"u", (void *) CW_U); + NSMapInsert(_cws, @"ul", (void *) CW_UL); + NSMapInsert(_cws, @"ulnone", (void *) CW_ULNONE); +} + +static void _init_fontCws_table() +{ + _fontCws = NSCreateMapTable(NSObjectMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 23); + NSMapInsert(_fontCws, @"f", (void *) FONTCW_F); + NSMapInsert(_fontCws, @"fbidi", (void *) FONTCW_FBIDI); + NSMapInsert(_fontCws, @"fcharset", (void *) FONTCW_FCHARSET); + NSMapInsert(_fontCws, @"fdecor", (void *) FONTCW_FDECOR); + NSMapInsert(_fontCws, @"fmodern", (void *) FONTCW_FMODERN); + NSMapInsert(_fontCws, @"fnil", (void *) FONTCW_FNIL); + NSMapInsert(_fontCws, @"fprq", (void *) FONTCW_FPRQ); + NSMapInsert(_fontCws, @"froman", (void *) FONTCW_FROMAN); + NSMapInsert(_fontCws, @"fscript", (void *) FONTCW_FSCRIPT); + NSMapInsert(_fontCws, @"fswiss", (void *) FONTCW_FSWISS); + NSMapInsert(_fontCws, @"ftech", (void *) FONTCW_FTECH); +} + +- (id) initWithData: (NSData *) theData +{ + if ((self = [super init])) + { + ASSIGN(_data, theData); + _bytes = (char *)[_data bytes]; + _len = [_data length]; + _current_pos = 0; + if (_charsets == nil) + _init_charsets_table(); + if (_cws == nil) + _init_cws_table(); + if (_fontCws == nil) + _init_fontCws_table(); + } + + return self; +} + +- (void) dealloc +{ + NSFreeMapTable(_charsets); + [_data release]; + [super dealloc]; +} + +/* + Returns pointer to the control word and in len pointer its length including numeric argument +*/ +- (const char *) parseControlWord: (unsigned int *) len +{ + const char *start, *end; + + start = ADVANCE; + + /* + A control word is defined by: + + \ + */ + while (isalpha(*_bytes)) + { + ADVANCE; + } + + /* + The can be one of the following: + + - A space. This serves only to delimit a control word and is + ignored in subsequent processing. + + - A numeric digit or an ASCII minus sign (-), which indicates + that a numeric parameter is associated with the control word. + Only this case requires to include it in the control word. + + - Any character other than a letter or a digit + */ + if (*_bytes == '-' || isdigit(*_bytes)) + { + ADVANCE; + while (isdigit(*_bytes)) // TODO: Allow up to 10 digits + { + ADVANCE; + } + } + /* In this case, the delimiting character terminates the control + word and is not part of the control word. */ + + end = _bytes; + *len = end-start-1; + + return start+1; +} + +- (const char *) parseControlWordAndSetLenIn: (unsigned int *) len + setHasIntArgumentIn: (BOOL *) hasArg + setIntArgumentIn: (int *) arg +{ + const char *start; + const char *end = NULL; + const char *startArg = NULL; + const char *endArg = NULL; + + ADVANCE; + start = _bytes; + + /* + A control word is defined by: + + \ + */ + while (isalpha(*_bytes)) + { + end = _bytes; + ADVANCE; + } + + if (end == NULL) + { + return NULL; + } + + /* + The can be one of the following: + + - A space. This serves only to delimit a control word and is + ignored in subsequent processing. + + - A numeric digit or an ASCII minus sign (-), which indicates + that a numeric parameter is associated with the control word. + Only this case requires to include it in the control word. + + - Any character other than a letter or a digit + */ + + if (*_bytes == '-' || isdigit(*_bytes)) + { + startArg = _bytes; + endArg = _bytes; + ADVANCE; + while (isdigit(*_bytes)) + { + endArg = _bytes; + ADVANCE; + } + } + + *hasArg = NO; + *arg = 0; + if (startArg) + { + NSString *s; + unsigned int argLength = endArg - startArg + 1; + // the next guard is to protect against a single '-' + if (argLength > 1 || (*startArg != '-')) + { + s = [[NSString alloc] initWithBytesNoCopy: (void *) startArg + length: argLength + encoding: NSASCIIStringEncoding + freeWhenDone: NO]; + [s autorelease]; + *hasArg = YES; + *arg = [s intValue]; // Warning: it does not detect conversion errors + } + } + + + /* In other cases, the delimiting character terminates the control + word and is not part of the control word. */ + *len = end - start + 1; + return start; +} + +// +// {\colortbl\red0\green0\blue0;\red128\green0\blue0;\red255\green0\blue0;} +// +- (RTFColorTable *) parseColorTable +{ + RTFColorTable *colorTable; + RTFColorDef *colorDef; + + colorTable = [[[RTFColorTable alloc] init] autorelease]; + colorDef = [[[RTFColorDef alloc] init] autorelease]; + + while (*_bytes != '}') + { + if (*_bytes == ';') + { + [colorTable addColorDef: colorDef]; + colorDef = [[[RTFColorDef alloc] init] autorelease]; + ADVANCE; + } + else if (*_bytes == '\\') + { + const char *cw; + unsigned int len; + NSString *s; + + cw = [self parseControlWord: &len]; + + // Skip our control word + //if (strncmp(start+1, "colortbl", len) == 0) + // continue; + + s = [[NSString alloc] initWithBytesNoCopy: (void *)cw + length: len + encoding: NSASCIIStringEncoding + freeWhenDone: NO]; + [s autorelease]; + + if ([s hasPrefix: @"red"]) + { + colorDef->red = [[s substringFromIndex: 3] intValue]; + } + else if ([s hasPrefix: @"green"]) + { + colorDef->green = [[s substringFromIndex: 4] intValue]; + } + else + { + colorDef->blue = [[s substringFromIndex: 4] intValue]; + } + } + else + { + ADVANCE; + } + + } + + return colorTable; +} + +// +// Possible formats: +// +// {\fonttbl\f0\fswiss Helvetica;} +// {\fonttbl{\f0\froman\fcharset0\fprq2 Arial;}{\f1\fswiss\fprq2\fcharset0 Arial;}} +// +// FIXME: Complex ones not handled right now: +// +// {\fonttbl{\f2\fnil\fcharset256\fprq2{\*\panose 00020703090202050204}Courier New;}{... +// {\fonttbl{\f31\fnil\fcharset0\fprq0 Times New Roman Monotype{\*\falt Times New Roman};}{... +// +// We receive the full string. +// +- (RTFFontTable *) parseFontTable +{ + RTFFontTable *fontTable; + RTFFontInfo *fontInfo; + + unsigned int level; + + fontTable = [[[RTFFontTable alloc] init] autorelease]; + fontInfo = nil; + level = 0; + + do + { + if (*_bytes == '{') + { + if (fontTable && level == 1) + { + fontInfo = [[[RTFFontInfo alloc] init] autorelease]; + } + ADVANCE; + level++; + } + else if (*_bytes == '}') + { + if (fontTable && level == 2) //&& ![NSAllMapTableValues(fontTable->fontInfos) containsObject: fontInfo]) + { + [fontTable addFontInfo: fontInfo atIndex: fontInfo->index]; + } + ADVANCE; + level--; + } + else if (*_bytes == '\\') + { + const char *cw; + unsigned int len; + BOOL hasArg; + int arg; + NSString *cwKey; + fontCommandWordId cwId; + + cw = [self parseControlWordAndSetLenIn: &len + setHasIntArgumentIn: &hasArg + setIntArgumentIn: &arg]; + if (level != 2) + continue; + else if (cw == NULL) + continue; + + cwKey= [[NSString alloc] initWithBytesNoCopy: (void *)cw + length: len + encoding: NSASCIIStringEncoding + freeWhenDone: NO]; + [cwKey autorelease]; + + cwId = (fontCommandWordId) NSMapGet(_fontCws, cwKey); + switch (cwId) + { + case FONTCW_F: + if (hasArg) + fontInfo->index = arg; + break; + case FONTCW_FBIDI: + fontInfo->family = @"bidi"; + break; + case FONTCW_FCHARSET: + if (hasArg) + fontInfo->charset = arg; + break; + case FONTCW_FDECOR: + fontInfo->family = @"decor"; + break; + case FONTCW_FMODERN: + fontInfo->family = @"modern"; + break; + case FONTCW_FNIL: + fontInfo->family = @"nil"; + break; + case FONTCW_FPRQ: + if (hasArg) + fontInfo->pitch = arg; + break; + case FONTCW_FROMAN: + fontInfo->family = @"roman"; + break; + case FONTCW_FSCRIPT: + fontInfo->family = @"script"; + break; + case FONTCW_FSWISS: + fontInfo->family = @"swiss"; + break; + case FONTCW_FTECH: + fontInfo->family = @"tech"; + break; + case FONTCW_UNKNOWN: + default: + // do nothing + break; + } + } + else // no char + { + if (level == 2 && isalnum(*_bytes)) + { + // we assume this is the fontname + unsigned int fontnameLen; + const char *delim = strpbrk(_bytes, ";{}\\"); + if (delim == NULL) + { + // no delimiter found, we skip to next characters + ADVANCE; + continue; + } + fontnameLen = delim - _bytes; + // only valid if the delimiter is a correct ';' + if (*delim == ';') + { + // there is no explicit limit length but we took 100 + // as protection + if (delim && fontnameLen <= FONTNAME_LEN_MAX) + { + fontInfo->name = [[NSString alloc] initWithBytesNoCopy: (char *) _bytes + length: fontnameLen + encoding: NSASCIIStringEncoding + freeWhenDone: NO]; + ADVANCE_N(fontnameLen); + } + } + else { + // advance just before the special character + ADVANCE_N(fontnameLen - 1); + } + } + ADVANCE; + } + + } while (level > 0); + + return fontTable; +} + +// +// +// +- (void) parseStyleSheet +{ + unsigned int count; + + count = 0; + + do + { + if (*_bytes == '{') + { + count++; + } + else if (*_bytes == '}') + { + count--; + } + ADVANCE; + + } while (count != 0); +} + +- (void) parseIgnoringEverything +{ + unsigned int count = 1; + // Ignore everything. But we cannot parse it blindly because it could have + // binary data with '}' and '{' bytes, so disasters can happen and they will + do + { + if (*_bytes == '\\') + { + unsigned int binary_size, len = 0, cw_len; + const char *cw = [self parseControlWord: &len]; + cw_len = strlen("bin"); + if (strncmp(cw, "bin", cw_len) == 0 && len > cw_len) + { + NSString *s; + s = [[NSString alloc] initWithBytesNoCopy: (void *) cw + cw_len + length: len - cw_len + encoding: NSASCIIStringEncoding + freeWhenDone: NO]; + [s autorelease]; + binary_size = [s intValue]; + ADVANCE_N(binary_size); + } + } + + if (*_bytes == '{') count++; + if (*_bytes == '}') count--; + ADVANCE; + } + while (count > 0); +} + +// +// +// +- (void) parsePicture +{ + [self parseIgnoringEverything]; +} + + +// todo: This keyword is only valid in the RTF header section right after the \ansi, \mac, \pc or \pca keyword. +inline static void parseAnsicpg (BOOL hasArg, int arg, const unsigned short **out_default_char) +{ + NSString *key; + const unsigned short *res; + + if (!hasArg) + return; + key = [NSString stringWithFormat: @"anscicpg%i", arg]; + res = NSMapGet(_charsets, key); + if (res) + *out_default_char = res; +} + +inline static void parseB(RTFHandler *self, BOOL hasArg, int arg, RTFFormattingOptions *formattingOptions) +{ + if (!formattingOptions) + return; + if (hasArg && arg == 0) + { + [self->_html appendBytes: "" length: 4]; + formattingOptions->bold = NO; + } + else + { + [self->_html appendBytes: "" length: 3]; + formattingOptions->bold = YES; + } +} + +inline static void parseCf(RTFHandler *self, BOOL hasArg, int arg, RTFFormattingOptions *formattingOptions, RTFColorTable *colorTable) +{ + RTFColorDef *colorDef; + char *v; + + if (!hasArg) + return; + if (!formattingOptions) + return; + + colorDef = [colorTable colorDefAtIndex: arg]; + if (!colorDef) + return; + + if (formattingOptions->color_index >= 0) + { + [self->_html appendBytes: "" length: 7]; + } + + formattingOptions->color_index = arg; + + v = calloc(23, sizeof(char)); + sprintf(v, "", colorDef->red, colorDef->green, colorDef->blue); + [self->_html appendBytes: v length: strlen(v)]; + free(v); +} + + +inline static void parseColorTableWrapper(RTFHandler *self, RTFColorTable **colorTable) +{ + *colorTable = [self parseColorTable]; +} + +inline static void parseF(RTFHandler *self, BOOL hasArg, int arg, RTFFormattingOptions *formattingOptions, RTFFontTable *fontTable) +{ + RTFFontInfo *fontInfo; + + if (!hasArg) + return; + if (!formattingOptions) + return; + + if (formattingOptions->font_index >= 0 && arg != formattingOptions->font_index) + { + [self->_html appendBytes: "" length: 7]; + } + + formattingOptions->font_index = arg; + + fontInfo = [fontTable fontInfoAtIndex: arg]; + char *v = NULL; + if (fontInfo && fontInfo->name) + { + if ([fontInfo->name length] < 128) + { + int tag_size = 15 + [fontInfo->name length]; + v = calloc(tag_size, sizeof(char)); + snprintf(v, tag_size, "", [fontInfo->name UTF8String]); + } + else + { + NSLog(@"RTFHandler: Font %u has %d chars length, parse error? " + "Ignored", arg, [fontInfo->name length]); + v = calloc(7, sizeof(char)); + sprintf(v, ""); + } + } + else + { + // RTF badformed? We don't know about that font (arg index not found). + // Anyhow, we still open the html tag because in the future + // we will close it (e.g. when new font is used). + v = calloc(7, sizeof(char)); + sprintf(v, ""); + } + + if (fontInfo && fontInfo->charset) + { + if (fontInfo->charset == DEFAULT_CHARSET) + /* charset 1 is default charset */ + formattingOptions->charset = NULL; + else { + NSNumber *key = [NSNumber numberWithUnsignedChar: fontInfo->charset]; + formattingOptions->charset = NSMapGet(_charsets, key); + } + } + + [self->_html appendBytes: v length: strlen(v)]; + free(v); +} + +inline static void parseFontTableWrapper(RTFHandler *self, const char * cw, RTFFontTable **fontTable) +{ + // We rewind our buffer so we start at the beginning of {\fonttbl... + self->_bytes = cw-2; + self->_current_pos -= 9; // Length: {\fonttbl + *fontTable = [self parseFontTable]; + + // We go back 1 byte in order to end our section properly ('}' character) + REWIND; +} + +inline static void parseI(RTFHandler *self, BOOL hasArg, int arg, RTFFormattingOptions *formattingOptions) +{ + if (!formattingOptions) + return; + if (hasArg && arg == 0) + { + [self->_html appendBytes: "" length: 4]; + formattingOptions->italic = NO; + } + else + { + [self->_html appendBytes: "" length: 3]; + formattingOptions->italic = YES; + } +} + +inline static void parsePar(RTFHandler *self) +{ + [self->_html appendBytes: "
" length: 4]; +} + +inline static void parsePictureWrapper(RTFHandler *self, const char * cw) +{ + self->_bytes = cw-2; + self->_current_pos -= 6; // Length: {\pict + [self parsePicture]; + REWIND; +} + +// same implementation that /par +inline static void parseSoftline(RTFHandler *self) +{ + [self->_html appendBytes: "
" length: 4]; +} + +inline static void parseStrike(RTFHandler *self, BOOL hasArg, int arg, RTFFormattingOptions *formattingOptions) +{ + if (!formattingOptions) + return; + if (hasArg && arg == 0) + { + [self->_html appendBytes: "" length: 9]; + formattingOptions->strikethrough = NO; + } + else + { + [self->_html appendBytes: "" length: 8]; + formattingOptions->strikethrough = YES; + } +} + +inline static void parseStyleSheetWrapper(RTFHandler *self, const char * cw) +{ + self->_bytes = cw-2; + self->_current_pos -= 12; // Length: {\stylesheet + [self parseStyleSheet]; + REWIND; +} + +inline static void parseTab(RTFHandler *self) +{ + [self->_html appendBytes: "  " length: 12]; +} + +inline static void parseU(RTFHandler *self, BOOL hasArg, int arg) +{ + unichar uch; + NSString *s; + NSData *d; + + if (!hasArg) + return; + if (arg < 0) + // a negative value means a value greater than 32767 + arg = 32767 - arg; + + uch = (unichar) arg; + s = [NSString stringWithCharacters: &uch length: 1]; + d = [s dataUsingEncoding: NSUTF8StringEncoding]; + [self->_html appendData: d]; +} + +inline static void parseUl(RTFHandler *self, BOOL hasArg, int arg, RTFFormattingOptions *formattingOptions) +{ + if (!formattingOptions) + return; + if (hasArg && arg ==0) + { + [self->_html appendBytes: "" length: 4]; + formattingOptions->underline = NO; + } + else + { + [self->_html appendBytes: "" length: 3]; + formattingOptions->underline = YES; + } +} + + +- (NSMutableData *) parse +{ + RTFFormattingOptions *formattingOptions; + RTFColorTable *colorTable; + RTFFontTable *fontTable; + RTFStack *stack; + + const unsigned short *defaultCharset; + + // convenience variables for parsing + unsigned char c; + NSData *d; + NSString *s; + + stack = [[RTFStack alloc] init]; + fontTable = nil; + colorTable = nil; + defaultCharset = ansicpg1252; + formattingOptions = nil; + + _html = [[[NSMutableData alloc] init] autorelease]; + [_html appendBytes: "" length: 34]; + + + // Check if we got RTF data + // this does not allow \s\n before '}' neither newline before control command + if (_len > 4 && strncmp((const char*)_bytes, "{\\rtf", 4) != 0) + return nil; + + while (_current_pos < _len) + { + c = *_bytes; + + // RTF control code + if (c == '\\') + { + unsigned int len; + const char *cw; + BOOL hasArg; + int arg; + NSString *cwKey; + commandWordId cwId; + char nextByte = *(_bytes+1); + + if (nextByte == '\'') + { + // A hexadecimal value, based on the specified character set (may be used to identify 8-bit values). + const char *b1, *b2; + short index; + short tmp; + + const unsigned short * active_charset; + if (formattingOptions && formattingOptions->charset) + active_charset = formattingOptions->charset; + else + active_charset = defaultCharset; + + + ADVANCE; + ADVANCE; + + b1 = ADVANCE; + b2 = ADVANCE; + + tmp = (isdigit(*b1) ? *b1 - 48 : toupper(*b1) - 55); + if (tmp < 0 || tmp > 16) + { + // Incorrect first hexadecimal character. Skipping. + continue; + } + index = tmp*16; + + tmp = (isdigit(*b2) ? *b2 - 48 : toupper(*b2) - 55); + if (tmp < 0 || tmp > 16) + { + // Incorrect second hexadecimal character. Skipping. + continue; + } + index += tmp; + + s = [NSString stringWithCharacters: &(active_charset[index]) length: 1]; + d = [s dataUsingEncoding: NSUTF8StringEncoding]; + [_html appendData: d]; + continue; + } + else if (nextByte == '*') + { + [self parseIgnoringEverything]; + continue; + } + else if (!isalpha(nextByte)) + { + // escape + character + ADVANCE_N(2); + // check for special escapes for the no-implemented features + // for control of word breaking + if (nextByte == '~') + // no breaking space + nextByte = ' '; + else if (nextByte == '-') + // optional hyphen; we skip it + continue; + else if (nextByte == '_') + // no breaking hyphen, treat it as a normal hyphen + nextByte = '-'; + + [_html appendBytes: &nextByte length: 1]; + continue; + } + + + cw = [self parseControlWordAndSetLenIn: &len + setHasIntArgumentIn: &hasArg + setIntArgumentIn: &arg]; + if (cw == NULL) + continue; + + cwKey= [[NSString alloc] initWithBytesNoCopy: (void *)cw + length: len + encoding: NSASCIIStringEncoding + freeWhenDone: NO]; + [cwKey autorelease]; + + cwId = (commandWordId) NSMapGet(_cws, cwKey); + switch (cwId) + { + case CW_ANSICPG: + parseAnsicpg(hasArg, arg, &defaultCharset); + break; + case CW_B: + parseB(self, hasArg, arg, formattingOptions); + break; + case CW_CF: + parseCf(self, hasArg, arg, formattingOptions, colorTable); + break; + case CW_COLORTBL: + parseColorTableWrapper(self, &colorTable); + break; + case CW_F: + parseF(self, hasArg, arg, formattingOptions, fontTable); + break; + case CW_FONTTBL: + parseFontTableWrapper(self, cw, &fontTable); + break; + case CW_I: + parseI(self, hasArg, arg, formattingOptions); + break; + case CW_PAR: + parsePar(self); + break; + case CW_PICT: + parsePictureWrapper(self, cw); + break; + case CW_SOFTLINE: + parseSoftline(self); + break; + case CW_STRIKE: + parseStrike(self, hasArg, arg, formattingOptions); + break; + case CW_STYLESHEET: + parseStyleSheetWrapper(self, cw); + break; + case CW_TAB: + parseTab(self); + break; + case CW_U: + parseU(self, hasArg, arg); + break; + case CW_UL: + parseUl(self, hasArg, arg, formattingOptions); + break; + case CW_ULNONE: + parseUl(self, YES, 0, formattingOptions); + break; + case CW_UNKNOWN: + default: + // do nothing + break; + } + + // If a space delimits the control word, the space does not appear in the document. + // Any characters following the delimiter, including spaces, will appear in the document. (except newline!) + if (*_bytes == ' ') + { + ADVANCE; + } + } + else if (c == '{') + { + formattingOptions = [[[RTFFormattingOptions alloc] init] autorelease]; + + formattingOptions->bold = NO; + formattingOptions->italic = NO; + formattingOptions->strikethrough = NO; + formattingOptions->underline = NO; + formattingOptions->font_index = -1; + formattingOptions->color_index = -1; + formattingOptions->start_pos = [_html length]; + formattingOptions->charset = defaultCharset; + [stack push: formattingOptions]; + ADVANCE; + } + else if (c == '}') + { + formattingOptions = [stack pop]; + + if (formattingOptions) + { + // Handle {\b bold} vs. \b bold \b0 + if (formattingOptions->bold) + { + [_html appendBytes: "
" length: 4]; + } + + if (formattingOptions->italic) + { + [_html appendBytes: "" length: 4]; + } + + if (formattingOptions->strikethrough) + { + [_html appendBytes: "" length: 9]; + } + + if (formattingOptions->underline) + { + [_html appendBytes: "" length: 4]; + } + + if (formattingOptions->font_index >= 0) + { + [_html appendBytes: "" length: 7]; + } + + if (formattingOptions->color_index >= 0) + { + [_html appendBytes: "" length: 7]; + } + } + + formattingOptions = [stack top]; + ADVANCE; + } + else + { + c = *_bytes; + // We avoid appending NULL bytes or endlines + if (c && (c != '\n') && (c != '\r')) + { + const unsigned short * active_charset; + if (formattingOptions && formattingOptions->charset) + active_charset = formattingOptions->charset; + else + active_charset = defaultCharset; + + s = [NSString stringWithCharacters: &(active_charset[c]) length: 1]; + d = [s dataUsingEncoding: NSUTF8StringEncoding]; + [_html appendData: d]; + } + ADVANCE; + } + } + + [_html appendBytes: "" length: 14]; + + [stack release]; + return _html; +} + +/* This method is for ease of testing and should not be used in normal operations */ +- (void) mangleInternalStateWithBytesPtr: (const char*) newBytes + andCurrentPos: (int) newCurrentPos +{ + _bytes = newBytes; + _current_pos = newCurrentPos; +} + +@end diff --git a/UI/MailPartViewers/GNUmakefile b/UI/MailPartViewers/GNUmakefile index 6834d0f68e..95718038bb 100644 --- a/UI/MailPartViewers/GNUmakefile +++ b/UI/MailPartViewers/GNUmakefile @@ -25,6 +25,7 @@ MailPartViewers_OBJC_FILES += \ UIxMailPartAlternativeViewer.m \ UIxMailPartMessageViewer.m \ UIxMailPartICalViewer.m \ + UIxMailPartTnefViewer.m \ \ UIxMailPartICalActions.m diff --git a/UI/MailPartViewers/GNUmakefile.preamble b/UI/MailPartViewers/GNUmakefile.preamble index ea7a2d4650..79f6419178 100644 --- a/UI/MailPartViewers/GNUmakefile.preamble +++ b/UI/MailPartViewers/GNUmakefile.preamble @@ -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)\"" \ diff --git a/UI/MailPartViewers/UIxMailPartTnefViewer.h b/UI/MailPartViewers/UIxMailPartTnefViewer.h new file mode 100644 index 0000000000..bd48f6342c --- /dev/null +++ b/UI/MailPartViewers/UIxMailPartTnefViewer.h @@ -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 */ diff --git a/UI/MailPartViewers/UIxMailPartTnefViewer.m b/UI/MailPartViewers/UIxMailPartTnefViewer.m new file mode 100644 index 0000000000..3603e32052 --- /dev/null +++ b/UI/MailPartViewers/UIxMailPartTnefViewer.m @@ -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 +#import + +#import +#import +#import +#import + +#import +#import + +#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 */ diff --git a/UI/MailPartViewers/UIxMailPartViewer.h b/UI/MailPartViewers/UIxMailPartViewer.h index 6844b39407..8d18dc8d8e 100644 --- a/UI/MailPartViewers/UIxMailPartViewer.h +++ b/UI/MailPartViewers/UIxMailPartViewer.h @@ -69,6 +69,7 @@ - (SOGoMailBodyPart *) clientPart; - (id) renderedPart; +- (NSDictionary *) attachmentIds; - (void) setAttachmentIds: (NSDictionary *) newAttachmentIds; - (NSData *)flatContent; diff --git a/UI/MailPartViewers/UIxMailPartViewer.m b/UI/MailPartViewers/UIxMailPartViewer.m index 9bc68cd012..edbfb40f1c 100644 --- a/UI/MailPartViewers/UIxMailPartViewer.m +++ b/UI/MailPartViewers/UIxMailPartViewer.m @@ -189,9 +189,19 @@ - (id) renderedPart [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 diff --git a/UI/MailPartViewers/UIxMailRenderingContext.m b/UI/MailPartViewers/UIxMailRenderingContext.m index 672ee661fd..48a7c899ef 100644 --- a/UI/MailPartViewers/UIxMailRenderingContext.m +++ b/UI/MailPartViewers/UIxMailRenderingContext.m @@ -174,6 +174,11 @@ - (WOComponent *) iCalViewer return [viewer pageWithName: @"UIxMailPartICalViewer"]; } +- (WOComponent *) tnefViewer +{ + return [viewer pageWithName: @"UIxMailPartTnefViewer"]; +} + /* main viewer selection */ - (WOComponent *) viewerForBodyInfo: (id) _info @@ -271,6 +276,11 @@ - (WOComponent *) viewerForBodyInfo: (id) _info } } + 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]; diff --git a/UI/MailerUI/UIxMailView.m b/UI/MailerUI/UIxMailView.m index 3e2b16ae0a..85b652e0ee 100644 --- a/UI/MailerUI/UIxMailView.m +++ b/UI/MailerUI/UIxMailView.m @@ -180,11 +180,6 @@ - (NSArray *) attachmentAttrs return attachmentAttrs; } -- (BOOL) hasAttachments -{ - return [[self attachmentAttrs] count] > 0 ? YES : NO; -} - - (NSFormatter *) sizeFormatter { return [UIxMailSizeFormatter sharedMailSizeFormatter]; @@ -199,14 +194,6 @@ - (NSString *) formattedDate 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 @@ - (id) contentViewerComponent SOGoMailObject *co; UIxEnvelopeAddressFormatter *addressFormatter; UIxMailRenderingContext *mctx; + id viewer, renderedPart; co = [self clientObject]; addressFormatter = [context mailEnvelopeAddressFormatter]; @@ -338,11 +326,13 @@ those are the IMAP4 flags (and annotations, which we do not use). 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"]; diff --git a/UI/WebServerResources/js/Mailer/sgImageGallery.service.js b/UI/WebServerResources/js/Mailer/sgImageGallery.service.js index d5b37b9f33..d391fb8be3 100644 --- a/UI/WebServerResources/js/Mailer/sgImageGallery.service.js +++ b/UI/WebServerResources/js/Mailer/sgImageGallery.service.js @@ -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 @@ '
', ' ', + ' ng-href="{{$panelCtrl.selectedImage.downloadURL}}">', ' file_download', ' ', ' ', @@ -137,7 +146,7 @@ ' ng-disabled="$panelCtrl.selectedIndex == 0">', ' navigate_before', ' ', - ' ', + ' ', ' ', ' navigate_next', @@ -145,7 +154,7 @@ ' ', '
', '
', - ' ', + ' ', '
', '
', '' diff --git a/configure b/configure index 1b4bd65a85..3847149971 100755 --- a/configure +++ b/configure @@ -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