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