]> err.no Git - scalable-opengroupware.org/blob - UI/MailPartViewers/UIxMailPartHTMLViewer.m
50f79391d77dd51b4e2013aa1e1b164e3fe674ab
[scalable-opengroupware.org] / UI / MailPartViewers / UIxMailPartHTMLViewer.m
1 /* UIxMailPartHTMLViewer.m - this file is part of SOGo
2  *
3  * Copyright (C) 2007 Inverse groupe conseil
4  *
5  * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
6  *
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)
10  * any later version.
11  *
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.
16  *
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.
21  */
22
23 #import <Foundation/NSArray.h>
24 #import <Foundation/NSDictionary.h>
25 #import <Foundation/NSKeyValueCoding.h>
26 #import <Foundation/NSValue.h>
27
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>
35
36 #include <libxml/encoding.h>
37
38 #import "UIxMailPartHTMLViewer.h"
39
40 #if 0
41 #define showWhoWeAre() NSLog(@"invoked '%@'", NSStringFromSelector(_cmd))
42 #else
43 #define showWhoWeAre()
44 #endif
45
46 @interface _UIxHTMLMailContentHandler : NSObject <SaxContentHandler, SaxLexicalHandler>
47 {
48   NSMutableString *result;
49   NSMutableString *css;
50   NSDictionary *attachmentIds;
51   BOOL inBody;
52   BOOL inStyle;
53   BOOL inScript;
54   BOOL inCSSDeclaration;
55   BOOL hasEmbeddedCSS;
56   NSMutableArray *crumb;
57   xmlCharEncoding contentEncoding;
58 }
59
60 - (NSString *) result;
61
62 @end
63
64 @implementation _UIxHTMLMailContentHandler
65
66 - (id) init
67 {
68   if ((self = [super init]))
69     {
70       crumb = nil;
71       css = nil;
72       result = nil;
73       attachmentIds = nil;
74       contentEncoding = XML_CHAR_ENCODING_UTF8;
75     }
76
77   return self;
78 }
79
80 - (void) dealloc
81 {
82   [crumb release];
83   [result release];
84   [css release];
85   [super dealloc];
86 }
87
88 - (void) setContentEncoding: (xmlCharEncoding) newContentEncoding
89 {
90   contentEncoding = newContentEncoding;
91 }
92
93 - (xmlCharEncoding) contentEncoding
94 {
95   return contentEncoding;
96 }
97
98 - (void) setAttachmentIds: (NSDictionary *) newAttachmentIds
99 {
100   attachmentIds = newAttachmentIds;
101 }
102
103 - (NSString *) css
104 {
105   return css;
106 }
107
108 - (NSString *) result
109 {
110   return result;
111 }
112
113 /* SaxContentHandler */
114 - (void) startDocument
115 {
116   showWhoWeAre();
117
118   [crumb release];
119   [css release];
120   [result release];
121
122   result = [NSMutableString new];
123   css = [NSMutableString new];
124   crumb = [NSMutableArray new];
125
126   inBody = NO;
127   inStyle = NO;
128   inScript = NO;
129   inCSSDeclaration = NO;
130   hasEmbeddedCSS = NO;
131 }
132
133 - (void) endDocument
134 {
135   unsigned int count, max;
136
137
138   showWhoWeAre();
139   max = [crumb count];
140   if (max > 0)
141     for (count = max - 1; count > -1; count--)
142       {
143         [result appendFormat: @"</%@>", [crumb objectAtIndex: count]];
144         [crumb removeObjectAtIndex: count];
145       }
146 }
147
148 - (void) startPrefixMapping: (NSString *)_prefix
149                         uri: (NSString *)_uri
150 {
151   showWhoWeAre();
152 }
153
154 - (void) endPrefixMapping: (NSString *)_prefix
155 {
156   showWhoWeAre();
157 }
158
159 - (void) _appendStyle: (unichar *) _chars
160                length: (int) _len
161 {
162   unsigned int count;
163   unichar *start, *currentChar;
164
165   start = _chars;
166   currentChar = start;
167   for (count = 0; count < _len; count++)
168     {
169       currentChar = _chars + count;
170       if (inCSSDeclaration)
171         {
172           if (*(char *) currentChar == '}')
173             {
174               inCSSDeclaration = NO;
175               hasEmbeddedCSS = NO;
176             }
177         }
178       else
179         {
180           if (*(char *) currentChar == '{')
181             inCSSDeclaration = YES;
182           if (*(char *) currentChar == ',')
183             hasEmbeddedCSS = NO;
184           else if (!hasEmbeddedCSS)
185             {
186               if (*(char *) currentChar == '@')
187                 hasEmbeddedCSS = YES;
188               else
189                 if (*(char *) currentChar > 32)
190                   {
191                     [css appendString: [NSString stringWithCharacters: start
192                                                  length: (currentChar - start)]];
193                     [css appendString: @".SOGoHTMLMail-CSS-Delimiter "];
194                     hasEmbeddedCSS = YES;
195                     start = currentChar;
196                   }
197             }
198         }
199     }
200   [css appendString: [NSString stringWithCharacters: start
201                                length: (currentChar - start)]];
202 }
203
204 - (void) startElement: (NSString *) _localName
205             namespace: (NSString *) _ns
206               rawName: (NSString *) _rawName
207            attributes: (id <SaxAttributes>) _attributes
208 {
209   unsigned int count, max;
210   NSString *name, *value;
211   NSMutableString *resultPart;
212   BOOL skipAttribute;
213
214   showWhoWeAre();
215   if (inStyle || inScript)
216     ;
217   else if ([_localName caseInsensitiveCompare: @"body"] == NSOrderedSame)
218     inBody = YES;
219   else if ([_localName caseInsensitiveCompare: @"script"] == NSOrderedSame)
220     inScript = YES;
221   else if ([_localName caseInsensitiveCompare: @"style"] == NSOrderedSame)
222     inStyle = YES;
223   else if (inBody)
224     {
225       resultPart = [NSMutableString new];
226       [resultPart appendFormat: @"<%@", _rawName];
227       
228       max = [_attributes count];
229       for (count = 0; count < max; count++)
230         {
231           skipAttribute = NO;
232           name = [_attributes nameAtIndex: count];
233           if ([[name lowercaseString] hasPrefix: @"on"])
234             skipAttribute = YES;
235           else if ([name caseInsensitiveCompare: @"src"] == NSOrderedSame)
236             {
237               value = [_attributes valueAtIndex: count];
238               if ([value hasPrefix: @"cid:"])
239                 {
240                   value = [attachmentIds
241                             objectForKey: [value substringFromIndex: 4]];
242                   skipAttribute = (value == nil);
243                 }
244               else
245                 skipAttribute = YES;
246             }
247           else
248             value = [_attributes valueAtIndex: count];
249           if (!skipAttribute)
250             [resultPart appendFormat: @" %@=\"%@\"",
251                         name, [value stringByReplacingString: @"\""
252                                      withString: @"\\\""]];
253         }
254
255       [resultPart appendString: @">"];
256       [result appendString: resultPart];
257     }
258 }
259
260 - (void) _finishCSS
261 {
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: @""];
267 }
268
269 - (void) endElement: (NSString *) _localName
270           namespace: (NSString *) _ns
271             rawName: (NSString *) _rawName
272 {
273   showWhoWeAre();
274
275   if (inStyle)
276     {
277      if ([_localName caseInsensitiveCompare: @"style"] == NSOrderedSame)
278        {
279          inStyle = NO;
280          inCSSDeclaration = NO;
281        }
282     }
283   else if (inScript)
284     inScript = ([_localName caseInsensitiveCompare: @"script"] != NSOrderedSame);
285   else if (inBody)
286     {
287       if ([_localName caseInsensitiveCompare: @"body"] == NSOrderedSame)
288         {
289           inBody = NO;
290           if (css)
291             [self _finishCSS];
292         }
293       else
294         [result appendFormat: @"</%@>", _localName];
295     }
296 }
297
298 - (void) characters: (unichar *) _chars
299              length: (int) _len
300 {
301   NSString *tmpString;
302
303   showWhoWeAre();
304   if (!inScript)
305     {
306       if (inStyle)
307         [self _appendStyle: _chars length: _len];
308       else if (inBody)
309         {
310           tmpString = [NSString stringWithCharacters: _chars length: _len];
311           [result appendString: [tmpString stringByEscapingHTMLString]];
312         }
313     }
314 }
315
316 - (void) ignorableWhitespace: (unichar *) _chars
317                       length: (int) _len
318 {
319   showWhoWeAre();
320 }
321
322 - (void) processingInstruction: (NSString *) _pi
323                           data: (NSString *) _data
324 {
325   showWhoWeAre();
326 }
327
328 - (void) setDocumentLocator: (id <NSObject, SaxLocator>) _locator
329 {
330   showWhoWeAre();
331 }
332
333 - (void) skippedEntity: (NSString *) _entityName
334 {
335   showWhoWeAre();
336 }
337
338 /* SaxLexicalHandler */
339 - (void) comment: (unichar *) _chars
340           length: (int) _len
341 {
342   showWhoWeAre();
343   if (inStyle)
344     [self _appendStyle: _chars length: _len];
345 }
346
347 - (void) startDTD: (NSString *) _name
348          publicId: (NSString *) _pub
349          systemId: (NSString *) _sys
350 {
351   showWhoWeAre();
352 }
353
354 - (void) endDTD
355 {
356   showWhoWeAre();
357 }
358
359 - (void) startEntity: (NSString *) _name
360 {
361   showWhoWeAre();
362 }
363
364 - (void) endEntity: (NSString *) _name
365 {
366   showWhoWeAre();
367 }
368
369 - (void) startCDATA
370 {
371   showWhoWeAre();
372 }
373
374 - (void) endCDATA
375 {
376   showWhoWeAre();
377 }
378
379 @end
380
381 @interface NSDictionary (SOGoDebug)
382
383 - (void) dump;
384
385 @end
386
387 @implementation NSDictionary (SOGoDebug)
388
389 - (void) dump
390 {
391   NSEnumerator *keys;
392   NSString *key;
393   NSMutableString *dump;
394
395   dump = [NSMutableString new];
396   [dump appendFormat: @"\nNSDictionary dump (%@):\n", self];
397   keys = [[self allKeys] objectEnumerator];
398   key = [keys nextObject];
399   while (key)
400     {
401       [dump appendFormat: @"%@: %@\n", key, [self objectForKey: key]];
402       key = [keys nextObject];
403     }
404   [dump appendFormat: @"--- end ---\n"];
405
406   NSLog (dump);
407   [dump release];
408 }
409
410 @end
411
412 @implementation UIxMailPartHTMLViewer
413
414 - (id) init
415 {
416   if ((self = [super init]))
417     {
418       handler = nil;
419     }
420
421   return self;
422 }
423
424 - (void) dealloc
425 {
426   [handler release];
427   [super dealloc];
428 }
429
430 - (void) _convertReferencesForPart: (NSDictionary *) part
431                          withCount: (unsigned int) count
432                         andBaseURL: (NSString *) url
433                     intoDictionary: (NSMutableDictionary *) attachmentIds
434 {
435   NSString *bodyId, *filename;
436   NSMutableString *attachmentURL;
437
438   bodyId = [part objectForKey: @"bodyId"];
439   if ([bodyId length] > 0)
440     {
441       filename = [[part objectForKey: @"parameterList"] objectForKey: @"name"];
442       if (!filename)
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];
455     }
456 }
457
458 - (NSDictionary *) _attachmentIds
459 {
460   NSMutableDictionary *attachmentIds;
461   UIxMailPartViewer *parent;
462   unsigned int count, max;
463 //   NSMutableString *url;
464   NSString *baseURL;
465   NSArray *parts;
466
467   attachmentIds = [NSMutableDictionary new];
468   [attachmentIds autorelease];
469   
470   parent = [self parent];
471   if ([NSStringFromClass ([parent class])
472                          isEqualToString: @"UIxMailPartAlternativeViewer"])
473     {
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"];
480       max = [parts count];
481       for (count = 0; count < max; count++)
482         [self _convertReferencesForPart: [parts objectAtIndex: count]
483               withCount: count + 1
484               andBaseURL: baseURL
485               intoDictionary: attachmentIds];
486 //       [url release];
487     }
488
489   return attachmentIds;
490 }
491
492 - (xmlCharEncoding) _xmlCharsetForCharset: (NSString *) charset
493 {
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}};
518   unsigned count;
519   xmlCharEncoding encoding;
520
521   encoding = XML_CHAR_ENCODING_NONE;
522   count = 0;
523
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;
528     else
529       count++;
530
531   if (encoding == XML_CHAR_ENCODING_NONE)
532     encoding = XML_CHAR_ENCODING_8859_1;
533
534   return encoding;
535 }
536
537 - (xmlCharEncoding) _xmlCharEncoding
538 {
539
540   NSString *charset;
541
542   charset = [[bodyInfo objectForKey:@"parameterList"]
543               objectForKey: @"charset"];
544   if (![charset length])
545     charset = @"us-ascii";
546   
547   return [self _xmlCharsetForCharset: [charset lowercaseString]];
548 }
549
550 - (void) _parseContent
551 {
552   NSObject <SaxXMLReader> *parser;
553   NSData *preparsedContent;
554
555   preparsedContent = [super decodedFlatContent];
556   parser = [[SaxXMLReaderFactory standardXMLReaderFactory]
557              createXMLReaderForMimeType: @"text/html"];
558
559   handler = [_UIxHTMLMailContentHandler new];
560   [handler setAttachmentIds: [self _attachmentIds]];
561   [handler setContentEncoding: [self _xmlCharEncoding]];
562   [parser setContentHandler: handler];
563   [parser parseFromSource: preparsedContent];
564 }
565
566 - (NSString *) cssContent
567 {
568   NSString *cssContent, *css;
569
570   if (!handler)
571     [self _parseContent];
572
573   css = [handler css];
574   if ([css length])
575     cssContent
576       = [NSString stringWithFormat: @"<style type=\"text/css\">%@</style>",
577                   [handler css]];
578   else
579     cssContent = @"";
580
581   return cssContent;
582 }
583
584 - (NSString *) flatContentAsString
585 {
586   if (!handler)
587     [self _parseContent];
588
589   return [handler result];
590 }
591
592 @end