Imported Upstream version 0.66.1
[hcoop/debian/courier-authlib.git] / libs / rfc822 / imaprefs.c
CommitLineData
d9898ee8 1/*
2** Copyright 2000-2003 Double Precision, Inc.
3** See COPYING for distribution information.
4*/
5
6/*
d9898ee8 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
19static 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
38struct imap_refmsgtable *rfc822_threadalloc()
39{
40struct 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
48void rfc822_threadfree(struct imap_refmsgtable *p)
49{
50int i;
51struct imap_refmsghash *h;
52struct imap_subjlookup *s;
53struct 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
80static int hashmsgid(const char *msgid)
81{
82unsigned 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
100struct imap_refmsg *rfc822_threadallocmsg(struct imap_refmsgtable *mt,
101 const char *msgid)
102{
103int n=hashmsgid(msgid);
104struct imap_refmsg *msgp= (struct imap_refmsg *)
105 malloc(sizeof(struct imap_refmsg)+1+strlen(msgid));
106struct 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
140struct imap_refmsg *rfc822_threadsearchmsg(struct imap_refmsgtable *mt,
141 const char *msgid)
142{
143int n=hashmsgid(msgid);
144struct 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
157static 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
203static 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
217static 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
229static struct imap_refmsg *dorefcreate(struct imap_refmsgtable *mt,
230 const char *newmsgid,
231 struct rfc822a *a)
232 /* a - references header */
233{
234struct imap_refmsg *lastmsg=0, *m;
235struct imap_refmsg *msg;
236int 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 {
8d138742 254 char *msgid=a->addrs[n].tokens ? rfc822_getaddr(a, n):NULL;
d9898ee8 255
8d138742 256 msg=msgid ? rfc822_threadsearchmsg(mt, msgid):0;
d9898ee8 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
375static 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
381static 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
389struct 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
423struct 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
472static 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
516static 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 dateheader_tm=rfc822_parsedt(dateheader);
527
528 m->timestamp=dateheader_tm;
529
530 m->seqnum=seqnum;
531
532 return (m);
533}
534
535/*
536 (2) Gather together all of the messages that have no parents
537 and make them all children (siblings of one another) of a dummy
538 parent (the "root"). These messages constitute first messages
539 of the threads created thus far.
540
541*/
542
543struct imap_refmsg *rfc822_threadgetroot(struct imap_refmsgtable *mt)
544{
545 struct imap_refmsg *root, *m;
546
547 if (mt->rootptr)
548 return (mt->rootptr);
549
550 root=rfc822_threadallocmsg(mt, "(root)");
551
552 if (!root) return (0);
553
554 root->parent=root; /* Temporary */
555 root->isdummy=1;
556
557 for (m=mt->firstmsg; m; m=m->next)
558 if (!m->parent)
559 {
560 if (m->isdummy && m->firstchild == 0)
561 continue; /* Can happen in reference creation */
562
563 linkparent(m, root);
564 }
565 root->parent=NULL;
566 return (mt->rootptr=root);
567}
568
569/*
570**
571** (3) Prune dummy messages from the thread tree. Traverse each
572** thread under the root, and for each message:
573*/
574
575void rfc822_threadprune(struct imap_refmsgtable *mt)
576{
577 struct imap_refmsg *msg;
578
579 for (msg=mt->firstmsg; msg; msg=msg->next)
580 {
581 struct imap_refmsg *saveparent, *m;
582
583 if (!msg->parent)
584 continue; /* The root, need it later. */
585
586 if (!msg->isdummy)
587 continue;
588
589 /*
590 **
591 ** If it is a dummy message with NO children, delete it.
592 */
593
594 if (msg->firstchild == 0)
595 {
596 breakparent(msg);
597 /*
598 ** Don't free the node, it'll be done on msgtable
599 ** purge.
600 */
601 continue;
602 }
603
604 /*
605 ** If it is a dummy message with children, delete it, but
606 ** promote its children to the current level. In other words,
607 ** splice them in with the dummy's siblings.
608 **
609 ** Do not promote the children if doing so would make them
610 ** children of the root, unless there is only one child.
611 */
612
613 if (msg->firstchild->nextsib &&
614 msg->parent->parent)
615 continue;
616
617 saveparent=msg->parent;
618 breakparent(msg);
619
620 while ((m=msg->firstchild) != 0)
621 {
622 breakparent(m);
623 linkparent(m, saveparent);
624 }
625 }
626}
627
628static int cmp_msgs(const void *, const void *);
629
630int rfc822_threadsortsubj(struct imap_refmsg *root)
631{
632 struct imap_refmsg *toproot;
633
634/*
635** (4) Sort the messages under the root (top-level siblings only)
636** by sent date. In the case of an exact match on sent date or if
637** either of the Date: headers used in a comparison can not be
638** parsed, use the order in which the messages appear in the
639** mailbox (that is, by sequence number) to determine the order.
640** In the case of a dummy message, sort its children by sent date
641** and then use the first child for the top-level sort.
642*/
643 size_t cnt, i;
644 struct imap_refmsg **sortarray;
645
646 for (cnt=0, toproot=root->firstchild; toproot;
647 toproot=toproot->nextsib)
648 {
649 if (toproot->isdummy)
650 rfc822_threadsortsubj(toproot);
651 ++cnt;
652 }
653
654 if ((sortarray=malloc(sizeof(struct imap_refmsg *)*(cnt+1))) == 0)
655 return (-1);
656
657 for (cnt=0; (toproot=root->firstchild) != NULL; ++cnt)
658 {
659 sortarray[cnt]=toproot;
660 breakparent(toproot);
661 }
662
663 qsort(sortarray, cnt, sizeof(*sortarray), cmp_msgs);
664
665 for (i=0; i<cnt; i++)
666 linkparent(sortarray[i], root);
667 free(sortarray);
668 return (0);
669}
670
671int rfc822_threadgathersubj(struct imap_refmsgtable *mt,
672 struct imap_refmsg *root)
673{
674 struct imap_refmsg *toproot, *p;
675
676/*
677** (5) Gather together messages under the root that have the same
678** extracted subject text.
679**
680** (A) Create a table for associating extracted subjects with
681** messages.
682**
683** (B) Populate the subject table with one message per
684** extracted subject. For each message under the root:
685*/
686
687 for (toproot=root->firstchild; toproot; toproot=toproot->nextsib)
688 {
689 const char *subj;
690 struct imap_subjlookup *subjtop;
691 int isrefwd;
692
693 /*
694 ** (i) Find the subject of this thread by extracting the
695 ** base subject from the current message, or its first child
696 ** if the current message is a dummy.
697 */
698
699 p=toproot;
700 if (p->isdummy)
701 p=p->firstchild;
702
703 subj=p->subj ? p->subj:"";
704
705
706 /*
707 ** (ii) If the extracted subject is empty, skip this
708 ** message.
709 */
710
711 if (*subj == 0)
712 continue;
713
714 /*
715 ** (iii) Lookup the message associated with this extracted
716 ** subject in the table.
717 */
718
719 if (findsubj(mt, subj, &isrefwd, 1, &subjtop))
720 return (-1);
721
722 /*
723 **
724 ** (iv) If there is no message in the table with this
725 ** subject, add the current message and the extracted
726 ** subject to the subject table.
727 */
728
729 if (subjtop->msg == 0)
730 {
731 subjtop->msg=toproot;
732 subjtop->msgisrefwd=isrefwd;
733 continue;
734 }
735
736 /*
737 ** Otherwise, replace the message in the table with the
738 ** current message if the message in the table is not a
739 ** dummy AND either of the following criteria are true:
740 */
741
742 if (!subjtop->msg->isdummy)
743 {
744 /*
745 ** The current message is a dummy
746 **
747 */
748
749 if (toproot->isdummy)
750 {
751 subjtop->msg=toproot;
752 subjtop->msgisrefwd=isrefwd;
753 continue;
754 }
755
756 /*
757 ** The message in the table is a reply or forward (its
758 ** original subject contains a subj-refwd part and/or a
759 ** "(fwd)" subj-trailer) and the current message is
760 not.
761 */
762
763 if (subjtop->msgisrefwd && !isrefwd)
764 {
765 subjtop->msg=toproot;
766 subjtop->msgisrefwd=isrefwd;
767 }
768 }
769 }
770 return (0);
771}
772
773/*
774** (C) Merge threads with the same subject. For each message
775** under the root:
776*/
777
778int rfc822_threadmergesubj(struct imap_refmsgtable *mt,
779 struct imap_refmsg *root)
780{
781 struct imap_refmsg *toproot, *p, *q, *nextroot;
782 char *str;
783
784 for (toproot=root->firstchild; toproot; toproot=nextroot)
785 {
786 const char *subj;
787 struct imap_subjlookup *subjtop;
788 int isrefwd;
789
790 nextroot=toproot->nextsib;
791
792 /*
793 ** (i) Find the subject of this thread as in step 4.B.i
794 ** above.
795 */
796
797 p=toproot;
798 if (p->isdummy)
799 p=p->firstchild;
800
801 subj=p->subj ? p->subj:"";
802
803 /*
804 ** (ii) If the extracted subject is empty, skip this
805 ** message.
806 */
807
808 if (*subj == 0)
809 continue;
810
811 /*
812 ** (iii) Lookup the message associated with this extracted
813 ** subject in the table.
814 */
815
816 if (findsubj(mt, subj, &isrefwd, 0, &subjtop) || subjtop == 0)
817 return (-1);
818
819 /*
820 ** (iv) If the message in the table is the current message,
821 ** skip it.
822 */
823
824 /* NOTE - ptr comparison IS NOT LEGAL */
825
826 subjtop->msg->flag2=1;
827 if (toproot->flag2)
828 {
829 toproot->flag2=0;
830 continue;
831 }
832 subjtop->msg->flag2=0;
833
834 /*
835 ** Otherwise, merge the current message with the one in the
836 ** table using the following rules:
837 **
838 ** If both messages are dummies, append the current
839 ** message's children to the children of the message in
840 ** the table (the children of both messages become
841 ** siblings), and then delete the current message.
842 */
843
844 if (subjtop->msg->isdummy && toproot->isdummy)
845 {
846 while ((p=toproot->firstchild) != 0)
847 {
848 breakparent(p);
849 linkparent(p, subjtop->msg);
850 }
851 breakparent(toproot);
852 continue;
853 }
854
855 /*
856 ** If the message in the table is a dummy and the current
857 ** message is not, make the current message a child of
858 ** the message in the table (a sibling of it's children).
859 */
860
861 if (subjtop->msg->isdummy)
862 {
863 breakparent(toproot);
864 linkparent(toproot, subjtop->msg);
865 continue;
866 }
867
868 /*
869 ** If the current message is a reply or forward and the
870 ** message in the table is not, make the current message
871 ** a child of the message in the table (a sibling of it's
872 ** children).
873 */
874
875 if (isrefwd)
876 {
877 p=subjtop->msg;
878 if (p->isdummy)
879 p=p->firstchild;
880
881 subj=p->subj ? p->subj:"";
882
883 str=rfc822_coresubj(subj, &isrefwd);
884
885 if (!str)
886 return (-1);
887 free(str); /* Don't really care */
888
889 if (!isrefwd)
890 {
891 breakparent(toproot);
892 linkparent(toproot, subjtop->msg);
893 continue;
894 }
895 }
896
897 /*
898 ** Otherwise, create a new dummy container and make both
899 ** messages children of the dummy, and replace the
900 ** message in the table with the dummy message.
901 */
902
903 /* What we do is create a new message, then move the
904 ** contents of subjtop->msg (including its children)
905 ** to the new message, then make the new message a child
906 ** of subjtop->msg, and mark subjtop->msg as a dummy msg.
907 */
908
909 q=rfc822_threadallocmsg(mt, "(dummy)");
910 if (!q)
911 return (-1);
912
913 q->isdummy=1;
914
915 swapmsgdata(q, subjtop->msg);
916
917 while ((p=subjtop->msg->firstchild) != 0)
918 {
919 breakparent(p);
920 linkparent(p, q);
921 }
922 linkparent(q, subjtop->msg);
923
924 breakparent(toproot);
925 linkparent(toproot, subjtop->msg);
926 }
927 return (0);
928}
929
930/*
931** (6) Traverse the messages under the root and sort each set of
932** siblings by sent date. Traverse the messages in such a way
933** that the "youngest" set of siblings are sorted first, and the
934** "oldest" set of siblings are sorted last (grandchildren are
935** sorted before children, etc). In the case of an exact match on
936** sent date or if either of the Date: headers used in a
937** comparison can not be parsed, use the order in which the
938** messages appear in the mailbox (that is, by sequence number) to
939** determine the order. In the case of a dummy message (which can
940** only occur with top-level siblings), use its first child for
941** sorting.
942*/
943
944static int cmp_msgs(const void *a, const void *b)
945{
946 struct imap_refmsg *ma=*(struct imap_refmsg **)a;
947 struct imap_refmsg *mb=*(struct imap_refmsg **)b;
948 time_t ta, tb;
949 unsigned long na, nb;
950
951 while (ma && ma->isdummy)
952 ma=ma->firstchild;
953
954 while (mb && mb->isdummy)
955 mb=mb->firstchild;
956
957 ta=tb=0;
958 na=nb=0;
959 if (ma)
960 {
961 ta=ma->timestamp;
962 na=ma->seqnum;
963 }
964 if (mb)
965 {
966 tb=mb->timestamp;
967 nb=mb->seqnum;
968 }
969
970 return (ta && tb && ta != tb ? ta < tb ? -1: 1:
971 na < nb ? -1: na > nb ? 1:0);
972}
973
974struct imap_threadsortinfo {
975 struct imap_refmsgtable *mt;
976 struct imap_refmsg **sort_table;
977 size_t sort_table_cnt;
978} ;
979
980static int dothreadsort(struct imap_threadsortinfo *,
981 struct imap_refmsg *);
982
983int rfc822_threadsortbydate(struct imap_refmsgtable *mt)
984{
985 struct imap_threadsortinfo itsi;
986 int rc;
987
988 itsi.mt=mt;
989 itsi.sort_table=0;
990 itsi.sort_table_cnt=0;
991
992 rc=dothreadsort(&itsi, mt->rootptr);
993
994 if (itsi.sort_table)
995 free(itsi.sort_table);
996 return (rc);
997}
998
999static int dothreadsort(struct imap_threadsortinfo *itsi,
1000 struct imap_refmsg *p)
1001{
1002 struct imap_refmsg *q;
1003 size_t i, n;
1004
1005 for (q=p->firstchild; q; q=q->nextsib)
1006 dothreadsort(itsi, q);
1007
1008 n=0;
1009 for (q=p->firstchild; q; q=q->nextsib)
1010 ++n;
1011
1012 if (n > itsi->sort_table_cnt)
1013 {
1014 struct imap_refmsg **new_array=(struct imap_refmsg **)
1015 (itsi->sort_table ?
1016 realloc(itsi->sort_table,
1017 sizeof(struct imap_refmsg *)*n)
1018 :malloc(sizeof(struct imap_refmsg *)*n));
1019
1020 if (!new_array)
1021 return (-1);
1022
1023 itsi->sort_table=new_array;
1024 itsi->sort_table_cnt=n;
1025 }
1026
1027 n=0;
1028 while ((q=p->firstchild) != 0)
1029 {
1030 breakparent(q);
1031 itsi->sort_table[n++]=q;
1032 }
1033
1034 qsort(itsi->sort_table, n, sizeof(struct imap_refmsg *), cmp_msgs);
1035
1036 for (i=0; i<n; i++)
1037 linkparent(itsi->sort_table[i], p);
1038 return (0);
1039}
1040
1041struct imap_refmsg *rfc822_thread(struct imap_refmsgtable *mt)
1042{
1043 if (!mt->rootptr)
1044 {
1045 rfc822_threadprune(mt);
1046 if ((mt->rootptr=rfc822_threadgetroot(mt)) == 0)
1047 return (0);
1048 if (rfc822_threadsortsubj(mt->rootptr) ||
1049 rfc822_threadgathersubj(mt, mt->rootptr) ||
1050 rfc822_threadmergesubj(mt, mt->rootptr) ||
1051 rfc822_threadsortbydate(mt))
1052 {
1053 mt->rootptr=0;
1054 return (0);
1055 }
1056 }
1057
1058 return (mt->rootptr);
1059}