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 "UIxMailPartHTMLViewer.h"
41 #define showWhoWeAre() NSLog(@"invoked '%@'", NSStringFromSelector(_cmd))
43 #define showWhoWeAre()
46 @interface _UIxHTMLMailContentHandler : NSObject <SaxContentHandler, SaxLexicalHandler>
48 NSMutableString *result;
50 NSDictionary *attachmentIds;
54 BOOL inCSSDeclaration;
56 NSMutableArray *crumb;
57 xmlCharEncoding contentEncoding;
60 - (NSString *) result;
64 @implementation _UIxHTMLMailContentHandler
68 if ((self = [super init]))
74 contentEncoding = XML_CHAR_ENCODING_UTF8;
88 - (void) setContentEncoding: (xmlCharEncoding) newContentEncoding
90 contentEncoding = newContentEncoding;
93 - (xmlCharEncoding) contentEncoding
95 return contentEncoding;
98 - (void) setAttachmentIds: (NSDictionary *) newAttachmentIds
100 attachmentIds = newAttachmentIds;
108 - (NSString *) result
113 /* SaxContentHandler */
114 - (void) startDocument
122 result = [NSMutableString new];
123 css = [NSMutableString new];
124 crumb = [NSMutableArray new];
129 inCSSDeclaration = NO;
135 unsigned int count, max;
141 for (count = max - 1; count > -1; count--)
143 [result appendFormat: @"</%@>", [crumb objectAtIndex: count]];
144 [crumb removeObjectAtIndex: count];
148 - (void) startPrefixMapping: (NSString *)_prefix
149 uri: (NSString *)_uri
154 - (void) endPrefixMapping: (NSString *)_prefix
159 - (void) _appendStyle: (unichar *) _chars
163 unichar *start, *currentChar;
167 for (count = 0; count < _len; count++)
169 currentChar = _chars + count;
170 if (inCSSDeclaration)
172 if (*(char *) currentChar == '}')
174 inCSSDeclaration = NO;
180 if (*(char *) currentChar == '{')
181 inCSSDeclaration = YES;
182 if (*(char *) currentChar == ',')
184 else if (!hasEmbeddedCSS)
186 if (*(char *) currentChar == '@')
187 hasEmbeddedCSS = YES;
189 if (*(char *) currentChar > 32)
191 [css appendString: [NSString stringWithCharacters: start
192 length: (currentChar - start)]];
193 [css appendString: @".SOGoHTMLMail-CSS-Delimiter "];
194 hasEmbeddedCSS = YES;
200 [css appendString: [NSString stringWithCharacters: start
201 length: (currentChar - start)]];
204 - (void) startElement: (NSString *) _localName
205 namespace: (NSString *) _ns
206 rawName: (NSString *) _rawName
207 attributes: (id <SaxAttributes>) _attributes
209 unsigned int count, max;
210 NSString *name, *value;
211 NSMutableString *resultPart;
215 if (inStyle || inScript)
217 else if ([_localName caseInsensitiveCompare: @"body"] == NSOrderedSame)
219 else if ([_localName caseInsensitiveCompare: @"script"] == NSOrderedSame)
221 else if ([_localName caseInsensitiveCompare: @"style"] == NSOrderedSame)
225 resultPart = [NSMutableString new];
226 [resultPart appendFormat: @"<%@", _rawName];
228 max = [_attributes count];
229 for (count = 0; count < max; count++)
232 name = [_attributes nameAtIndex: count];
233 if ([[name lowercaseString] hasPrefix: @"on"])
235 else if ([name caseInsensitiveCompare: @"src"] == NSOrderedSame)
237 value = [_attributes valueAtIndex: count];
238 if ([value hasPrefix: @"cid:"])
240 value = [attachmentIds
241 objectForKey: [value substringFromIndex: 4]];
242 skipAttribute = (value == nil);
248 value = [_attributes valueAtIndex: count];
250 [resultPart appendFormat: @" %@=\"%@\"",
251 name, [value stringByReplacingString: @"\""
252 withString: @"\\\""]];
255 [resultPart appendString: @">"];
256 [result appendString: resultPart];
262 [css replaceString: @".SOGoHTMLMail-CSS-Delimiter body"
263 withString: @".SOGoHTMLMail-CSS-Delimiter"];
264 [css replaceString: @";" withString: @" !important;"];
265 [css replaceString: @"<!--" withString: @""];
266 [css replaceString: @"-->" withString: @""];
269 - (void) endElement: (NSString *) _localName
270 namespace: (NSString *) _ns
271 rawName: (NSString *) _rawName
277 if ([_localName caseInsensitiveCompare: @"style"] == NSOrderedSame)
280 inCSSDeclaration = NO;
284 inScript = ([_localName caseInsensitiveCompare: @"script"] != NSOrderedSame);
287 if ([_localName caseInsensitiveCompare: @"body"] == NSOrderedSame)
294 [result appendFormat: @"</%@>", _localName];
298 - (void) characters: (unichar *) _chars
307 [self _appendStyle: _chars length: _len];
310 tmpString = [NSString stringWithCharacters: _chars length: _len];
311 [result appendString: [tmpString stringByEscapingHTMLString]];
316 - (void) ignorableWhitespace: (unichar *) _chars
322 - (void) processingInstruction: (NSString *) _pi
323 data: (NSString *) _data
328 - (void) setDocumentLocator: (id <NSObject, SaxLocator>) _locator
333 - (void) skippedEntity: (NSString *) _entityName
338 /* SaxLexicalHandler */
339 - (void) comment: (unichar *) _chars
344 [self _appendStyle: _chars length: _len];
347 - (void) startDTD: (NSString *) _name
348 publicId: (NSString *) _pub
349 systemId: (NSString *) _sys
359 - (void) startEntity: (NSString *) _name
364 - (void) endEntity: (NSString *) _name
381 @interface NSDictionary (SOGoDebug)
387 @implementation NSDictionary (SOGoDebug)
393 NSMutableString *dump;
395 dump = [NSMutableString new];
396 [dump appendFormat: @"\nNSDictionary dump (%@):\n", self];
397 keys = [[self allKeys] objectEnumerator];
398 key = [keys nextObject];
401 [dump appendFormat: @"%@: %@\n", key, [self objectForKey: key]];
402 key = [keys nextObject];
404 [dump appendFormat: @"--- end ---\n"];
412 @implementation UIxMailPartHTMLViewer
416 if ((self = [super init]))
430 - (void) _convertReferencesForPart: (NSDictionary *) part
431 withCount: (unsigned int) count
432 andBaseURL: (NSString *) url
433 intoDictionary: (NSMutableDictionary *) attachmentIds
435 NSString *bodyId, *filename;
436 NSMutableString *attachmentURL;
438 bodyId = [part objectForKey: @"bodyId"];
439 if ([bodyId length] > 0)
441 filename = [[part objectForKey: @"parameterList"] objectForKey: @"name"];
443 filename = [[[part objectForKey: @"disposition"]
444 objectForKey: @"parameterList"]
445 objectForKey: @"filename"];
446 if ([bodyId hasPrefix: @"<"])
447 bodyId = [bodyId substringFromIndex: 1];
448 if ([bodyId hasSuffix: @">"])
449 bodyId = [bodyId substringToIndex: [bodyId length] - 1];
450 attachmentURL = [NSMutableString stringWithString: url];
451 [attachmentURL appendFormat: @"/%d", count];
452 if ([filename length])
453 [attachmentURL appendFormat: @"/%@", filename];
454 [attachmentIds setObject: attachmentURL forKey: bodyId];
458 - (NSDictionary *) _attachmentIds
460 NSMutableDictionary *attachmentIds;
461 UIxMailPartViewer *parent;
462 unsigned int count, max;
463 // NSMutableString *url;
467 attachmentIds = [NSMutableDictionary new];
468 [attachmentIds autorelease];
470 parent = [self parent];
471 if ([NSStringFromClass ([parent class])
472 isEqualToString: @"UIxMailPartAlternativeViewer"])
474 baseURL = [[self clientObject] baseURLInContext: context];
475 // url = [NSMutableString new];
476 // [url appendString: baseURL];
477 // [url appendFormat: @"/%@", [partPath componentsJoinedByString: @"/"]];
478 // [url deleteCharactersInRange: NSMakeRange([url length] - 4, 4)];
479 parts = [[[parent parent] bodyInfo] objectForKey: @"parts"];
481 for (count = 0; count < max; count++)
482 [self _convertReferencesForPart: [parts objectAtIndex: count]
485 intoDictionary: attachmentIds];
489 return attachmentIds;
492 - (xmlCharEncoding) _xmlCharsetForCharset: (NSString *) charset
494 struct { NSString *name; xmlCharEncoding encoding; } xmlEncodings[] = {
495 { @"us-ascii", XML_CHAR_ENCODING_ASCII},
496 { @"utf-8", XML_CHAR_ENCODING_UTF8},
497 { @"utf-16le", XML_CHAR_ENCODING_UTF16LE},
498 { @"utf-16be", XML_CHAR_ENCODING_UTF16BE},
499 { @"ucs-4le", XML_CHAR_ENCODING_UCS4LE},
500 { @"ucs-4be", XML_CHAR_ENCODING_UCS4BE},
501 { @"ebcdic", XML_CHAR_ENCODING_EBCDIC},
502 // { @"iso-10646" , XML_CHAR_ENCODING_UCS4_2143},
503 // { , XML_CHAR_ENCODING_UCS4_3412},
504 // { @"ucs-2", XML_CHAR_ENCODING_UCS2},
505 { @"iso8859_1", XML_CHAR_ENCODING_8859_1},
506 { @"iso-8859-1", XML_CHAR_ENCODING_8859_1},
507 { @"iso-8859-2", XML_CHAR_ENCODING_8859_2},
508 { @"iso-8859-3", XML_CHAR_ENCODING_8859_3},
509 { @"iso-8859-4", XML_CHAR_ENCODING_8859_4},
510 { @"iso-8859-5", XML_CHAR_ENCODING_8859_5},
511 { @"iso-8859-6", XML_CHAR_ENCODING_8859_6},
512 { @"iso-8859-7", XML_CHAR_ENCODING_8859_7},
513 { @"iso-8859-8", XML_CHAR_ENCODING_8859_8},
514 { @"iso-8859-9", XML_CHAR_ENCODING_8859_9},
515 { @"iso-2022-jp", XML_CHAR_ENCODING_2022_JP},
516 // { @"iso-2022-jp", XML_CHAR_ENCODING_SHIFT_JIS},
517 { @"euc-jp", XML_CHAR_ENCODING_EUC_JP}};
519 xmlCharEncoding encoding;
521 encoding = XML_CHAR_ENCODING_NONE;
524 while (encoding == XML_CHAR_ENCODING_NONE
525 && count < (sizeof (xmlEncodings) / sizeof (xmlEncodings[0])))
526 if ([charset isEqualToString: xmlEncodings[count].name])
527 encoding = xmlEncodings[count].encoding;
531 if (encoding == XML_CHAR_ENCODING_NONE)
532 encoding = XML_CHAR_ENCODING_8859_1;
537 - (xmlCharEncoding) _xmlCharEncoding
542 charset = [[bodyInfo objectForKey:@"parameterList"]
543 objectForKey: @"charset"];
544 if (![charset length])
545 charset = @"us-ascii";
547 return [self _xmlCharsetForCharset: [charset lowercaseString]];
550 - (void) _parseContent
552 NSObject <SaxXMLReader> *parser;
553 NSData *preparsedContent;
555 preparsedContent = [super decodedFlatContent];
556 parser = [[SaxXMLReaderFactory standardXMLReaderFactory]
557 createXMLReaderForMimeType: @"text/html"];
559 handler = [_UIxHTMLMailContentHandler new];
560 [handler setAttachmentIds: [self _attachmentIds]];
561 [handler setContentEncoding: [self _xmlCharEncoding]];
562 [parser setContentHandler: handler];
563 [parser parseFromSource: preparsedContent];
566 - (NSString *) cssContent
568 NSString *cssContent, *css;
571 [self _parseContent];
576 = [NSString stringWithFormat: @"<style type=\"text/css\">%@</style>",
584 - (NSString *) flatContentAsString
587 [self _parseContent];
589 return [handler result];