1 /* UIxMailPartHTMLViewer.m - this file is part of SOGo
3 * Copyright (C) 2007 Inverse groupe conseil
5 * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
7 * This file is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
12 * This file is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; see the file COPYING. If not, write to
19 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
23 #import <Foundation/NSArray.h>
24 #import <Foundation/NSDictionary.h>
25 #import <Foundation/NSKeyValueCoding.h>
26 #import <Foundation/NSValue.h>
28 #import <SaxObjC/SaxAttributes.h>
29 #import <SaxObjC/SaxContentHandler.h>
30 #import <SaxObjC/SaxLexicalHandler.h>
31 #import <SaxObjC/SaxXMLReader.h>
32 #import <SaxObjC/SaxXMLReaderFactory.h>
33 #import <NGExtensions/NSString+misc.h>
34 #import <NGObjWeb/SoObjects.h>
36 #include <libxml/encoding.h>
38 #import <SoObjects/Mailer/SOGoMailObject.h>
40 #import "UIxMailPartHTMLViewer.h"
43 #define showWhoWeAre() NSLog(@"invoked '%@'", NSStringFromSelector(_cmd))
45 #define showWhoWeAre()
48 @interface _UIxHTMLMailContentHandler : NSObject <SaxContentHandler, SaxLexicalHandler>
50 NSMutableString *result;
52 NSDictionary *attachmentIds;
56 BOOL inCSSDeclaration;
58 NSMutableArray *crumb;
59 xmlCharEncoding contentEncoding;
62 - (NSString *) result;
66 @implementation _UIxHTMLMailContentHandler
70 if ((self = [super init]))
76 contentEncoding = XML_CHAR_ENCODING_UTF8;
90 - (void) setContentEncoding: (xmlCharEncoding) newContentEncoding
92 contentEncoding = newContentEncoding;
95 - (xmlCharEncoding) contentEncoding
97 return contentEncoding;
100 - (void) setAttachmentIds: (NSDictionary *) newAttachmentIds
102 attachmentIds = newAttachmentIds;
110 - (NSString *) result
115 /* SaxContentHandler */
116 - (void) startDocument
124 result = [NSMutableString new];
125 css = [NSMutableString new];
126 crumb = [NSMutableArray new];
131 inCSSDeclaration = NO;
137 unsigned int count, max;
143 for (count = max - 1; count > -1; count--)
145 [result appendFormat: @"</%@>", [crumb objectAtIndex: count]];
146 [crumb removeObjectAtIndex: count];
150 - (void) startPrefixMapping: (NSString *)_prefix
151 uri: (NSString *)_uri
156 - (void) endPrefixMapping: (NSString *)_prefix
161 - (void) _appendStyle: (unichar *) _chars
165 unichar *start, *currentChar;
169 for (count = 0; count < _len; count++)
171 currentChar = _chars + count;
172 if (inCSSDeclaration)
174 if (*(char *) currentChar == '}')
176 inCSSDeclaration = NO;
182 if (*(char *) currentChar == '{')
183 inCSSDeclaration = YES;
184 if (*(char *) currentChar == ',')
186 else if (!hasEmbeddedCSS)
188 if (*(char *) currentChar == '@')
189 hasEmbeddedCSS = YES;
191 if (*(char *) currentChar > 32)
193 [css appendString: [NSString stringWithCharacters: start
194 length: (currentChar - start)]];
195 [css appendString: @".SOGoHTMLMail-CSS-Delimiter "];
196 hasEmbeddedCSS = YES;
202 [css appendString: [NSString stringWithCharacters: start
203 length: (currentChar - start)]];
206 - (void) startElement: (NSString *) _localName
207 namespace: (NSString *) _ns
208 rawName: (NSString *) _rawName
209 attributes: (id <SaxAttributes>) _attributes
211 unsigned int count, max;
212 NSString *name, *value, *cid;
213 NSMutableString *resultPart;
217 if (inStyle || inScript)
219 else if ([_localName caseInsensitiveCompare: @"body"] == NSOrderedSame)
221 else if ([_localName caseInsensitiveCompare: @"script"] == NSOrderedSame)
223 else if ([_localName caseInsensitiveCompare: @"style"] == NSOrderedSame)
227 resultPart = [NSMutableString new];
228 [resultPart appendFormat: @"<%@", _rawName];
230 max = [_attributes count];
231 for (count = 0; count < max; count++)
234 name = [_attributes nameAtIndex: count];
235 if ([[name lowercaseString] hasPrefix: @"on"])
237 else if ([name caseInsensitiveCompare: @"src"] == NSOrderedSame)
239 value = [_attributes valueAtIndex: count];
240 if ([value hasPrefix: @"cid:"])
242 cid = [NSString stringWithFormat: @"<%@>",
243 [value substringFromIndex: 4]];
244 value = [attachmentIds objectForKey: cid];
245 skipAttribute = (value == nil);
251 value = [_attributes valueAtIndex: count];
253 [resultPart appendFormat: @" %@=\"%@\"",
254 name, [value stringByReplacingString: @"\""
255 withString: @"\\\""]];
258 [resultPart appendString: @">"];
259 [result appendString: resultPart];
265 [css replaceString: @".SOGoHTMLMail-CSS-Delimiter body"
266 withString: @".SOGoHTMLMail-CSS-Delimiter"];
267 [css replaceString: @";" withString: @" !important;"];
268 [css replaceString: @"<!--" withString: @""];
269 [css replaceString: @"-->" withString: @""];
272 - (void) endElement: (NSString *) _localName
273 namespace: (NSString *) _ns
274 rawName: (NSString *) _rawName
280 if ([_localName caseInsensitiveCompare: @"style"] == NSOrderedSame)
283 inCSSDeclaration = NO;
287 inScript = ([_localName caseInsensitiveCompare: @"script"] != NSOrderedSame);
290 if ([_localName caseInsensitiveCompare: @"body"] == NSOrderedSame)
297 [result appendFormat: @"</%@>", _localName];
301 - (void) characters: (unichar *) _chars
308 [self _appendStyle: _chars length: _len];
313 tmpString = [NSString stringWithCharacters: _chars length: _len];
315 // HACK: This is to avoid appending the useless junk in the <html> tag
316 // that Outlook adds. It seems to confuse the XML parser for
317 // forwarded messages as we get this in the _body_ of the email
318 // while we really aren't in it!
319 if (![tmpString hasPrefix: @" xmlns:v=\"urn:schemas-microsoft-com:vml\""])
320 [result appendString: [tmpString stringByEscapingHTMLString]];
325 - (void) ignorableWhitespace: (unichar *) _chars
331 - (void) processingInstruction: (NSString *) _pi
332 data: (NSString *) _data
337 - (void) setDocumentLocator: (id <NSObject, SaxLocator>) _locator
342 - (void) skippedEntity: (NSString *) _entityName
347 /* SaxLexicalHandler */
348 - (void) comment: (unichar *) _chars
353 [self _appendStyle: _chars length: _len];
356 - (void) startDTD: (NSString *) _name
357 publicId: (NSString *) _pub
358 systemId: (NSString *) _sys
368 - (void) startEntity: (NSString *) _name
373 - (void) endEntity: (NSString *) _name
390 @interface NSDictionary (SOGoDebug)
396 @implementation NSDictionary (SOGoDebug)
402 NSMutableString *dump;
404 dump = [NSMutableString new];
405 [dump appendFormat: @"\nNSDictionary dump (%@):\n", self];
406 keys = [[self allKeys] objectEnumerator];
407 key = [keys nextObject];
410 [dump appendFormat: @"%@: %@\n", key, [self objectForKey: key]];
411 key = [keys nextObject];
413 [dump appendFormat: @"--- end ---\n"];
421 @implementation UIxMailPartHTMLViewer
425 if ((self = [super init]))
439 - (xmlCharEncoding) _xmlCharsetForCharset: (NSString *) charset
441 struct { NSString *name; xmlCharEncoding encoding; } xmlEncodings[] = {
442 { @"us-ascii", XML_CHAR_ENCODING_ASCII},
443 { @"utf-8", XML_CHAR_ENCODING_UTF8},
444 { @"utf-16le", XML_CHAR_ENCODING_UTF16LE},
445 { @"utf-16be", XML_CHAR_ENCODING_UTF16BE},
446 { @"ucs-4le", XML_CHAR_ENCODING_UCS4LE},
447 { @"ucs-4be", XML_CHAR_ENCODING_UCS4BE},
448 { @"ebcdic", XML_CHAR_ENCODING_EBCDIC},
449 // { @"iso-10646" , XML_CHAR_ENCODING_UCS4_2143},
450 // { , XML_CHAR_ENCODING_UCS4_3412},
451 // { @"ucs-2", XML_CHAR_ENCODING_UCS2},
452 { @"iso8859_1", XML_CHAR_ENCODING_8859_1},
453 { @"iso-8859-1", XML_CHAR_ENCODING_8859_1},
454 { @"iso-8859-2", XML_CHAR_ENCODING_8859_2},
455 { @"iso-8859-3", XML_CHAR_ENCODING_8859_3},
456 { @"iso-8859-4", XML_CHAR_ENCODING_8859_4},
457 { @"iso-8859-5", XML_CHAR_ENCODING_8859_5},
458 { @"iso-8859-6", XML_CHAR_ENCODING_8859_6},
459 { @"iso-8859-7", XML_CHAR_ENCODING_8859_7},
460 { @"iso-8859-8", XML_CHAR_ENCODING_8859_8},
461 { @"iso-8859-9", XML_CHAR_ENCODING_8859_9},
462 { @"iso-2022-jp", XML_CHAR_ENCODING_2022_JP},
463 // { @"iso-2022-jp", XML_CHAR_ENCODING_SHIFT_JIS},
464 { @"euc-jp", XML_CHAR_ENCODING_EUC_JP}};
466 xmlCharEncoding encoding;
468 encoding = XML_CHAR_ENCODING_NONE;
471 while (encoding == XML_CHAR_ENCODING_NONE
472 && count < (sizeof (xmlEncodings) / sizeof (xmlEncodings[0])))
473 if ([charset isEqualToString: xmlEncodings[count].name])
474 encoding = xmlEncodings[count].encoding;
478 if (encoding == XML_CHAR_ENCODING_NONE)
479 encoding = XML_CHAR_ENCODING_8859_1;
484 - (xmlCharEncoding) _xmlCharEncoding
489 charset = [[bodyInfo objectForKey:@"parameterList"]
490 objectForKey: @"charset"];
491 if (![charset length])
492 charset = @"us-ascii";
494 return [self _xmlCharsetForCharset: [charset lowercaseString]];
497 - (void) _parseContent
499 NSObject <SaxXMLReader> *parser;
500 NSData *preparsedContent;
501 SOGoMailObject *mail;
503 mail = [self clientObject];
505 preparsedContent = [super decodedFlatContent];
506 parser = [[SaxXMLReaderFactory standardXMLReaderFactory]
507 createXMLReaderForMimeType: @"text/html"];
509 handler = [_UIxHTMLMailContentHandler new];
510 [handler setAttachmentIds: [mail fetchAttachmentIds]];
511 [handler setContentEncoding: [self _xmlCharEncoding]];
512 [parser setContentHandler: handler];
513 [parser parseFromSource: preparsedContent];
516 - (NSString *) cssContent
518 NSString *cssContent, *css;
521 [self _parseContent];
526 = [NSString stringWithFormat: @"<style type=\"text/css\">%@</style>",
534 - (NSString *) flatContentAsString
537 [self _parseContent];
539 return [handler result];