"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 */