"Fossies" - the Fresh Open Source Software Archive

Member "opengroupware-5.5rc3/WebUI/Mailer/OGoWebMail/LSWImapMailViewer.m" (5 Dec 2015, 40758 Bytes) of package /linux/privat/opengroupware-5.5rc3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Matlab source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "LSWImapMailViewer.m" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 5.5rc2_vs_5.5rc3.

    1 /*
    2   Copyright (C) 2000-2007 SKYRIX Software AG
    3 
    4   This file is part of OpenGroupware.org.
    5 
    6   OGo is free software; you can redistribute it and/or modify it under
    7   the terms of the GNU Lesser General Public License as published by the
    8   Free Software Foundation; either version 2, or (at your option) any
    9   later version.
   10 
   11   OGo is distributed in the hope that it will be useful, but WITHOUT ANY
   12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
   14   License for more details.
   15 
   16   You should have received a copy of the GNU Lesser General Public
   17   License along with OGo; see the file COPYING.  If not, write to the
   18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
   19   02111-1307, USA.
   20 */
   21 
   22 #include "LSWImapMailViewer.h"
   23 #include "common.h"
   24 #include "LSWImapMailMove.h"
   25 #include "SkyImapMailListState.h"
   26 #include <NGObjWeb/WEClientCapabilities.h>
   27 #include "SkyImapMailRestrictions.h"
   28 
   29 // TODO: this class is way too large - split up!
   30 
   31 #define MAX_LENGTH 100
   32 
   33 @interface NSObject(PRIVATE)
   34 - (void)setMessages:(NSArray *)_mes;
   35 @end
   36 
   37 @interface LSWImapMailViewer(PrivateMethods)
   38 - (NGMimeMessage *)_mdnMessage;
   39 - (id)showMail:(BOOL)_next;
   40 - (id)showMail:(BOOL)_next unread:(BOOL)_unread alsoOther:(BOOL)_alsoOthers;
   41 - (void)resetUrlState;
   42 @end /* LSWImapMailViewer(MDNPrivateMethods) */
   43 
   44 @implementation LSWImapMailViewer
   45 
   46 static NSString   *FileNameDateFmt         = @"%Y-%m-%d_%H:%M.mail";
   47 static int        UseOldMailSourceSubject  = -1;
   48 static int        BodyStructureInViewerBoundary = -1;
   49 static int        NoBodyStructureInViewer  = -1;
   50 static int        MaxSubjectLength         = -1;
   51 static NGMimeType *multipartReportType     = nil;
   52 static NGMimeType *textRfc822HeadersType   = nil;
   53 static NGMimeType *textPlainType           = nil;
   54 static NGMimeType *msgDispositionNotiType  = nil;
   55 static NGMimeType *objcMessageType         = nil;
   56 static NSString   *xMailer                 = @"OpenGroupware.org 0.9";
   57 static NSString   *winJSSizing = @"width=100,height=100,left=10,top=10";
   58 static NSString   *sendDateDateFmt = @"%Y-%m-%d %H:%M";
   59 
   60 + (void)initialize {
   61   // TODO: check superclass version
   62   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
   63   NSDictionary *paras;
   64   static BOOL didInit = NO;
   65   if (didInit) return;
   66   didInit = YES;
   67   
   68   UseOldMailSourceSubject = [ud boolForKey:@"UseOldMailSourceSubject"]?1:0;
   69   NoBodyStructureInViewer = [ud boolForKey:@"NoBodyStructureInViewer"]?1:0;
   70   
   71   BodyStructureInViewerBoundary =
   72     [ud integerForKey:@"BodyStructureInViewerBoundary"];
   73   if (BodyStructureInViewerBoundary < 100)
   74     BodyStructureInViewerBoundary = 5000;
   75 
   76   if ((MaxSubjectLength = [ud integerForKey:@"mail_maxSubjectLenght"]) < 5)
   77     MaxSubjectLength = 30;
   78 
   79   paras = [NSDictionary dictionaryWithObjectsAndKeys:
   80                           @"disposition-notification", @"report-type", nil];
   81   multipartReportType = 
   82     [[NGMimeType mimeType:@"multipart" subType:@"report" parameters:paras]
   83                  retain];
   84   textRfc822HeadersType = 
   85     [[NGMimeType mimeType:@"text" subType:@"rfc822-headers"] retain];
   86   textPlainType = [[NGMimeType mimeType:@"text" subType:@"plain"] retain];
   87   msgDispositionNotiType = 
   88     [[NGMimeType mimeType:@"message" subType:@"disposition-notification"] 
   89                  retain];
   90   objcMessageType = 
   91     [[NGMimeType mimeType:@"objc" subType:@"NGImap4Message"] retain];
   92 }
   93 
   94 - (id)init {
   95   if ((self = [super init])) {
   96     self->url = [[LSWMailViewerURLState alloc] init];
   97 
   98     [self setTabKey:@"mail"];
   99     [self setIsInWarningMode:NO];
  100   }
  101   return self;
  102 }
  103 
  104 - (void)dealloc {
  105   // TODO: it is *MORE* than questionable to access the session in -dealloc!
  106   [(WOSession *)[self session] 
  107                 removeObjectForKey:@"displayedAttachmentDownloadUrls"];
  108   
  109   [self resetUrlState]; /* Note: this *must* be run prior releasing the url..*/
  110   [self->url                       release];
  111   
  112   [self->bcc                       release];
  113   [self->cc                        release];
  114   [self->dispositionNotificationTo release];
  115   [self->emailContent              release];
  116   [self->imapContext               release];
  117   [self->mailDS                    release];
  118   [self->mailSource                release];
  119   [self->mailSourceString          release];
  120   [self->state                     release];
  121   [self->tabKey                    release];
  122   [self->to                        release];
  123   [self->downloadAllItem           release];
  124   [self->downloadAllObjs           release];
  125   [super dealloc];
  126 }
  127 
  128 - (NSString *)_receiverForHeaderFieldWithName:(NSString *)_fieldName {
  129   NSMutableString *receiver;
  130   NSString        *str;
  131   NSEnumerator    *enumerator;
  132   
  133   receiver   = [NSMutableString stringWithCapacity:32];
  134   enumerator = [self->emailContent valuesOfHeaderFieldWithName:_fieldName];
  135   
  136   if ((str = [enumerator nextObject]) != nil)
  137     [receiver setString:str];
  138   else
  139     return nil;
  140   
  141   while ((str = [enumerator nextObject]) != nil) {
  142     [receiver appendString:@", "];
  143     [receiver appendString:str];
  144   }
  145   return receiver;
  146 }
  147 
  148 - (void)initRawData {
  149   NGMimeMessageParser *parser;
  150 
  151   parser = [[NGMimeMessageParser alloc] init];
  152 
  153   ASSIGN(self->mailSource,   [[self object] rawData]);
  154   ASSIGN(self->emailContent, [parser parsePartFromData:self->mailSource]);
  155   
  156   [parser release]; parser = nil;
  157 }
  158 
  159 /* response generation */
  160 
  161 - (void)appendToResponse:(WOResponse *)_response 
  162   inContext:(WOContext *)_context 
  163 {
  164   WOSession *sn;
  165 
  166   sn = (WOSession *)[self session];
  167   if ([[[[sn context] request] clientCapabilities] doesSupportUTF8Encoding])
  168     [_response setContentEncoding:NSUTF8StringEncoding];
  169   
  170   [sn takeValue:[NSMutableArray arrayWithCapacity:4] 
  171       forKey:@"displayedAttachmentDownloadUrls"];
  172   
  173   [super appendToResponse:_response inContext:_context];
  174 }
  175 
  176 /* activation */
  177 
  178 - (void)_loadReceiverInfoFromContent {
  179   ASSIGN(self->to,  [self _receiverForHeaderFieldWithName:@"to"]);
  180   ASSIGN(self->cc,  [self _receiverForHeaderFieldWithName:@"cc"]);
  181   ASSIGN(self->bcc, [self _receiverForHeaderFieldWithName:@"bcc"]);
  182 }
  183 
  184 - (void)_setupMessageDispositionInfo {
  185     NSEnumerator   *e;
  186     id             one;
  187     NSMutableArray *mdn;
  188 
  189     e   = [self->emailContent valuesOfHeaderFieldWithName:
  190                                @"disposition-notification-to"];
  191     mdn = [NSMutableArray array];
  192 
  193     while ((one = [e nextObject])) 
  194       [mdn addObject:one];
  195     
  196     if ([mdn count] > 1) {
  197       [self logWithFormat:
  198           @"%s INVALID Disposition-Notification-To - header in mail %@"
  199               @" .. ignoring",
  200               __PRETTY_FUNCTION__, [self object]];
  201     }
  202     else if ([mdn count] == 1) {
  203         id      tmp;
  204     NSRange r;
  205         
  206         tmp                      = [mdn lastObject];
  207         self->askToReturnReceipt = YES;
  208     
  209         /* filtering address */
  210       
  211     r = [tmp rangeOfString:@"<"];
  212     if (r.length > 0) {
  213             tmp = [tmp substringFromIndex:r.location + r.length];
  214             r = [tmp rangeOfString:@">"];
  215             if (r.length > 0)
  216               tmp = [tmp substringToIndex:r.location];
  217         }
  218         ASSIGN(self->dispositionNotificationTo, tmp);
  219     }
  220 }
  221 
  222 - (void)_loadAccountDefaults {
  223   NSUserDefaults *ud;
  224 
  225   ud                  = [[self session] userDefaults];
  226   self->isCcCollapsed = [ud boolForKey:@"mail_is_cc_collapsed"];
  227   self->isToCollapsed = [ud boolForKey:@"mail_is_to_collapsed"];
  228 }
  229 
  230 - (BOOL)prepareForActivationCommand:(NSString *)_command
  231   type:(NGMimeType *)_type
  232   configuration:(NSDictionary *)_cmdCfg
  233 {
  234   id email;
  235   
  236   if (![super prepareForActivationCommand:_command
  237           type:_type configuration:_cmdCfg])
  238     return NO;
  239     
  240   email = [self object];
  241   NSAssert(email, @"no email is sets !");
  242 
  243   ASSIGN(self->imapContext, [(NGImap4Message *)email context]);
  244   ASSIGN(self->mailSource, nil);
  245     
  246   if ([email size] < BodyStructureInViewerBoundary || NoBodyStructureInViewer)
  247     [self initRawData];
  248   else {
  249     ASSIGN(self->emailContent, [email bodyStructure]);
  250   }
  251 
  252   if (self->emailContent == nil) {
  253     [self setErrorString:@"no content in email !"];
  254     return NO;
  255   }
  256   
  257   [self _loadReceiverInfoFromContent];
  258   [self _setupMessageDispositionInfo];
  259   [self _loadAccountDefaults];
  260   return YES;
  261 }
  262 
  263 - (void)resetUrlState {
  264   [self->url reset];
  265 }
  266 
  267 /* defaults */
  268 
  269 - (NSUserDefaults *)userDefaults {
  270   return [[self existingSession] userDefaults];
  271 }
  272 - (BOOL)shouldGoToNextMessageAfterDelete {
  273   return [[self userDefaults] boolForKey:@"mail_nextMesgAfterDelete"];
  274 }
  275 - (BOOL)shouldShowNextUnreadMessageAsNext {
  276   return [[self userDefaults] boolForKey:@"mail_showUnreadMesgAsNext"];
  277 }
  278 - (NSString *)mailMDNType {
  279   return [[self userDefaults] stringForKey:@"mail_MDN_type"];
  280 }
  281 - (NSString *)mailMDNText {
  282   return [[self userDefaults] stringForKey:@"mail_MDN_text"];
  283 }
  284 - (NSString *)mailMDNSubject {
  285   return [[self userDefaults] stringForKey:@"mail_MDN_subject"];
  286 }
  287 
  288 /* notifications */
  289 
  290 - (void)syncAwake {
  291   [super syncAwake];
  292 
  293   [self resetUrlState];
  294 
  295   if ((self->askToReturnReceipt) && (self->messageWasUnread)) {
  296     NSString *mdnType;
  297 
  298     mdnType = [self mailMDNType];
  299     
  300     // three types: automatic, ask, never
  301     if (mdnType == nil)
  302       mdnType = @"ask";
  303 
  304     if ([mdnType isEqualToString:@"never"])
  305       self->askToReturnReceipt = NO;
  306     else if ([mdnType isEqualToString:@"automatic"]) {
  307       [self sendMDN];
  308       self->askToReturnReceipt = NO;
  309     }
  310     else if ([mdnType isEqualToString:@"ask"]) {
  311       // just ask
  312       [self setIsInWarningMode:NO];
  313       self->askToReturnReceipt = YES;
  314     }
  315     else {
  316       [self logWithFormat:
  317           @"%s unknown message disposition notification type: %@",
  318               __PRETTY_FUNCTION__, mdnType];
  319     }
  320     // no more checks
  321     self->messageWasUnread = NO;
  322   }
  323   else
  324     // no more questions
  325     self->askToReturnReceipt = NO;
  326 }
  327 
  328 - (void)syncSleep {
  329   NSUserDefaults *ud;
  330   
  331   /* store defaults */
  332   ud = [self userDefaults];
  333   [ud setBool:self->isCcCollapsed forKey:@"is_cc_collapsed"];
  334   [ud setBool:self->isToCollapsed forKey:@"is_to_collapsed"];
  335   [ud synchronize];
  336 
  337   [self resetUrlState];
  338   [self->downloadAllObjs release]; self->downloadAllObjs = nil;
  339   [super syncSleep];
  340 }
  341 
  342 /* accessors */
  343 
  344 - (void)setTabKey:(NSString *)_key {
  345   ASSIGN(self->tabKey, _key);
  346 }
  347 - (NSString *)tabKey {
  348   return self->tabKey;
  349 }
  350 
  351 - (BOOL)askToReturnReceipt {
  352   return self->askToReturnReceipt;
  353 }
  354 
  355 - (id)emailContent {
  356   return self->emailContent;
  357 }
  358 
  359 - (NSString *)subject {
  360   NSString *str;
  361 
  362   str = [[self object] valueForKey:@"subject"];
  363   if (![str isNotNull])
  364     return @"";
  365   
  366   if ([str length] < MaxSubjectLength)
  367     return str;
  368   
  369   return [[str substringToIndex:MaxSubjectLength]
  370                stringByAppendingString:@".."];
  371 }
  372 
  373 
  374 - (NSString *)to {
  375   if ([self->to length] < MAX_LENGTH) return self->to;
  376 
  377   if (self->isToCollapsed) { // TODO: category!
  378     return [[self->to substringToIndex:MAX_LENGTH]
  379                       stringByAppendingString:@".."];
  380   }
  381   return self->to;
  382 }
  383 
  384 
  385 - (NSString *)cc {
  386   if ([self->cc length] < MAX_LENGTH) return self->cc;
  387 
  388   if (self->isCcCollapsed) { // TODO: category!
  389     return [[self->cc substringToIndex:MAX_LENGTH]
  390                       stringByAppendingString:@".."];
  391   }
  392   return self->cc;
  393 }
  394 
  395 - (NSString *)bcc {
  396   return self->bcc;
  397 }
  398 
  399 - (NSCalendarDate *)sendDate {
  400   NSCalendarDate *result;
  401   
  402   result = [[self object] valueForKey:@"sendDate"];
  403 
  404   if ([result respondsToSelector:@selector(setTimeZone:)])
  405     [result setTimeZone:[[self session] timeZone]];
  406   
  407   return result;
  408 }
  409 
  410 /* to and cc collapsing */
  411 
  412 - (BOOL)showToCollapser {
  413   if ([self->to length] < MAX_LENGTH) return NO;
  414   
  415   return !self->isToCollapsed;
  416 }
  417 - (BOOL)showToExpander {
  418   if ([self->to length] < MAX_LENGTH) return NO;
  419   
  420   return self->isToCollapsed;
  421 }
  422 
  423 - (BOOL)showCcCollapser {
  424   if ([self->cc length] < MAX_LENGTH) return NO;
  425   
  426   return !self->isCcCollapsed;
  427 }
  428 - (BOOL)showCcExpander {
  429   if ([self->cc length] < MAX_LENGTH) return NO;
  430   
  431   return self->isCcCollapsed;
  432 }
  433 
  434 - (id)expandTo {
  435   self->isToCollapsed = NO;
  436   return nil;
  437 }
  438 - (id)collapseTo {
  439   self->isToCollapsed = YES;
  440   return nil;
  441 }
  442 - (id)expandCc {
  443   self->isCcCollapsed = NO;
  444   return nil;
  445 }
  446 - (id)collapseCc {
  447   self->isCcCollapsed = YES;
  448   return nil;
  449 }
  450 
  451 - (BOOL)hasCC {
  452   return (self->cc != nil && [self->cc length] > 0) ? YES : NO;
  453 }
  454 
  455 - (NSData *)mailSource {
  456   if (self->mailSource == nil)
  457     [self initRawData];
  458   
  459   return self->mailSource;
  460 }
  461 
  462 - (NSString *)mailSourceString {
  463   if (self->mailSourceString == nil) {
  464     NSData *data;
  465 
  466     data = [self mailSource];
  467 
  468     self->mailSourceString =
  469       [[NSString alloc] initWithData:data
  470                         encoding:[NSString defaultCStringEncoding]];
  471   }
  472   return self->mailSourceString;
  473 }
  474 
  475 - (void)setImapContext:(NGImap4Context *)_ctx {
  476   ASSIGN(self->imapContext, _ctx);
  477 }
  478 
  479 /* actions */
  480 
  481 - (id)printMail {
  482   WOComponent *page;
  483   WOResponse  *response;
  484   
  485   // TODO: shouldn't we use an action lookup?!
  486   page = [self pageWithName:@"SkyImapMailPrintViewer"];
  487   [page takeValue:self->emailContent forKey:@"emailContent"];
  488   [page takeValue:[self object]      forKey:@"object"];
  489   response = [page generateResponse];
  490   [response setHeader:@"text/html" forKey:@"content-type"];
  491   return response;
  492 }
  493 
  494 - (id)tabClicked {
  495   return nil;
  496 }
  497 
  498 - (id)newMail {
  499   WOComponent *ct;
  500   
  501   ct = [[self session] instantiateComponentForCommand:@"new" 
  502                        type:objcMessageType];
  503   [(id)ct setImapContext:self->imapContext];
  504   return ct;
  505 }
  506 
  507 - (id)reply {
  508   return [self activateObject:[self object] withVerb:@"reply"];
  509 }
  510 - (id)replyAll {
  511   return [self activateObject:[self object] withVerb:@"reply-all"];
  512 }
  513 - (id)forward {
  514   return [self activateObject:[self object] withVerb:@"forward"];
  515 }
  516 - (id)editAsNew {
  517   return [self activateObject:[self object] withVerb:@"edit-as-new"];
  518 }
  519 
  520 - (NSNotificationCenter *)notificationCenter {
  521   return [NSNotificationCenter defaultCenter];
  522 }
  523 - (void)postMailsDeletedNotification:(NSArray *)_mails {
  524   [[self notificationCenter]
  525     postNotificationName:@"LSWImapMailWasDeleted" object:_mails];
  526 }
  527 
  528 - (id)_processMessageDeleteFailure:(id)_folder {
  529   NSException *exc;
  530   NSString    *wp, *reason;
  531   id          l;
  532 
  533         exc = [_folder lastException];
  534 
  535         [self logWithFormat:@"%s: could not copy: %@", __PRETTY_FUNCTION__, 
  536             exc];
  537     
  538         l  = [self labels];
  539         wp = [l valueForKey:@"MoveMailToTrashFailedWithReason"];
  540         if ((reason = [exc reason])) {
  541           reason = [l valueForKey:reason];
  542         }
  543         wp = [NSString stringWithFormat:@"%@: '%@'. %@", wp, reason,
  544                        [l valueForKey:@"DeleteMailsAnyway"]];
  545 
  546         [self setWarningPhrase:wp];
  547         [self setIsInWarningMode:YES];
  548         [self setWarningOkAction:@"reallyDeleteMail"];
  549 
  550         [self leavePage];
  551         return self;
  552 }
  553 
  554 - (id)reallyDeleteMail {
  555   NSArray        *msg;
  556   NGImap4Folder  *folder;
  557   OGoContentPage *page;
  558   
  559   [self setIsInWarningMode:NO];
  560   
  561   if ([self object] == nil) {
  562     [self setErrorString:@"No object available for delete operation."];
  563     return nil;
  564   }
  565   
  566   page = nil;
  567   if ([self shouldGoToNextMessageAfterDelete]) {
  568     page = [self showMail:YES
  569                  unread:[self shouldShowNextUnreadMessageAsNext]
  570                  alsoOther:YES];
  571     page = [[page retain] autorelease];
  572   }
  573 
  574   msg    = [NSArray arrayWithObject:[self object]];
  575   folder = [[self object] folder];
  576     
  577   [folder deleteMessages:msg];
  578   [self postMailsDeletedNotification:msg];
  579   
  580   if (page) {
  581     [self leavePage];
  582     [self leavePage];
  583     [self enterPage:page]; // TODO: somehow this doesn't work if I return page
  584     return nil;
  585   }
  586   else
  587     return [self leavePage];
  588 }
  589 
  590 - (id)delete {
  591   NSArray        *msg;
  592   NGImap4Folder  *folder;
  593   OGoContentPage *page = nil;
  594   
  595   if ([self object] == nil) {
  596     [self setErrorString:@"No object available for delete operation."];
  597     return nil;
  598   }
  599   
  600   if ([self shouldGoToNextMessageAfterDelete]) {
  601     page = [self showMail:YES
  602                  unread:[self shouldShowNextUnreadMessageAsNext]
  603                  alsoOther:YES];
  604     page = [[page retain] autorelease];
  605   }
  606   
  607   msg    = [NSArray arrayWithObject:[self object]];
  608   folder = [[self object] folder];
  609     
  610   if ([folder isInTrash]) {
  611     [folder deleteMessages:msg];
  612   }
  613   else {
  614     if (![folder moveMessages:msg toFolder:[self->imapContext trashFolder]])
  615       return [self _processMessageDeleteFailure:folder];
  616   }
  617   [self postMailsDeletedNotification:msg];
  618   
  619   if (page) {
  620     [self leavePage];
  621     [self leavePage];
  622     [self enterPage:page]; // TODO: somehow this doesn't work if I return page
  623     return nil;
  624   }
  625   else 
  626     return [self leavePage];
  627 }
  628 
  629 - (id)cancel {
  630   [self setIsInWarningMode:NO];
  631   return nil;
  632 }
  633 
  634 - (id)move {
  635   NGImap4Message  *msg;
  636   LSWImapMailMove *page;
  637   
  638   if ((msg = [self object]) == nil) {
  639     [self setErrorString:@"No object available for move operation."];
  640     return nil;
  641   }
  642   
  643   page = [self pageWithName:@"LSWImapMailMove"];
  644   [page setMails:[NSArray arrayWithObject:msg]];
  645   [self leavePage];
  646   
  647   // Note: for whatever reason just returning the page is *not* sufficient
  648   [self enterPage:page];
  649   return page;
  650 }
  651 
  652 - (id)downloadSource {
  653   WOResponse *response;
  654   id         content;
  655 
  656   response = [WOResponse responseWithRequest:[[self context] request]];
  657   [response setStatus:200];
  658   [response setHeader:@"text/plain" forKey:@"content-type"];
  659 
  660   content = [self mailSource];
  661 
  662   if ([content isKindOfClass:[NSData class]]) {
  663     ;
  664   }
  665   else if ([content isKindOfClass:[NSString class]]) {
  666     content = [NSData dataWithBytes:[content cString]
  667                       length:[content cStringLength]];
  668   }
  669   else {
  670     [(id)[[self context] page]
  671             setErrorString:
  672               @"couldn't provide downloadable representation of body"];
  673     [self logWithFormat:
  674         @"couldn't provide downloadable representation of body"];
  675     return nil;
  676   }
  677   [response setHeader:[NSString stringWithFormat:@"%"PRIuPTR, [content length]]
  678             forKey:@"content-length"];
  679 
  680   [response setHeader:@"identity" forKey:@"content-encoding"];
  681   [response setContent:content];
  682   return response;
  683 }
  684 
  685 - (id)toDoc {
  686   OGoContentPage *page;
  687   id             nv;
  688 
  689   nv   = [[[self context] valueForKey:@"page"] navigation];  
  690   page = [self pageWithName:@"SkyProject4DocumentEditor"];
  691   
  692   [page takeValue:[NSNumber numberWithBool:YES] forKey:@"isImport"];
  693 
  694   [nv enterPage:page];
  695 
  696   [page takeValue:[self mailSource] forKey:@"blob"];
  697   {
  698     id tmp = nil;
  699 
  700     tmp = [[self object] valueForKey:@"sendDate"];
  701     if ([tmp respondsToSelector:@selector(descriptionWithCalendarFormat:)])
  702       [page takeValue:[tmp descriptionWithCalendarFormat:FileNameDateFmt]
  703             forKey:@"fileName"];
  704   }
  705 
  706   if (!UseOldMailSourceSubject) { /* build abstract */
  707       id       tmp;
  708       NSString *subject;
  709 
  710       tmp = [[self object] valueForKey:@"sendDate"];
  711       tmp = [tmp respondsToSelector:@selector(descriptionWithCalendarFormat:)]
  712     ? [tmp descriptionWithCalendarFormat:sendDateDateFmt]
  713     : (NSString *)@"<unknown>";
  714       
  715       if (![tmp isNotEmpty])
  716         tmp = @"<unknown>";
  717       
  718       subject = [[self object] valueForKey:@"subject"];
  719       if (![subject isNotEmpty])
  720         subject = @"<unknown>";
  721       
  722       [page takeValue:[NSString stringWithFormat:@"%@ [%@]", subject, tmp]
  723             forKey:@"subject"];
  724   }
  725   else {
  726     id tmp;
  727     
  728     tmp = [[self object] valueForKey:@"subject"];
  729     if (tmp) {
  730 #if LIB_FOUNDATION_LIBRARY
  731       if ([tmp isKindOfClass:[NSInlineUTF16String class]]) {
  732     /* TODO: hack to avoid uff16 string confusings */
  733     tmp = [NSString stringWithFormat:@"%@", tmp];
  734       }
  735 #endif
  736       [page takeValue:tmp forKey:@"subject"];
  737     }
  738   }
  739   return page;
  740 }
  741 
  742 - (BOOL)projectAllowed {
  743   return YES;
  744 }
  745 
  746 - (NSString *)downloadTarget {
  747   return [[self context] contextID];
  748 }
  749 
  750 - (id)copyToProject {
  751   WOComponent *page;
  752 
  753   page = [self pageWithName:@"LSWImapMail2Project"];
  754   [page setMessages:[NSArray arrayWithObject:[self object]]];
  755   return page;
  756 }
  757 
  758 
  759 - (id)sendNoMDN {
  760   self->askToReturnReceipt = NO;
  761   [self setIsInWarningMode:NO];
  762   return nil;
  763 }
  764 - (id)sendMDN {
  765   // see RFC 2298 (Message Disposition Notification) for further information
  766   NGMimeMessage *message;
  767   NSArray       *addresses;
  768 
  769   [self setIsInWarningMode:NO];
  770   message = [self _mdnMessage];
  771   if (message == nil) {
  772     // failed to build message
  773     NSString *mdnType;
  774 
  775     mdnType = [self mailMDNType];
  776     
  777     if ([mdnType isEqualToString:@"automatic"]) {
  778       // automatic, no warnings
  779       [self setErrorString:nil];
  780       self->askToReturnReceipt = NO;
  781       return nil;
  782     }
  783     
  784     if (![[self errorString] length]) {
  785       [self setErrorString:
  786             [[self labels] valueForKey:@"mdnBuildMessageError"]];
  787     }
  788     self->askToReturnReceipt = NO; // only one try
  789     return nil;    
  790   }
  791 
  792   addresses = [NSArray arrayWithObject:self->dispositionNotificationTo];
  793 
  794   [self runCommand:@"email::deliver",
  795         @"copyToSentFolder", [NSNumber numberWithBool:NO],
  796         @"addresses",        addresses,
  797         @"mimePart",         message, nil];
  798 
  799   if (![self commit]) {
  800     [self rollback];
  801     [self setErrorString:
  802           [[self labels] valueForKey:@"mdnSendMessageError"]];
  803   }
  804   // no more questions / panels
  805   self->askToReturnReceipt = NO;
  806   return nil;
  807 }
  808 
  809 /* KVC */
  810 
  811 - (void)takeValue:(id)_val forKey:(NSString *)_key {
  812   if ([_key isEqualToString:@"messageWasUnread"]) {
  813     self->messageWasUnread = [_val boolValue];
  814   }
  815   else if ([_key isEqualToString:@"mailDS"]) {
  816     ASSIGN(self->mailDS, _val);
  817   }
  818   else if ([_key isEqualToString:@"state"]) {
  819     ASSIGN(self->state, _val);
  820   }
  821   else 
  822     [super takeValue:_val forKey:_key];
  823 }
  824 
  825 /* more accessors */
  826 
  827 - (void)setState:(SkyImapMailListState *)_s {
  828   ASSIGN(self->state, _s);
  829 }
  830 - (SkyImapMailListState *)state {
  831   return self->state;
  832 }
  833 
  834 /* actions */
  835  
  836 - (id)showMail:(BOOL)_next { // DEPRECATED
  837   BOOL showUnreadAsNext;
  838   
  839   showUnreadAsNext = [self shouldShowNextUnreadMessageAsNext];
  840   return [self showMail:_next unread:showUnreadAsNext alsoOther:YES];
  841 }
  842 
  843 - (id)nextMail {
  844   return [self showMail:YES unread:NO alsoOther:NO];
  845 }
  846 - (id)nextUnread {
  847   return [self showMail:YES unread:YES alsoOther:NO];
  848 }
  849 - (id)prevUnread {
  850   return [self showMail:NO unread:NO alsoOther:NO];
  851 }
  852 - (id)prevMail {
  853   return [self showMail:NO
  854                unread:[self shouldShowNextUnreadMessageAsNext]
  855                alsoOther:YES];
  856 }
  857 
  858 - (NGImap4Message *)nextMail:(BOOL)_next unread:(BOOL)_unread
  859   alsoOther:(BOOL)_alsoOthers
  860 {
  861   NSEnumerator   *mails;
  862   NGImap4Message *msg, *unreadMsg, *obj, *org, *prevUnread;
  863 
  864   mails      = [[self->mailDS fetchObjects] objectEnumerator];
  865   org        = [self object];
  866   msg        = nil;
  867   unreadMsg  = nil;
  868   prevUnread = nil;
  869 
  870   while ((obj = [mails nextObject])) {
  871     if ([obj isEqual:org])
  872       break;
  873 
  874     if (!_next) {
  875       msg = obj;
  876 
  877       if (![obj isRead])
  878         prevUnread = obj;
  879     }
  880     else if (_unread) {
  881       if (![obj isRead])
  882         prevUnread = obj;
  883     }
  884   }
  885   if (_next) {
  886     while ((obj = [mails nextObject])) {
  887       if (!msg)
  888         msg = obj;
  889 
  890       if (![obj isRead])
  891         unreadMsg = obj;
  892 
  893       if (!unreadMsg && _unread)
  894         continue;
  895 
  896       break;
  897     }
  898   }
  899   obj = nil;
  900   if (_unread) {
  901     obj = (_next) ? unreadMsg : prevUnread;
  902     if (_alsoOthers && !obj)
  903       obj = msg;
  904   }
  905   else
  906     obj = msg;
  907 
  908   return obj;
  909 }
  910 
  911 - (id)showMail:(BOOL)_next unread:(BOOL)_unread alsoOther:(BOOL)_alsoOthers {
  912   NGImap4Message *obj;
  913   WOComponent    *page;
  914 
  915   if ((obj=[self nextMail:_next unread:_unread alsoOther:_alsoOthers])==nil)
  916     return nil;
  917 
  918   page = [[[self session] navigation] activateObject:obj withVerb:@"view"];
  919     
  920   [page takeValue:[NSNumber numberWithBool:![obj isRead]]
  921     forKey:@"messageWasUnread"];
  922   [page takeValue:self->mailDS forKey:@"mailDS"];
  923 
  924   if (![obj isRead])
  925     [obj markRead];
  926 
  927   return page;
  928 }
  929 
  930 - (NSString *)viewUrlWithArguments:(NSDictionary *)_args
  931   message:(NGImap4Message *)_message
  932 {
  933   id       dict;
  934   NSString *listName;
  935   Class    c;
  936   int      cnt;
  937 
  938   if (_message == nil) {
  939     [self logWithFormat:@"%s: Missing message, args %@", __PRETTY_FUNCTION__,
  940           _args];
  941     return nil;
  942   }
  943   
  944   listName = [self->state name];
  945 
  946   if (![listName length])
  947     listName = @"MailList";
  948 
  949   c = ((cnt = [_args count]) > 0)
  950     ? [NSMutableDictionary class]
  951     : [NSDictionary class];
  952 
  953   dict = [c dictionaryWithObjectsAndKeys:
  954             [[_message url] stringValue], @"url",
  955             listName,                     @"listName",
  956             [[self session] sessionID],   @"wosid", 
  957             [[self context] contextID],   @"cid",
  958             nil];
  959 
  960   if (cnt > 0)
  961     [(NSMutableDictionary *)dict addEntriesFromDictionary:_args];
  962 
  963   return [[self context]
  964                 directActionURLForActionNamed:@"SkyImapMailActions/viewImapMail"
  965                 queryDictionary:dict];
  966 }
  967 
  968 - (NSString *)flagUrl {
  969   static NSDictionary *Dict = nil;
  970 
  971   if (Dict == nil) {
  972     NSString *v, *k;
  973 
  974     v = @"markFlagged";
  975     k = @"action";
  976     
  977     Dict = [[NSDictionary alloc]
  978                           initWithObjects:&v forKeys:&k count:1];
  979   }
  980   return [self viewUrlWithArguments:Dict message:[self object]];
  981 }
  982 
  983 - (NSString *)unFlagUrl {
  984   static NSDictionary *Dict = nil;
  985 
  986   if (Dict == nil) {
  987     NSString *v, *k;
  988 
  989     v = @"markUnFlagged";
  990     k = @"action";
  991     
  992     Dict = [[NSDictionary alloc]
  993                           initWithObjects:&v forKeys:&k count:1];
  994   }
  995   return [self viewUrlWithArguments:Dict message:[self object]];
  996 }
  997 
  998 - (NSString *)urlNext:(BOOL)_next unread:(BOOL)_unread {
  999   NGImap4Message *m;
 1000   
 1001   if ((m = [self nextMail:_next unread:_unread alsoOther:NO]) == nil)
 1002     return nil;
 1003   
 1004   return [self viewUrlWithArguments:nil message:m];
 1005 }
 1006 
 1007 - (NSString *)nextUrl {
 1008   if (!self->url->nextCalled)
 1009     [self->url applyNext:[self urlNext:YES unread:NO]];
 1010   return self->url->next;
 1011 }
 1012 - (NSString *)prevUrl {
 1013   if (!self->url->prevCalled)
 1014     [self->url applyPrev:[self urlNext:NO unread:NO]];
 1015   return self->url->prev;
 1016 }
 1017 - (NSString *)prevUnreadUrl {
 1018   if (!self->url->prevUnreadCalled) {
 1019     self->url->prevUnread       = [[self urlNext:NO unread:YES] retain];
 1020     self->url->prevUnreadCalled = YES;
 1021   }
 1022   return self->url->prevUnread;
 1023 }
 1024 
 1025 - (NSString *)nextUnreadUrl {
 1026   if (!self->url->nextUnreadCalled) {
 1027     self->url->nextUnread       = [[self urlNext:YES unread:YES] retain];
 1028     self->url->nextUnreadCalled = YES;
 1029   }
 1030   return self->url->nextUnread;
 1031 }
 1032 
 1033 
 1034 - (BOOL)viewSourceEnabled {
 1035   return self->viewSourceEnabled;
 1036 }
 1037 - (id)alternateShowSource {
 1038   self->viewSourceEnabled = !self->viewSourceEnabled;
 1039   return nil;
 1040 }
 1041 
 1042 - (BOOL)hasUnFlag {
 1043   return [[self object] isFlagged];
 1044 }
 1045 
 1046 - (BOOL)hasFlag {
 1047   return ![[self object] isFlagged];
 1048 }
 1049 
 1050 
 1051 - (BOOL)isDownloadAllEnabled {
 1052   return ([[[self session]
 1053                   valueForKey:@"displayedAttachmentDownloadUrls"] count] > 1)
 1054     ? YES : NO;
 1055 }
 1056 - (BOOL)downloadAllEnabled {
 1057   /* ignore first object */
 1058   return ([self->downloadAllObjs count] < 2) ? NO : YES; 
 1059 }
 1060 
 1061 - (id)downloadAll {
 1062   id tmp;
 1063   
 1064   tmp = [[self session] valueForKey:@"displayedAttachmentDownloadUrls"];
 1065   ASSIGN(self->downloadAllObjs, tmp);
 1066   return nil;
 1067 }
 1068 
 1069 - (NSString *)downloadAllString {
 1070   // TODO: move to sepearate component
 1071   NSMutableString *str;
 1072   NSEnumerator *enumerator;
 1073   NSDictionary *obj;
 1074   int          cnt;
 1075 
 1076   str = [NSMutableString stringWithCapacity:128];
 1077 
 1078   [str appendString:@"<script type=\"text/javascript\">\n"];
 1079   [str appendString:@"<!-- \n"];
 1080   
 1081   // TODO: document what kind of object the 'downloadAllObjs' array contains
 1082   enumerator = [self->downloadAllObjs objectEnumerator];
 1083   
 1084   cnt        = 0;
 1085   [enumerator nextObject]; /* ignore first object */
 1086   
 1087   while ((obj = [enumerator nextObject])) { 
 1088     NSDictionary *dict;
 1089     NSString     *name, *fname;
 1090     
 1091     fname = [obj objectForKey:@"name"];
 1092     
 1093     if ([fname length] == 0)
 1094       fname = @"download";
 1095     
 1096     // TODO: construct URLs using WOContext methods!
 1097     name = [@"OGoMailDownloadAction/download/" stringByAppendingString:fname];
 1098     
 1099     if ([obj objectForKey:@"url"]) {
 1100       dict = [NSDictionary dictionaryWithObjectsAndKeys:
 1101                            [obj objectForKey:@"url"],      @"url",
 1102                            [obj objectForKey:@"mimeType"], @"mimeType",
 1103                            [obj objectForKey:@"encoding"], @"encoding",
 1104                            fname, @"filename",
 1105                            nil];
 1106     }
 1107     else {
 1108       NSString *k;
 1109       id       d;
 1110       
 1111       if ((d = [obj objectForKey:@"data"]) == nil) {
 1112         [self logWithFormat:@"missing object data for %@", obj];
 1113         continue;
 1114       }
 1115       k = [NSString stringWithFormat:@"download_%d", cnt];
 1116       [[self session] takeValue:d forKey:k];
 1117       
 1118       dict = [NSDictionary dictionaryWithObjectsAndKeys:
 1119                            k, @"data_key",
 1120                            [obj objectForKey:@"mimeType"], @"mimeType",
 1121                            [obj objectForKey:@"encoding"], @"encoding",
 1122                            fname, @"filename",
 1123                            nil];
 1124     }
 1125   
 1126     [str appendString:@"window.open(\""];
 1127     [str appendString:[[self context]
 1128                              directActionURLForActionNamed:name
 1129                              queryDictionary:dict]];
 1130     [str appendString:@"\", \"download_"];
 1131     [str appendString:[[NSNumber numberWithInt:cnt++] stringValue]];
 1132     [str appendString:@"\", \""];
 1133     [str appendString:winJSSizing];
 1134     [str appendString:@"\");\n"];
 1135   }
 1136   [str appendString:@"self.focus(); \n"];
 1137   [str appendString:@"//-->\n"];
 1138   [str appendString:@"</script> \n"];
 1139   return str;
 1140 }
 1141 
 1142 /* MDNPrivateMethods */
 1143 
 1144 // FIRST BodyPart: the human readable part
 1145 + (NSString *)_mdnDefaultNotification {
 1146   // TODO: this should be localized !
 1147   static NSString *defNotification =
 1148     @"This is a Return Receipt for the mail you sent on "
 1149     @"$date$ \nto $to$ with subject '$subject$'.\n\n"
 1150     @"This only acknowledges that the message was displayed on "
 1151     @"the recipient's machine.\n"
 1152     @"This is no guarantee that the message has been read or understood.";
 1153   return defNotification;
 1154 }
 1155 
 1156 static inline NSString *_a(NSString *_obj) {
 1157   return (_obj == nil) ? (NSString *)@"" : _obj;
 1158 }
 1159 
 1160 - (NSDictionary *)_messageProperties {
 1161   NGImap4Message *o;
 1162   
 1163   o = [self object];
 1164   return [NSDictionary dictionaryWithObjectsAndKeys:
 1165                        _a(self->to),                        @"to",
 1166                        _a(self->cc),                        @"cc",
 1167                        _a(self->bcc),                       @"bcc",
 1168                        _a([o valueForKey:@"subject"]),      @"subject",
 1169                        _a([o valueForKey:@"sender"]),       @"sender",
 1170                        _a([o valueForKey:@"contentLen"]),   @"size",
 1171                        _a([o valueForKey:@"organization"]), @"organization",
 1172                        _a([o valueForKey:@"priority"]),     @"priority",
 1173                        _a([o valueForKey:@"messageId"]),    @"messageId",
 1174                        _a([o valueForKey:@"contentType"]),  @"contentType",
 1175                        [o valueForKey:@"sendDate"],         @"date",
 1176                        nil];
 1177 }
 1178 
 1179 - (NGMimeBodyPart *)_readableMDNPart {
 1180   NGMutableHashMap *h;
 1181   NGMimeBodyPart   *part;
 1182   NSString         *text;
 1183   NSDictionary     *props;
 1184 
 1185   /* prepare header */
 1186   h = [[NGMutableHashMap alloc] initWithCapacity:1];
 1187   [h setObject:textPlainType forKey:@"content-type"];
 1188   
 1189   /* build body part */
 1190   part  = [NGMimeBodyPart bodyPartWithHeader:h];
 1191   text  = [self mailMDNText];
 1192   props = [self _messageProperties];
 1193   
 1194   if ([text length] == 0) text = [LSWImapMailViewer _mdnDefaultNotification];
 1195   text = [text stringByReplacingVariablesWithBindings:props];
 1196 
 1197   [part setBody:text];
 1198   [h release]; h = nil;
 1199   return part;
 1200 }
 1201 
 1202 /* SECOND Part: the MDN part */
 1203 
 1204 - (NSString *)_mdnReportingUserAgent {
 1205   NSString *hostname;
 1206 
 1207   hostname = [[NSHost currentHost] name];
 1208   
 1209   if ([hostname length] == 0) hostname = [[NSHost currentHost] address];
 1210   if ([hostname length] == 0) hostname = @"mail";
 1211   return [[hostname description] stringByAppendingString:
 1212                                    @"; OpenGroupware.org"];
 1213 }
 1214 
 1215 - (NSString *)_mdnFinalRecipient {
 1216   NSString *str, *t;
 1217   id       acc;
 1218   NSRange  r;
 1219   
 1220   acc = [[self session] activeAccount];
 1221   str = [acc valueForKey:@"email1"];
 1222   
 1223   t = str;
 1224   if ([t length] == 0)
 1225     t = [acc valueForKey:@"login"];
 1226   else if ((r = [t rangeOfString:@"<"]).length > 0) {
 1227     // TODO: make this wrapping code a NSString category!
 1228     t = [t substringFromIndex:(r.location + r.length)];
 1229     
 1230     r = [t rangeOfString:@">"];
 1231     if (r.length == 0) {
 1232       // can not handle address format
 1233       [self logWithFormat:@"ERROR(%s) can not handle to - address format: %@",
 1234             __PRETTY_FUNCTION__, str];
 1235       // take it anyway
 1236       t = str;
 1237     }
 1238     else
 1239       t = [t substringToIndex:r.location];
 1240   }
 1241   return [@"rfc822;" stringByAppendingString:[t description]];
 1242 }
 1243 
 1244 - (NSString *)_mdnOriginalRecipient {
 1245   NSString       *str;
 1246   NSEnumerator   *e;
 1247   NSMutableArray *t;
 1248   id             one;
 1249   NSRange        r;
 1250 
 1251   e   = [self->emailContent valuesOfHeaderFieldWithName:@"to"];
 1252   t   = [NSMutableArray array];
 1253 
 1254   while ((one = [e nextObject])) 
 1255     [t addObject:one];
 1256   if ([t count] != 1)
 1257     return nil;
 1258 
 1259   // TODO: move to a string category
 1260   str = [t lastObject];
 1261   r = [str rangeOfString:@"<"];
 1262   if (r.length > 0) {
 1263     str = [str substringFromIndex:r.location + r.length];
 1264     r = [str rangeOfString:@">"];
 1265     if (r.length == 0) {
 1266       // can not handle address format
 1267       [self logWithFormat:@"ERROR(%s): can not handle to - address format: %@",
 1268             __PRETTY_FUNCTION__, [t lastObject]];
 1269       return nil;
 1270     }
 1271     str = [str substringToIndex:r.location];
 1272   }
 1273   return [@"rfc822;" stringByAppendingString:str];
 1274 }
 1275 
 1276 - (NSString *)_mdnDisposition {
 1277   NSString *type;
 1278 
 1279   type = [self mailMDNType];
 1280   type = [type isEqualToString:@"automatic"]
 1281     ? @"MDN-sent-automatically"
 1282     : @"MDN-sent-manually";
 1283   return [NSString stringWithFormat:@"manual-action/%@; displayed", type];
 1284 }
 1285 
 1286 - (NGMimeBodyPart *)_mdnPart {
 1287   NGMutableHashMap *h;
 1288   NGMimeBodyPart   *part;
 1289   NSMutableArray   *lines;
 1290   NSString         *text;
 1291 
 1292   /* prepare header */
 1293   h = [[NGMutableHashMap alloc] initWithCapacity:1];
 1294   [h setObject:msgDispositionNotiType forKey:@"content-type"];
 1295   
 1296   // build body part
 1297   part  = [NGMimeBodyPart bodyPartWithHeader:h];
 1298   lines = [NSMutableArray array];
 1299 
 1300   // reportin UA
 1301   text  = [self _mdnReportingUserAgent];
 1302   if ([text length] > 0)
 1303     [lines addObject:[@"Reporting-UA: " stringByAppendingString:text]];
 1304 
 1305   // original recipient
 1306   text  = [self _mdnOriginalRecipient];
 1307   if ([text length] > 0)
 1308     [lines addObject:[@"Original-Recipient: " stringByAppendingString:text]];
 1309   
 1310   // final recipient
 1311   text = [self _mdnFinalRecipient];
 1312   if ([text length] == 0) {
 1313     [self logWithFormat:
 1314         @"WARNING[%s]: invalid final recipient field for MDN, failed to "
 1315             @"build MDN - part for disposition notification",
 1316           __PRETTY_FUNCTION__];
 1317     return nil;
 1318   }
 1319   [lines addObject:[@"Final-Recipient: " stringByAppendingString:text]];
 1320   
 1321   // original message id
 1322   text = [[self object] valueForKey:@"messageId"];
 1323   if ([text length] > 0)
 1324     [lines addObject:[@"Original-Message-ID: " stringByAppendingString:text]];
 1325   
 1326   /* disposition */
 1327   text = [self _mdnDisposition];
 1328   [lines addObject:[@"Disposition: " stringByAppendingString:text]];
 1329   
 1330   /* build body */
 1331   [lines addObject:@""];
 1332   text = [lines componentsJoinedByString:@"\r\n"];
 1333   
 1334   [part setBody:[text dataUsingEncoding:NSUTF8StringEncoding]];
 1335   [h release]; h = nil;
 1336   return part;
 1337 }
 1338 
 1339 /* THIRD PART */
 1340 - (NGMimeBodyPart *)_originalMessageHeadersPart {
 1341   NGMutableHashMap       *h;
 1342   NGMimeBodyPart         *part;
 1343   NGMimeMessageGenerator *gen;
 1344   NSData                 *data;
 1345   NSString               *body;
 1346 
 1347   h = [[NGMutableHashMap alloc] initWithCapacity:1];
 1348   [h setObject:textRfc822HeadersType forKey:@"content-type"];
 1349   
 1350   // build body part
 1351   part = [NGMimeBodyPart bodyPartWithHeader:h];
 1352   gen  = [[NGMimeMessageGenerator alloc] init];
 1353   data = [gen generateHeaderData:[(NGImap4Message *)[self object] headers]];
 1354   body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
 1355   
 1356   [part setBody:body];
 1357 
 1358   [body release]; body = nil;
 1359   [gen  release]; gen  = nil;
 1360   [h    release]; h    = nil;
 1361   
 1362   return part;
 1363 }
 1364 
 1365 /* THE MDN MESSAGE (TODO: move MDN composition to own class) */
 1366 - (NSString *)_mdnSubject {
 1367   NSString *subject;
 1368   
 1369   subject = [self mailMDNSubject];
 1370 
 1371   if ([subject length] == 0) 
 1372     subject = [[self labels] valueForKey:@"mdn_subject"];
 1373   if ([subject length] == 0) 
 1374     subject = @"Disposition Notification (displayed)";
 1375   
 1376   return [subject stringByAppendingFormat:@" - %@",
 1377                     [[self object] valueForKey:@"subject"]];
 1378 }
 1379 
 1380 - (NSString *)errorStringForDispositionNotificationTo:(NSString *)_disp
 1381   withMailRestrictions:(SkyImapMailRestrictions *)mailRestrictions
 1382 {
 1383   NSMutableString *ms;
 1384   id labels;
 1385   
 1386   labels = [self labels];
 1387   ms = [NSMutableString stringWithCapacity:256];
 1388   [ms appendString:[labels valueForKey:@"mdnSendProhibitedToError"]];
 1389   [ms appendString:@"\n"];
 1390   [ms appendString:[labels valueForKey:@"prohibitedAddressError"]];
 1391   [ms appendString:@":\n"];
 1392   [ms appendString:[self->dispositionNotificationTo stringValue]];
 1393   [ms appendString:@"\n"];
 1394   [ms appendString:[labels valueForKey:@"allowedAddressError"]];
 1395   [ms appendString:@":\n"];
 1396   [ms appendString:[[mailRestrictions allowedDomains] description]];
 1397   return ms;
 1398 }
 1399 
 1400 - (BOOL)_checkMailRestrictionsWithHeaderMap:(NGMutableHashMap *)hs {
 1401   SkyImapMailRestrictions *mailRestrictions;
 1402   LSCommandContext *cmdctx;
 1403   
 1404   cmdctx           = [[self session] commandContext];
 1405   mailRestrictions = [SkyImapMailRestrictions alloc]; // to avoid warnings
 1406   mailRestrictions = [mailRestrictions initWithContext:(id)cmdctx];
 1407   
 1408   if (![mailRestrictions emailAddressAllowed:
 1409                            self->dispositionNotificationTo]) {
 1410     NSString *s;
 1411     
 1412     s = [self errorStringForDispositionNotificationTo:
 1413         self->dispositionNotificationTo
 1414           withMailRestrictions:mailRestrictions];
 1415     [self setErrorString:s];
 1416     [mailRestrictions release]; mailRestrictions = nil;
 1417     return NO;
 1418   }
 1419   
 1420   [hs addObject:self->dispositionNotificationTo forKey:@"to"];
 1421   [mailRestrictions release]; mailRestrictions = nil;
 1422   return YES;
 1423 }
 1424 - (void)_applyMdnMessageFromHeaderWithMap:(NGMutableHashMap *)hs {
 1425   NSString *from;
 1426   id       acc;
 1427 
 1428   acc  = [[self session] activeAccount];
 1429   from = [acc valueForKey:@"email1"];
 1430     
 1431   if ([from length] == 0) from = [acc valueForKey:@"login"];
 1432 
 1433   [hs setObject:from forKey:@"from"];
 1434 }
 1435 
 1436 - (NGMimeMessage *)_mdnMessage {
 1437   NGMutableHashMap    *hs;
 1438   NGMimeMessage       *message;
 1439   NGMimeMultipartBody *body;
 1440 
 1441   hs = [[NGMutableHashMap alloc] initWithCapacity:16];
 1442   
 1443   if (![self _checkMailRestrictionsWithHeaderMap:hs])
 1444     return nil;
 1445   
 1446   /* setting headers */
 1447   [hs addObject:[self _mdnSubject]    forKey:@"subject"];
 1448   [hs addObject:[NSCalendarDate date] forKey:@"date"];
 1449   [hs addObject:@"1.0"                forKey:@"MIME-Version"];
 1450   [hs addObject:xMailer               forKey:@"X-Mailer"];
 1451 
 1452   [self _applyMdnMessageFromHeaderWithMap:hs];
 1453   [hs addObject:multipartReportType forKey:@"content-type"];
 1454   
 1455   /* building message */
 1456   message = [[NGMimeMessage alloc] initWithHeader:hs];
 1457   [hs release]; hs = nil;
 1458   
 1459   // building multipart body
 1460 
 1461   body = [[NGMimeMultipartBody alloc] initWithPart:message];
 1462   [body addBodyPart:[self _readableMDNPart]];
 1463   [body addBodyPart:[self _mdnPart]];
 1464   [body addBodyPart:[self _originalMessageHeadersPart]];
 1465 
 1466   [message setBody:body];
 1467   [body release]; body = nil;
 1468   
 1469   return [message autorelease];
 1470 }
 1471 
 1472 @end /* LSWImapMailViewer */
 1473 
 1474 @implementation LSWMailViewerURLState
 1475 
 1476 - (void)dealloc {
 1477   [self->next       release];
 1478   [self->prev       release];
 1479   [self->prevUnread release];
 1480   [self->nextUnread release];
 1481   [super dealloc];
 1482 }
 1483 
 1484 /* operations */
 1485 
 1486 - (void)applyNext:(NSString *)_next {
 1487   self->next       = [_next copy];
 1488   self->nextCalled = YES;
 1489 }
 1490 - (void)applyPrev:(NSString *)_prev {
 1491   self->prev       = [_prev copy];
 1492   self->prevCalled = YES;
 1493 }
 1494 
 1495 - (void)reset {
 1496   [self->next       release]; self->next       = nil;
 1497   [self->prev       release]; self->prev       = nil;
 1498   [self->prevUnread release]; self->prevUnread = nil;
 1499   [self->nextUnread release]; self->nextUnread = nil;
 1500   
 1501   self->nextCalled       = NO;
 1502   self->prevCalled       = NO;
 1503   self->prevUnreadCalled = NO;
 1504   self->nextUnreadCalled = NO;
 1505 }
 1506 
 1507 @end /* LSWMailViewerURLState */