1 /*
2 ** Copyright 2000-2003 Double Precision, Inc.
3 ** See COPYING for distribution information.
4 */
5
6 /*
7 */
8
9 #include "config.h"
10
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <time.h>
15
16 #include "rfc822.h"
17 #include "imaprefs.h"
18
swapmsgdata(struct imap_refmsg * a,struct imap_refmsg * b)19 static void swapmsgdata(struct imap_refmsg *a, struct imap_refmsg *b)
20 {
21 char *cp;
22 char c;
23 time_t t;
24 unsigned long ul;
25
26 #define swap(a,b,tmp) (tmp)=(a); (a)=(b); (b)=(tmp);
27
28 swap(a->msgid, b->msgid, cp);
29 swap(a->isdummy, b->isdummy, c);
30 swap(a->flag2, b->flag2, c);
31
32 swap(a->timestamp, b->timestamp, t);
33 swap(a->seqnum, b->seqnum, ul);
34
35 #undef swap
36 }
37
rfc822_threadalloc()38 struct imap_refmsgtable *rfc822_threadalloc()
39 {
40 struct imap_refmsgtable *p;
41
42 p=(struct imap_refmsgtable *)malloc(sizeof(struct imap_refmsgtable));
43 if (p)
44 memset(p, 0, sizeof(*p));
45 return (p);
46 }
47
rfc822_threadfree(struct imap_refmsgtable * p)48 void rfc822_threadfree(struct imap_refmsgtable *p)
49 {
50 int i;
51 struct imap_refmsghash *h;
52 struct imap_subjlookup *s;
53 struct imap_refmsg *m;
54
55 for (i=0; i<sizeof(p->hashtable)/sizeof(p->hashtable[0]); i++)
56 while ((h=p->hashtable[i]) != 0)
57 {
58 p->hashtable[i]=h->nexthash;
59 free(h);
60 }
61
62 for (i=0; i<sizeof(p->subjtable)/sizeof(p->subjtable[0]); i++)
63 while ((s=p->subjtable[i]) != 0)
64 {
65 p->subjtable[i]=s->nextsubj;
66 free(s->subj);
67 free(s);
68 }
69
70 while ((m=p->firstmsg) != 0)
71 {
72 p->firstmsg=m->next;
73 if (m->subj)
74 free(m->subj);
75 free(m);
76 }
77 free(p);
78 }
79
hashmsgid(const char * msgid)80 static int hashmsgid(const char *msgid)
81 {
82 unsigned long hashno=0;
83
84 while (*msgid)
85 {
86 unsigned long n= (hashno << 1);
87
88 #define HMIDS (((struct imap_refmsgtable *)0)->hashtable)
89 #define HHMIDSS ( sizeof(HMIDS) / sizeof( HMIDS[0] ))
90
91 if (hashno & HHMIDSS )
92 n ^= 1;
93
94 hashno= n ^ (unsigned char)*msgid++;
95 }
96
97 return (hashno % HHMIDSS);
98 }
99
rfc822_threadallocmsg(struct imap_refmsgtable * mt,const char * msgid)100 struct imap_refmsg *rfc822_threadallocmsg(struct imap_refmsgtable *mt,
101 const char *msgid)
102 {
103 int n=hashmsgid(msgid);
104 struct imap_refmsg *msgp= (struct imap_refmsg *)
105 malloc(sizeof(struct imap_refmsg)+1+strlen(msgid));
106 struct imap_refmsghash *h, **hp;
107
108 if (!msgp) return (0);
109 memset(msgp, 0, sizeof(*msgp));
110 strcpy ((msgp->msgid=(char *)(msgp+1)), msgid);
111
112 h=(struct imap_refmsghash *)malloc(sizeof(struct imap_refmsghash));
113 if (!h)
114 {
115 free(msgp);
116 return (0);
117 }
118
119 for (hp= &mt->hashtable[n]; *hp; hp= & (*hp)->nexthash)
120 {
121 if (strcmp( (*hp)->msg->msgid, msgp->msgid) > 0)
122 break;
123 }
124
125 h->nexthash= *hp;
126 *hp=h;
127 h->msg=msgp;
128
129 msgp->last=mt->lastmsg;
130
131 if (mt->lastmsg)
132 mt->lastmsg->next=msgp;
133 else
134 mt->firstmsg=msgp;
135
136 mt->lastmsg=msgp;
137 return (msgp);
138 }
139
rfc822_threadsearchmsg(struct imap_refmsgtable * mt,const char * msgid)140 struct imap_refmsg *rfc822_threadsearchmsg(struct imap_refmsgtable *mt,
141 const char *msgid)
142 {
143 int n=hashmsgid(msgid);
144 struct imap_refmsghash *h;
145
146 for (h= mt->hashtable[n]; h; h= h->nexthash)
147 {
148 int rc=strcmp(h->msg->msgid, msgid);
149
150 if (rc == 0) return (h->msg);
151 if (rc > 0)
152 break;
153 }
154 return (0);
155 }
156
findsubj(struct imap_refmsgtable * mt,const char * s,int * isrefwd,int create,struct imap_subjlookup ** ptr)157 static int findsubj(struct imap_refmsgtable *mt, const char *s, int *isrefwd,
158 int create, struct imap_subjlookup **ptr)
159 {
160 char *ss=rfc822_coresubj(s, isrefwd);
161 int n;
162 struct imap_subjlookup **h;
163 struct imap_subjlookup *newsubj;
164
165 if (!ss) return (-1);
166 n=hashmsgid(ss);
167
168 for (h= &mt->subjtable[n]; *h; h= &(*h)->nextsubj)
169 {
170 int rc=strcmp((*h)->subj, ss);
171
172 if (rc == 0)
173 {
174 free(ss);
175 *ptr= *h;
176 return (0);
177 }
178 if (rc > 0)
179 break;
180 }
181 if (!create)
182 {
183 free(ss);
184 *ptr=0;
185 return (0);
186 }
187
188 newsubj=malloc(sizeof(struct imap_subjlookup));
189 if (!newsubj)
190 {
191 free(ss);
192 return (-1);
193 }
194 memset(newsubj, 0, sizeof(*newsubj));
195 newsubj->subj=ss;
196 newsubj->nextsubj= *h;
197 newsubj->msgisrefwd= *isrefwd;
198 *h=newsubj;
199 *ptr=newsubj;
200 return (0);
201 }
202
linkparent(struct imap_refmsg * msg,struct imap_refmsg * lastmsg)203 static void linkparent(struct imap_refmsg *msg, struct imap_refmsg *lastmsg)
204 {
205 msg->parent=lastmsg;
206 msg->prevsib=lastmsg->lastchild;
207 if (msg->prevsib)
208 msg->prevsib->nextsib=msg;
209 else
210 lastmsg->firstchild=msg;
211
212 lastmsg->lastchild=msg;
213 msg->nextsib=0;
214 }
215
216
breakparent(struct imap_refmsg * m)217 static void breakparent(struct imap_refmsg *m)
218 {
219 if (!m->parent) return;
220
221 if (m->prevsib) m->prevsib->nextsib=m->nextsib;
222 else m->parent->firstchild=m->nextsib;
223
224 if (m->nextsib) m->nextsib->prevsib=m->prevsib;
225 else m->parent->lastchild=m->prevsib;
226 m->parent=0;
227 }
228
dorefcreate(struct imap_refmsgtable * mt,const char * newmsgid,struct rfc822a * a)229 static struct imap_refmsg *dorefcreate(struct imap_refmsgtable *mt,
230 const char *newmsgid,
231 struct rfc822a *a)
232 /* a - references header */
233 {
234 struct imap_refmsg *lastmsg=0, *m;
235 struct imap_refmsg *msg;
236 int n;
237
238 /*
239 (A) Using the Message-IDs in the message's references, link
240 the corresponding messages together as parent/child. Make
241 the first reference the parent of the second (and the second
242 a child of the first), the second the parent of the third
243 (and the third a child of the second), etc. The following
244 rules govern the creation of these links:
245
246 If no reference message can be found with a given
247 Message-ID, create a dummy message with this ID. Use
248 this dummy message for all subsequent references to this
249 ID.
250 */
251
252 for (n=0; n<a->naddrs; n++)
253 {
254 char *msgid=a->addrs[n].tokens ? rfc822_getaddr(a, n):NULL;
255
256 msg=msgid ? rfc822_threadsearchmsg(mt, msgid):0;
257 if (!msg)
258 {
259 msg=rfc822_threadallocmsg(mt, msgid ? msgid:"");
260 if (!msg)
261 {
262 if (msgid)
263 free(msgid);
264
265 return (0);
266 }
267 msg->isdummy=1;
268 }
269
270 if (msgid)
271 free(msgid);
272
273 /*
274 If a reference message already has a parent, don't change
275 the existing link.
276 */
277
278 if (lastmsg == 0 || msg->parent)
279 {
280 lastmsg=msg;
281 continue;
282 }
283
284 /*
285 Do not create a parent/child link if creating that link
286 would introduce a loop. For example, before making
287 message A the parent of B, make sure that A is not a
288 descendent of B.
289
290 */
291
292 for (m=lastmsg; m; m=m->parent)
293 if (strcmp(m->msgid, msg->msgid) == 0)
294 break;
295 if (m)
296 {
297 lastmsg=msg;
298 continue;
299 }
300
301 linkparent(msg, lastmsg);
302
303 lastmsg=msg;
304 }
305
306 /*
307 (B) Create a parent/child link between the last reference
308 (or NIL if there are no references) and the current message.
309 If the current message has a parent already, break the
310 current parent/child link before creating the new one. Note
311 that if this message has no references, that it will now
312 have no parent.
313
314 NOTE: The parent/child links MUST be kept consistent with
315 one another at ALL times.
316
317 */
318
319 msg=*newmsgid ? rfc822_threadsearchmsg(mt, newmsgid):0;
320
321 /*
322 If a message does not contain a Message-ID header line,
323 or the Message-ID header line does not contain a valid
324 Message ID, then assign a unique Message ID to this
325 message.
326
327 Implementation note: empty msgid, plus dupe check below,
328 implements that.
329 */
330
331 if (msg && msg->isdummy)
332 {
333 msg->isdummy=0;
334 if (msg->parent)
335 breakparent(msg);
336 }
337 else
338 {
339 #if 1
340 /*
341 ** If two or more messages have the same Message ID, assign
342 ** a unique Message ID to each of the duplicates.
343 **
344 ** Implementation note: just unlink the existing message from
345 ** it's parents/children.
346 */
347 if (msg)
348 {
349 while (msg->firstchild)
350 breakparent(msg->firstchild);
351 breakparent(msg);
352 newmsgid="";
353
354 /* Create new entry with an empty msgid, if any more
355 ** msgids come, they'll hit the dupe check again.
356 */
357
358 }
359 #endif
360 msg=rfc822_threadallocmsg(mt, newmsgid);
361 if (!msg) return (0);
362 }
363
364 if (lastmsg)
365 {
366 for (m=lastmsg; m; m=m->parent)
367 if (strcmp(m->msgid, msg->msgid) == 0)
368 break;
369 if (!m)
370 linkparent(msg, lastmsg);
371 }
372 return (msg);
373 }
374
375 static struct imap_refmsg *threadmsg_common(struct imap_refmsg *m,
376 const char *subjheader,
377 const char *dateheader,
378 time_t dateheader_tm,
379 unsigned long seqnum);
380
381 static struct imap_refmsg *rfc822_threadmsgaref(struct imap_refmsgtable *mt,
382 const char *msgidhdr,
383 struct rfc822a *refhdr,
384 const char *subjheader,
385 const char *dateheader,
386 time_t dateheader_tm,
387 unsigned long seqnum);
388
rfc822_threadmsg(struct imap_refmsgtable * mt,const char * msgidhdr,const char * refhdr,const char * subjheader,const char * dateheader,time_t dateheader_tm,unsigned long seqnum)389 struct imap_refmsg *rfc822_threadmsg(struct imap_refmsgtable *mt,
390 const char *msgidhdr,
391 const char *refhdr,
392 const char *subjheader,
393 const char *dateheader,
394 time_t dateheader_tm,
395 unsigned long seqnum)
396 {
397 struct rfc822t *t;
398 struct rfc822a *a;
399 struct imap_refmsg *m;
400
401 t=rfc822t_alloc_new(refhdr ? refhdr:"", NULL, NULL);
402 if (!t)
403 {
404 return (0);
405 }
406
407 a=rfc822a_alloc(t);
408 if (!a)
409 {
410 rfc822t_free(t);
411 return (0);
412 }
413
414 m=rfc822_threadmsgaref(mt, msgidhdr, a, subjheader, dateheader,
415 dateheader_tm, seqnum);
416
417 rfc822a_free(a);
418 rfc822t_free(t);
419 return m;
420 }
421
422
rfc822_threadmsgrefs(struct imap_refmsgtable * mt,const char * msgid_s,const char * const * msgidList,const char * subjheader,const char * dateheader,time_t dateheader_tm,unsigned long seqnum)423 struct imap_refmsg *rfc822_threadmsgrefs(struct imap_refmsgtable *mt,
424 const char *msgid_s,
425 const char * const * msgidList,
426 const char *subjheader,
427 const char *dateheader,
428 time_t dateheader_tm,
429 unsigned long seqnum)
430 {
431 struct imap_refmsg *m;
432 struct rfc822token *tArray;
433 struct rfc822addr *aArray;
434
435 struct rfc822a a;
436 size_t n, i;
437
438 for (n=0; msgidList[n]; n++)
439 ;
440
441 if ((tArray=malloc((n+1) * sizeof(*tArray))) == NULL)
442 return NULL;
443
444 if ((aArray=malloc((n+1) * sizeof(*aArray))) == NULL)
445 {
446 free(tArray);
447 return NULL;
448 }
449
450 for (i=0; i<n; i++)
451 {
452 tArray[i].next=NULL;
453 tArray[i].token=0;
454 tArray[i].ptr=msgidList[i];
455 tArray[i].len=strlen(msgidList[i]);
456
457 aArray[i].name=NULL;
458 aArray[i].tokens=&tArray[i];
459 }
460
461 a.naddrs=n;
462 a.addrs=aArray;
463
464 m=rfc822_threadmsgaref(mt, msgid_s, &a, subjheader, dateheader,
465 dateheader_tm, seqnum);
466
467 free(tArray);
468 free(aArray);
469 return m;
470 }
471
rfc822_threadmsgaref(struct imap_refmsgtable * mt,const char * msgidhdr,struct rfc822a * refhdr,const char * subjheader,const char * dateheader,time_t dateheader_tm,unsigned long seqnum)472 static struct imap_refmsg *rfc822_threadmsgaref(struct imap_refmsgtable *mt,
473 const char *msgidhdr,
474 struct rfc822a *refhdr,
475 const char *subjheader,
476 const char *dateheader,
477 time_t dateheader_tm,
478 unsigned long seqnum)
479 {
480 struct rfc822t *t;
481 struct rfc822a *a;
482 struct imap_refmsg *m;
483
484 char *msgid_s;
485
486 t=rfc822t_alloc_new(msgidhdr ? msgidhdr:"", NULL, NULL);
487 if (!t)
488 return (0);
489 a=rfc822a_alloc(t);
490 if (!a)
491 {
492 rfc822t_free(t);
493 return (0);
494 }
495
496 msgid_s=a->naddrs > 0 ? rfc822_getaddr(a, 0):strdup("");
497
498 rfc822a_free(a);
499 rfc822t_free(t);
500
501 if (!msgid_s)
502 return (0);
503
504 m=dorefcreate(mt, msgid_s, refhdr);
505
506 free(msgid_s);
507
508 if (!m)
509 return (0);
510
511
512 return threadmsg_common(m, subjheader, dateheader,
513 dateheader_tm, seqnum);
514 }
515
threadmsg_common(struct imap_refmsg * m,const char * subjheader,const char * dateheader,time_t dateheader_tm,unsigned long seqnum)516 static struct imap_refmsg *threadmsg_common(struct imap_refmsg *m,
517 const char *subjheader,
518 const char *dateheader,
519 time_t dateheader_tm,
520 unsigned long seqnum)
521 {
522 if (subjheader && (m->subj=strdup(subjheader)) == 0)
523 return (0); /* Cleanup in rfc822_threadfree() */
524
525 if (dateheader)
526 {
527 rfc822_parsedate_chk(dateheader, &dateheader_tm);
528 }
529
530 m->timestamp=dateheader_tm;
531
532 m->seqnum=seqnum;
533
534 return (m);
535 }
536
537 /*
538 (2) Gather together all of the messages that have no parents
539 and make them all children (siblings of one another) of a dummy
540 parent (the "root"). These messages constitute first messages
541 of the threads created thus far.
542
543 */
544
rfc822_threadgetroot(struct imap_refmsgtable * mt)545 struct imap_refmsg *rfc822_threadgetroot(struct imap_refmsgtable *mt)
546 {
547 struct imap_refmsg *root, *m;
548
549 if (mt->rootptr)
550 return (mt->rootptr);
551
552 root=rfc822_threadallocmsg(mt, "(root)");
553
554 if (!root) return (0);
555
556 root->parent=root; /* Temporary */
557 root->isdummy=1;
558
559 for (m=mt->firstmsg; m; m=m->next)
560 if (!m->parent)
561 {
562 if (m->isdummy && m->firstchild == 0)
563 continue; /* Can happen in reference creation */
564
565 linkparent(m, root);
566 }
567 root->parent=NULL;
568 return (mt->rootptr=root);
569 }
570
571 /*
572 **
573 ** (3) Prune dummy messages from the thread tree. Traverse each
574 ** thread under the root, and for each message:
575 */
576
rfc822_threadprune(struct imap_refmsgtable * mt)577 void rfc822_threadprune(struct imap_refmsgtable *mt)
578 {
579 struct imap_refmsg *msg;
580
581 for (msg=mt->firstmsg; msg; msg=msg->next)
582 {
583 struct imap_refmsg *saveparent, *m;
584
585 if (!msg->parent)
586 continue; /* The root, need it later. */
587
588 if (!msg->isdummy)
589 continue;
590
591 /*
592 **
593 ** If it is a dummy message with NO children, delete it.
594 */
595
596 if (msg->firstchild == 0)
597 {
598 breakparent(msg);
599 /*
600 ** Don't free the node, it'll be done on msgtable
601 ** purge.
602 */
603 continue;
604 }
605
606 /*
607 ** If it is a dummy message with children, delete it, but
608 ** promote its children to the current level. In other words,
609 ** splice them in with the dummy's siblings.
610 **
611 ** Do not promote the children if doing so would make them
612 ** children of the root, unless there is only one child.
613 */
614
615 if (msg->firstchild->nextsib &&
616 msg->parent->parent)
617 continue;
618
619 saveparent=msg->parent;
620 breakparent(msg);
621
622 while ((m=msg->firstchild) != 0)
623 {
624 breakparent(m);
625 linkparent(m, saveparent);
626 }
627 }
628 }
629
630 static int cmp_msgs(const void *, const void *);
631
rfc822_threadsortsubj(struct imap_refmsg * root)632 int rfc822_threadsortsubj(struct imap_refmsg *root)
633 {
634 struct imap_refmsg *toproot;
635
636 /*
637 ** (4) Sort the messages under the root (top-level siblings only)
638 ** by sent date. In the case of an exact match on sent date or if
639 ** either of the Date: headers used in a comparison can not be
640 ** parsed, use the order in which the messages appear in the
641 ** mailbox (that is, by sequence number) to determine the order.
642 ** In the case of a dummy message, sort its children by sent date
643 ** and then use the first child for the top-level sort.
644 */
645 size_t cnt, i;
646 struct imap_refmsg **sortarray;
647
648 for (cnt=0, toproot=root->firstchild; toproot;
649 toproot=toproot->nextsib)
650 {
651 if (toproot->isdummy)
652 rfc822_threadsortsubj(toproot);
653 ++cnt;
654 }
655
656 if ((sortarray=malloc(sizeof(struct imap_refmsg *)*(cnt+1))) == 0)
657 return (-1);
658
659 for (cnt=0; (toproot=root->firstchild) != NULL; ++cnt)
660 {
661 sortarray[cnt]=toproot;
662 breakparent(toproot);
663 }
664
665 qsort(sortarray, cnt, sizeof(*sortarray), cmp_msgs);
666
667 for (i=0; i<cnt; i++)
668 linkparent(sortarray[i], root);
669 free(sortarray);
670 return (0);
671 }
672
rfc822_threadgathersubj(struct imap_refmsgtable * mt,struct imap_refmsg * root)673 int rfc822_threadgathersubj(struct imap_refmsgtable *mt,
674 struct imap_refmsg *root)
675 {
676 struct imap_refmsg *toproot, *p;
677
678 /*
679 ** (5) Gather together messages under the root that have the same
680 ** extracted subject text.
681 **
682 ** (A) Create a table for associating extracted subjects with
683 ** messages.
684 **
685 ** (B) Populate the subject table with one message per
686 ** extracted subject. For each message under the root:
687 */
688
689 for (toproot=root->firstchild; toproot; toproot=toproot->nextsib)
690 {
691 const char *subj;
692 struct imap_subjlookup *subjtop;
693 int isrefwd;
694
695 /*
696 ** (i) Find the subject of this thread by extracting the
697 ** base subject from the current message, or its first child
698 ** if the current message is a dummy.
699 */
700
701 p=toproot;
702 if (p->isdummy)
703 p=p->firstchild;
704
705 subj=p->subj ? p->subj:"";
706
707
708 /*
709 ** (ii) If the extracted subject is empty, skip this
710 ** message.
711 */
712
713 if (*subj == 0)
714 continue;
715
716 /*
717 ** (iii) Lookup the message associated with this extracted
718 ** subject in the table.
719 */
720
721 if (findsubj(mt, subj, &isrefwd, 1, &subjtop))
722 return (-1);
723
724 /*
725 **
726 ** (iv) If there is no message in the table with this
727 ** subject, add the current message and the extracted
728 ** subject to the subject table.
729 */
730
731 if (subjtop->msg == 0)
732 {
733 subjtop->msg=toproot;
734 subjtop->msgisrefwd=isrefwd;
735 continue;
736 }
737
738 /*
739 ** Otherwise, replace the message in the table with the
740 ** current message if the message in the table is not a
741 ** dummy AND either of the following criteria are true:
742 */
743
744 if (!subjtop->msg->isdummy)
745 {
746 /*
747 ** The current message is a dummy
748 **
749 */
750
751 if (toproot->isdummy)
752 {
753 subjtop->msg=toproot;
754 subjtop->msgisrefwd=isrefwd;
755 continue;
756 }
757
758 /*
759 ** The message in the table is a reply or forward (its
760 ** original subject contains a subj-refwd part and/or a
761 ** "(fwd)" subj-trailer) and the current message is
762 not.
763 */
764
765 if (subjtop->msgisrefwd && !isrefwd)
766 {
767 subjtop->msg=toproot;
768 subjtop->msgisrefwd=isrefwd;
769 }
770 }
771 }
772 return (0);
773 }
774
775 /*
776 ** (C) Merge threads with the same subject. For each message
777 ** under the root:
778 */
779
rfc822_threadmergesubj(struct imap_refmsgtable * mt,struct imap_refmsg * root)780 int rfc822_threadmergesubj(struct imap_refmsgtable *mt,
781 struct imap_refmsg *root)
782 {
783 struct imap_refmsg *toproot, *p, *q, *nextroot;
784 char *str;
785
786 for (toproot=root->firstchild; toproot; toproot=nextroot)
787 {
788 const char *subj;
789 struct imap_subjlookup *subjtop;
790 int isrefwd;
791
792 nextroot=toproot->nextsib;
793
794 /*
795 ** (i) Find the subject of this thread as in step 4.B.i
796 ** above.
797 */
798
799 p=toproot;
800 if (p->isdummy)
801 p=p->firstchild;
802
803 subj=p->subj ? p->subj:"";
804
805 /*
806 ** (ii) If the extracted subject is empty, skip this
807 ** message.
808 */
809
810 if (*subj == 0)
811 continue;
812
813 /*
814 ** (iii) Lookup the message associated with this extracted
815 ** subject in the table.
816 */
817
818 if (findsubj(mt, subj, &isrefwd, 0, &subjtop) || subjtop == 0)
819 return (-1);
820
821 /*
822 ** (iv) If the message in the table is the current message,
823 ** skip it.
824 */
825
826 /* NOTE - ptr comparison IS NOT LEGAL */
827
828 subjtop->msg->flag2=1;
829 if (toproot->flag2)
830 {
831 toproot->flag2=0;
832 continue;
833 }
834 subjtop->msg->flag2=0;
835
836 /*
837 ** Otherwise, merge the current message with the one in the
838 ** table using the following rules:
839 **
840 ** If both messages are dummies, append the current
841 ** message's children to the children of the message in
842 ** the table (the children of both messages become
843 ** siblings), and then delete the current message.
844 */
845
846 if (subjtop->msg->isdummy && toproot->isdummy)
847 {
848 while ((p=toproot->firstchild) != 0)
849 {
850 breakparent(p);
851 linkparent(p, subjtop->msg);
852 }
853 breakparent(toproot);
854 continue;
855 }
856
857 /*
858 ** If the message in the table is a dummy and the current
859 ** message is not, make the current message a child of
860 ** the message in the table (a sibling of it's children).
861 */
862
863 if (subjtop->msg->isdummy)
864 {
865 breakparent(toproot);
866 linkparent(toproot, subjtop->msg);
867 continue;
868 }
869
870 /*
871 ** If the current message is a reply or forward and the
872 ** message in the table is not, make the current message
873 ** a child of the message in the table (a sibling of it's
874 ** children).
875 */
876
877 if (isrefwd)
878 {
879 p=subjtop->msg;
880 if (p->isdummy)
881 p=p->firstchild;
882
883 subj=p->subj ? p->subj:"";
884
885 str=rfc822_coresubj(subj, &isrefwd);
886
887 if (!str)
888 return (-1);
889 free(str); /* Don't really care */
890
891 if (!isrefwd)
892 {
893 breakparent(toproot);
894 linkparent(toproot, subjtop->msg);
895 continue;
896 }
897 }
898
899 /*
900 ** Otherwise, create a new dummy container and make both
901 ** messages children of the dummy, and replace the
902 ** message in the table with the dummy message.
903 */
904
905 /* What we do is create a new message, then move the
906 ** contents of subjtop->msg (including its children)
907 ** to the new message, then make the new message a child
908 ** of subjtop->msg, and mark subjtop->msg as a dummy msg.
909 */
910
911 q=rfc822_threadallocmsg(mt, "(dummy)");
912 if (!q)
913 return (-1);
914
915 q->isdummy=1;
916
917 swapmsgdata(q, subjtop->msg);
918
919 while ((p=subjtop->msg->firstchild) != 0)
920 {
921 breakparent(p);
922 linkparent(p, q);
923 }
924 linkparent(q, subjtop->msg);
925
926 breakparent(toproot);
927 linkparent(toproot, subjtop->msg);
928 }
929 return (0);
930 }
931
932 /*
933 ** (6) Traverse the messages under the root and sort each set of
934 ** siblings by sent date. Traverse the messages in such a way
935 ** that the "youngest" set of siblings are sorted first, and the
936 ** "oldest" set of siblings are sorted last (grandchildren are
937 ** sorted before children, etc). In the case of an exact match on
938 ** sent date or if either of the Date: headers used in a
939 ** comparison can not be parsed, use the order in which the
940 ** messages appear in the mailbox (that is, by sequence number) to
941 ** determine the order. In the case of a dummy message (which can
942 ** only occur with top-level siblings), use its first child for
943 ** sorting.
944 */
945
cmp_msgs(const void * a,const void * b)946 static int cmp_msgs(const void *a, const void *b)
947 {
948 struct imap_refmsg *ma=*(struct imap_refmsg **)a;
949 struct imap_refmsg *mb=*(struct imap_refmsg **)b;
950 time_t ta, tb;
951 unsigned long na, nb;
952
953 while (ma && ma->isdummy)
954 ma=ma->firstchild;
955
956 while (mb && mb->isdummy)
957 mb=mb->firstchild;
958
959 ta=tb=0;
960 na=nb=0;
961 if (ma)
962 {
963 ta=ma->timestamp;
964 na=ma->seqnum;
965 }
966 if (mb)
967 {
968 tb=mb->timestamp;
969 nb=mb->seqnum;
970 }
971
972 return (ta && tb && ta != tb ? ta < tb ? -1: 1:
973 na < nb ? -1: na > nb ? 1:0);
974 }
975
976 struct imap_threadsortinfo {
977 struct imap_refmsgtable *mt;
978 struct imap_refmsg **sort_table;
979 size_t sort_table_cnt;
980 } ;
981
982 static int dothreadsort(struct imap_threadsortinfo *,
983 struct imap_refmsg *);
984
rfc822_threadsortbydate(struct imap_refmsgtable * mt)985 int rfc822_threadsortbydate(struct imap_refmsgtable *mt)
986 {
987 struct imap_threadsortinfo itsi;
988 int rc;
989
990 itsi.mt=mt;
991 itsi.sort_table=0;
992 itsi.sort_table_cnt=0;
993
994 rc=dothreadsort(&itsi, mt->rootptr);
995
996 if (itsi.sort_table)
997 free(itsi.sort_table);
998 return (rc);
999 }
1000
dothreadsort(struct imap_threadsortinfo * itsi,struct imap_refmsg * p)1001 static int dothreadsort(struct imap_threadsortinfo *itsi,
1002 struct imap_refmsg *p)
1003 {
1004 struct imap_refmsg *q;
1005 size_t i, n;
1006
1007 for (q=p->firstchild; q; q=q->nextsib)
1008 dothreadsort(itsi, q);
1009
1010 n=0;
1011 for (q=p->firstchild; q; q=q->nextsib)
1012 ++n;
1013
1014 if (n > itsi->sort_table_cnt)
1015 {
1016 struct imap_refmsg **new_array=(struct imap_refmsg **)
1017 (itsi->sort_table ?
1018 realloc(itsi->sort_table,
1019 sizeof(struct imap_refmsg *)*n)
1020 :malloc(sizeof(struct imap_refmsg *)*n));
1021
1022 if (!new_array)
1023 return (-1);
1024
1025 itsi->sort_table=new_array;
1026 itsi->sort_table_cnt=n;
1027 }
1028
1029 n=0;
1030 while ((q=p->firstchild) != 0)
1031 {
1032 breakparent(q);
1033 itsi->sort_table[n++]=q;
1034 }
1035
1036 qsort(itsi->sort_table, n, sizeof(struct imap_refmsg *), cmp_msgs);
1037
1038 for (i=0; i<n; i++)
1039 linkparent(itsi->sort_table[i], p);
1040 return (0);
1041 }
1042
rfc822_thread(struct imap_refmsgtable * mt)1043 struct imap_refmsg *rfc822_thread(struct imap_refmsgtable *mt)
1044 {
1045 if (!mt->rootptr)
1046 {
1047 rfc822_threadprune(mt);
1048 if ((mt->rootptr=rfc822_threadgetroot(mt)) == 0)
1049 return (0);
1050 if (rfc822_threadsortsubj(mt->rootptr) ||
1051 rfc822_threadgathersubj(mt, mt->rootptr) ||
1052 rfc822_threadmergesubj(mt, mt->rootptr) ||
1053 rfc822_threadsortbydate(mt))
1054 {
1055 mt->rootptr=0;
1056 return (0);
1057 }
1058 }
1059
1060 return (mt->rootptr);
1061 }
1062