1 // Copyright 2009 The Archiveopteryx Developers <info@aox.org>
2 
3 #include "injector.h"
4 
5 #include "map.h"
6 #include "dict.h"
7 #include "flag.h"
8 #include "query.h"
9 #include "timer.h"
10 #include "address.h"
11 #include "message.h"
12 #include "ustring.h"
13 #include "mailbox.h"
14 #include "bodypart.h"
15 #include "datefield.h"
16 #include "mimefields.h"
17 #include "messagecache.h"
18 #include "helperrowcreator.h"
19 #include "addressfield.h"
20 #include "transaction.h"
21 #include "annotation.h"
22 #include "postgres.h"
23 #include "session.h"
24 #include "scope.h"
25 #include "graph.h"
26 #include "html.h"
27 #include "md5.h"
28 #include "utf.h"
29 #include "log.h"
30 #include "dsn.h"
31 
32 
33 static GraphableCounter * successes;
34 static GraphableCounter * failures;
35 
36 
37 struct BodypartRow
38     : public Garbage
39 {
BodypartRowBodypartRow40     BodypartRow()
41         : id( 0 ), text( 0 ), data( 0 ), bytes( 0 )
42     {}
43 
44     uint id;
45     EString hash;
46     EString * text;
47     EString * data;
48     uint bytes;
49     List<Bodypart> bodyparts;
50 };
51 
52 
53 // The following is everything the Injector needs to do its work.
54 
55 enum State {
56     Inactive,
57     CreatingMailboxes,
58     FindingDependencies,
59     CreatingDependencies,
60     ConvertingInReplyTo, AddingMoreReferences,
61     ConvertingThreadIndex,
62     CreatingThreadRoots,
63     InsertingBodyparts,
64     SelectingMessageIds, SelectingUids,
65     InsertingMessages,
66     AwaitingCompletion, Done
67 };
68 
69 
70 class InjectorData
71     : public Garbage
72 {
73 public:
InjectorData()74     InjectorData()
75         : owner( 0 ),
76           state( Inactive ), failed( false ), retried( 0 ), transaction( 0 ),
77           mailboxesCreated( 0 ),
78           fieldNameCreator( 0 ), flagCreator( 0 ), annotationNameCreator( 0 ),
79           lockUidnext( 0 ), select( 0 ), insert( 0 ),
80           substate( 0 ), subtransaction( 0 ),
81           findParents( 0 ), findReferences( 0 ),
82           findBlah( 0 ), findMessagesInOutlookThreads( 0 ),
83           threads( 0 )
84     {}
85 
86     struct Delivery
87         : public Garbage
88     {
DeliveryInjectorData::Delivery89         Delivery( Injectee * m, Address * a, List<Address> * l, Date * when )
90             : message( m ), sender( a ), recipients( l ), later( when )
91         {}
92 
93         Injectee * message;
94         Address * sender;
95         List<Address> * recipients;
96         Date * later;
97     };
98 
99     List<Injectee> messages;
100     List<Injectee> injectables;
101     List<Delivery> deliveries;
102 
103     EventHandler * owner;
104 
105     State state;
106     bool failed;
107     bool retried;
108 
109     Transaction *transaction;
110 
111     EStringList flags;
112     EStringList fields;
113     EStringList annotationNames;
114     UStringList baseSubjects;
115     Dict<Address> addresses;
116     List< ::Mailbox > * mailboxesCreated;
117 
118     struct Mailbox
119         : public Garbage
120     {
MailboxInjectorData::Mailbox121         Mailbox( ::Mailbox * m ): Garbage(), mailbox( m ) {}
122         ::Mailbox * mailbox;
123         List<Injectee> messages;
124     };
125 
126     Map<Mailbox> mailboxes;
127 
128     HelperRowCreator * fieldNameCreator;
129     HelperRowCreator * flagCreator;
130     HelperRowCreator * annotationNameCreator;
131 
132     Query * lockUidnext;
133     Query * select;
134     Query * insert;
135 
136     uint substate;
137     Transaction * subtransaction;
138 
139     Dict<BodypartRow> hashes;
140     List<BodypartRow> bodyparts;
141 
142     // for convertInReplyTo()
143     Dict< List<Message> > outlooks;
144     Map<EString> outlookParentIds;
145     Query * findParents;
146     Query * findReferences;
147     // for convertThreadIndex()
148     Query * findBlah;
149     Query * findMessagesInOutlookThreads;
150 
151     struct ThreadParentInfo
152         : public Garbage
153     {
154     public:
ThreadParentInfoInjectorData::ThreadParentInfo155         ThreadParentInfo(): Garbage() {}
156 
157         EString references;
158         EString messageId;
159     };
160 
161     struct ThreadInjectee
162         : public ThreadRootCreator::Message
163     {
164     public:
ThreadInjecteeInjectorData::ThreadInjectee165         ThreadInjectee( Injectee * i, Transaction * tr )
166             : ThreadRootCreator::Message(), m( i ), t( tr ) {}
167 
168         Injectee * m;
169         Transaction * t;
170 
referencesInjectorData::ThreadInjectee171         EStringList references() const {
172             EStringList result;
173             AddressField * r = 0;
174             Header * h = m->header();
175             if ( h )
176                 r = h->addressField( HeaderField::References );
177             if ( r ) {
178                 List<Address>::Iterator i( r->addresses() );
179                 while ( i ) {
180                     if ( !i->lpdomain().isEmpty() )
181                         result.append( "<" + i->lpdomain() + ">" );
182                     ++i;
183                 }
184             }
185             return result;
186         }
187 
messageIdInjectorData::ThreadInjectee188         EString messageId() const {
189             Header * h = m->header();
190             if ( h )
191                 return h->messageId();
192             return "";
193         }
194     };
195 
196     ThreadRootCreator * threads;
197 };
198 
199 
200 /*! \class Injector injector.h
201     Stores message objects in the database.
202 
203     This class takes a list of Message objects and performs the database
204     operations necessary to inject them into their respective mailboxes.
205     Injection commences only when execute() is called.
206 */
207 
208 
209 /*! Creates a new Injector to inject messages into the database on
210     behalf of the \a owner, which is notified when the injection is
211     completed.
212 */
213 
Injector(EventHandler * owner)214 Injector::Injector( EventHandler * owner )
215     : d( new InjectorData )
216 {
217     if ( !::successes ) {
218         ::failures = new GraphableCounter( "injection-errors" );
219         ::successes = new GraphableCounter( "messages-injected" );
220     }
221 
222     d->owner = owner;
223 }
224 
225 
226 /*!  Notes that \a messages must be injected into the database. */
227 
addInjection(List<Injectee> * messages)228 void Injector::addInjection( List<Injectee> * messages )
229 {
230     if ( !messages || messages->isEmpty() )
231         return;
232 
233     List<Injectee>::Iterator i( messages );
234     while ( i ) {
235         d->injectables.append( i );
236         ++i;
237     }
238 }
239 
240 
241 /*! Notes that \a message must be injected, and spooled for delivery
242     to the specified \a recipients from the given \a sender, and
243     delivered \a later if \a later is non-null.
244 */
245 
addDelivery(Injectee * message,Address * sender,List<Address> * recipients,Date * later)246 void Injector::addDelivery( Injectee * message, Address * sender,
247                             List<Address> * recipients,
248                             Date * later )
249 {
250     d->deliveries.append( new InjectorData::Delivery( message, sender,
251                                                       recipients, later ) );
252 }
253 
254 
255 /*! Returns true if this injector has finished its work, and false if it
256     hasn't started or is currently working.
257 */
258 
done() const259 bool Injector::done() const
260 {
261     return ( d->failed || d->state == Done );
262 }
263 
264 
265 /*! Returns true if this injection failed, and false if it has succeeded
266     or is in progress.
267 */
268 
failed() const269 bool Injector::failed() const
270 {
271     return d->failed;
272 }
273 
274 
275 /*! Returns an error message if injection failed, or an empty string
276     if it succeeded or hasn't failed yet.
277 */
278 
error() const279 EString Injector::error() const
280 {
281     if ( !d->failed )
282         return "";
283 
284     List<Injectee>::Iterator it( d->messages );
285     while ( it ) {
286         Message * m = it;
287         if ( !m->valid() )
288             return m->error();
289         ++it;
290     }
291 
292     if ( !d->transaction )
293         return "";
294     return d->transaction->error();
295 }
296 
297 
298 /*! This private function advances the injector to the next state. */
299 
next()300 void Injector::next()
301 {
302     d->state = (State)(d->state + 1);
303 }
304 
305 
306 /*! Instructs this Injector to use a subtransaction of \a t for all
307     its database work.
308 
309     Does nothing if the injector already has a transaction.
310 */
311 
setTransaction(class Transaction * t)312 void Injector::setTransaction( class Transaction * t )
313 {
314     if ( t && !d->transaction )
315         d->transaction = t->subTransaction( this );
316 }
317 
318 
execute()319 void Injector::execute()
320 {
321     Scope x( log() );
322 
323     State last;
324 
325     // We start in state Inactive, and execute the functions responsible
326     // for making progress in each state. If they change the state using
327     // next(), we restart the loop; otherwise we wait for callbacks. We
328     // check for errors after each call, so the functions don't need to
329     // do anything about errors, other than returning early or setting
330     // d->failed (if the error doesn't affect the Transaction).
331 
332     do {
333         last = d->state;
334         switch ( d->state ) {
335         case Inactive:
336             findMessages();
337             logDescription();
338             if ( d->messages.isEmpty() ) {
339                 d->state = Done;
340             }
341             else {
342                 if ( !d->transaction )
343                     d->transaction = new Transaction( this );
344                 next();
345             }
346             break;
347 
348         case CreatingMailboxes:
349             createMailboxes();
350             break;
351 
352         case FindingDependencies:
353             findDependencies();
354             next();
355             break;
356 
357         case CreatingDependencies:
358             createDependencies();
359             break;
360 
361         case ConvertingInReplyTo:
362             convertInReplyTo();
363             break;
364 
365         case AddingMoreReferences:
366             addMoreReferences();
367             next();
368             break;
369 
370         case ConvertingThreadIndex:
371             convertThreadIndex();
372             break;
373 
374         case CreatingThreadRoots:
375             insertThreadRoots();
376             next();
377             break;
378 
379         case InsertingBodyparts:
380             insertBodyparts();
381             break;
382 
383         case SelectingMessageIds:
384             selectMessageIds();
385             break;
386 
387         case SelectingUids:
388             selectUids();
389             break;
390 
391         case InsertingMessages:
392             insertMessages();
393             insertDeliveries();
394             insertThreadIndexes();
395             next();
396             if ( !d->mailboxes.isEmpty() ) {
397                 cache();
398                 Mailbox::refreshMailboxes( d->transaction );
399             }
400             d->transaction->commit();
401             break;
402 
403         case AwaitingCompletion:
404             if ( !d->transaction->done() )
405                 return;
406 
407             if ( d->failed || d->transaction->failed() ) {
408                 ::failures->tick();
409                 Cache::clearAllCaches( false );
410             }
411             else {
412                 ::successes->tick();
413             }
414 
415             next();
416             break;
417 
418         case Done:
419             break;
420         }
421 
422         if ( !d->failed && d->transaction )
423             d->failed = d->transaction->failed();
424     }
425     while ( last != d->state && d->state != Done && !d->failed );
426 
427     if ( d->state == Done && d->owner ) {
428         if ( d->failed )
429             log( "Injection failed: " + error() );
430         else
431             log( "Injection succeeded" );
432 
433         // We don't want to notify the owner multiple times if we
434         // aborted early and continue to get callbacks for failed
435         // queries.
436 
437         EventHandler * owner = d->owner;
438         d->owner = 0;
439         owner->notify();
440     }
441 }
442 
443 
444 /*! This private helper makes a master list of messages to be
445     inserted, based on what addDelivery() and addInjection() have
446     done.
447 */
448 
findMessages()449 void Injector::findMessages()
450 {
451     PatriciaTree<Injectee> unique;
452     List<Injectee>::Iterator im( d->injectables );
453     while ( im ) {
454         Injectee * m = im;
455         if ( !unique.find( (const char *)&m, sizeof(m) * 8 ) ) {
456             unique.insert( (const char *)&m, sizeof(m) * 8, m );
457             d->messages.append( m );
458         }
459         ++im;
460     }
461     List<InjectorData::Delivery>::Iterator dm( d->deliveries );
462     while ( dm ) {
463         Injectee * m = dm->message;
464         if ( !unique.find( (const char *)&m, sizeof(m) * 8 ) ) {
465             unique.insert( (const char *)&m, sizeof(m) * 8, m );
466             d->messages.append( m );
467         }
468         ++dm;
469     }
470     log( "Injecting " + fn( d->messages.count() ) + " messages (" +
471          fn( d->injectables.count() ) + ", " +
472          fn( d->deliveries.count() ) + ")", Log::Debug );
473 }
474 
475 
476 /*! This private function looks through the list of messages, notes
477     what mailboxes are needed, and creates any that do not exist or
478     are currently deleted.
479 */
480 
createMailboxes()481 void Injector::createMailboxes()
482 {
483     if ( !d->mailboxesCreated ) {
484         d->mailboxesCreated = new List<Mailbox>;
485         UDict<Mailbox> nonexistent;
486         List<Injectee>::Iterator imi( d->injectables );
487         while ( imi ) {
488             Injectee * m = imi;
489             ++imi;
490 
491             List<Mailbox>::Iterator mi( m->mailboxes() );
492             while ( mi ) {
493                 Mailbox * mb = mi;
494                 ++mi;
495                 if ( mb->deleted() && !nonexistent.contains( mb->name() ) ) {
496                     mb->create( d->transaction, 0 );
497                     d->mailboxesCreated->append( mb );
498                     nonexistent.insert( mb->name(), mb );
499                 }
500             }
501         }
502         if ( !d->mailboxesCreated->isEmpty() ) {
503             Mailbox::refreshMailboxes( d->transaction );
504         }
505     }
506     List<Mailbox>::Iterator m( d->mailboxesCreated );
507     while ( m ) {
508         if ( m->deleted() )
509             return;
510         ++m;
511     }
512     next();
513 }
514 
515 
516 /*! This private function looks through the list of messages given to
517     this Injector, to make sure that they are all valid, and to collect
518     lists of any unknown header field names, flags, annotation names, or
519     addresses.
520 
521     In the common case there will be few, if any, entries to insert into
522     the *_names tables, so we build lists of them without worrying about
523     memory use. The list of addresses may be large, but we can't avoid
524     building that list anyway.
525 */
526 
findDependencies()527 void Injector::findDependencies()
528 {
529     Dict<Injector> seenFields;
530 
531     List<Header> * l = new List<Header>;
532 
533     List<Injectee>::Iterator it( d->messages );
534     while ( it ) {
535         Message * m = it;
536         ++it;
537 
538         if ( !m->valid() ) {
539             d->failed = true;
540             return;
541         }
542 
543         // Collect the headers for this message.
544 
545         l->clear();
546         l->append( m->header() );
547         List<Bodypart>::Iterator bi( m->allBodyparts() );
548         while ( bi ) {
549             Bodypart *bp = bi;
550             l->append( bp->header() );
551             if ( bp->message() )
552                 l->append( bp->message()->header() );
553             ++bi;
554         }
555 
556         // And then step through them, looking for unknown fields and
557         // address fields.
558 
559         List<Header>::Iterator hi( l );
560         while ( hi ) {
561             Header * hdr = hi;
562             List< HeaderField >::Iterator fi( hdr->fields() );
563             while ( fi ) {
564                 HeaderField *hf = fi;
565                 EString n( hf->name() );
566 
567                 if ( hf->type() >= HeaderField::Other &&
568                      !seenFields.contains( n ) )
569                 {
570                     d->fields.append( n );
571                     seenFields.insert( n, this );
572                 }
573 
574                 if ( hf->type() <= HeaderField::LastAddressField )
575                     updateAddresses( ((AddressField *)hf)->addresses() );
576 
577                 ++fi;
578             }
579             ++hi;
580         }
581     }
582 
583     List<Injectee>::Iterator imi( d->injectables );
584     while ( imi ) {
585         Injectee * m = imi;
586         ++imi;
587 
588         // Then look through this message's mailboxes to find any
589         // unknown flags or annotation names; and to build a list
590         // of unique mailboxes for use later.
591 
592         List<Mailbox>::Iterator mi( m->mailboxes() );
593         while ( mi ) {
594             Mailbox * mb = mi;
595             if ( !mb->id() || mb->deleted() )
596                 log( "Internal error: Mailbox " + mb->name().ascii() +
597                      " is not properly known", Log::Disaster );
598             InjectorData::Mailbox * mbc = d->mailboxes.find( mb->id() );
599             if ( !mbc ) {
600                 mbc = new InjectorData::Mailbox( mb );
601                 d->mailboxes.insert( mb->id(), mbc );
602             }
603             mbc->messages.append( m );
604 
605             EStringList::Iterator fi( m->flags( mb ) );
606             while ( fi ) {
607                 d->flags.append( fi );
608                 ++fi;
609             }
610 
611             List<Annotation>::Iterator ai( m->annotations( mb ) );
612             while ( ai ) {
613                 Annotation * a = ai;
614                 d->annotationNames.append( a->entryName() );
615                 ++ai;
616             }
617 
618             ++mi;
619         }
620     }
621 
622     d->flags.removeDuplicates();
623     d->annotationNames.removeDuplicates( true );
624     d->baseSubjects.removeDuplicates( true );
625 
626     // Rows destined for deliveries/delivery_recipients also contain
627     // addresses that need to be looked up.
628 
629     List<Address> * senders = new List<Address>;
630     List<InjectorData::Delivery>::Iterator di( d->deliveries );
631     while ( di ) {
632         senders->append( di->sender );
633         updateAddresses( di->recipients );
634         ++di;
635     }
636     updateAddresses( senders );
637 }
638 
639 
640 /*! Adds previously unknown addresses from \a newAddresses to
641     d->addresses. */
642 
updateAddresses(List<Address> * newAddresses)643 void Injector::updateAddresses( List<Address> * newAddresses )
644 {
645     List<Address>::Iterator ai( newAddresses );
646     while ( ai ) {
647         Address * a = ai;
648         ++ai;
649         EString k = AddressCreator::key( a );
650         d->addresses.insert( k, a );
651     }
652 }
653 
654 
655 /*! Ensures that \a a is present in the database after injection. */
656 
addAddress(Address * a)657 void Injector::addAddress( Address * a )
658 {
659     EString k = AddressCreator::key( a );
660     d->addresses.insert( k, a );
661 }
662 
663 
664 /*! Returns the database ID of \a a, or 0 if this injector hasn't added
665     \a a to the database.
666 */
667 
addressId(Address * a)668 uint Injector::addressId( Address * a )
669 {
670     Address * a2 = d->addresses.find( AddressCreator::key( a ) );
671     if ( !a2 )
672         return 0;
673     return a2->id();
674 }
675 
676 
677 /*! This function creates any unknown names found by
678     findDependencies().  It creates up to four subtransactions and
679     advances to the next state, trusting Transaction to queue the work
680     appropriately.
681 */
682 
createDependencies()683 void Injector::createDependencies()
684 {
685     if ( !d->fields.isEmpty() ) {
686         d->fieldNameCreator =
687             new FieldNameCreator( d->fields, d->transaction );
688         d->fieldNameCreator->execute();
689     }
690 
691     if ( !d->flags.isEmpty() ) {
692         d->flagCreator = new FlagCreator( d->flags, d->transaction );
693         d->flagCreator->execute();
694     }
695 
696     if ( !d->annotationNames.isEmpty() ) {
697         d->annotationNameCreator =
698             new AnnotationNameCreator( d->annotationNames, d->transaction );
699         d->annotationNameCreator->execute();
700     }
701 
702     if ( !d->addresses.isEmpty() ) {
703         AddressCreator * ac
704             = new AddressCreator( &d->addresses, d->transaction );
705         ac->execute();
706     }
707 
708     next();
709 }
710 
711 
712 /*! Creates a proper References field for any messages which have
713     In-Reply-To but not References. This covers some versions of
714     Outlook, but not all.
715 */
716 
convertInReplyTo()717 void Injector::convertInReplyTo()
718 {
719     EStringList ids;
720     if ( d->outlooks.isEmpty() ) {
721         List<Injectee>::Iterator i( d->messages );
722         while ( i ) {
723             Header * h = i->header();
724             if ( !h->field( HeaderField::References ) ) {
725                 // this mostly catches outlook, but will also catch a
726                 // few other senders
727                 EString irt = h->inReplyTo();
728                 int lt = -1;
729                 do {
730                     // we look at each possible message-id in the
731                     // in-reply-to field, not just the first or last
732                     lt = irt.find( '<', lt + 1 );
733                     int gt = irt.find( '>', lt );
734                     if ( lt >= 0 && gt > lt ) {
735                         AddressParser ap( irt.mid( lt, gt + 1 - lt ) );
736                         ap.assertSingleAddress();
737                         if ( ap.error().isEmpty() ) {
738                             // there is a message-id, so map from it
739                             // to the message(s) that cite it as a
740                             // possible parent
741                             Address * a = ap.addresses()->firstElement();
742                             EString msgid = "<" + a->lpdomain() + ">";
743                             if ( !d->outlooks.contains( msgid ) )
744                                 d->outlooks.insert( msgid, new List<Message> );
745                             d->outlooks.find( msgid )->append( i );
746                             ids.append( msgid );
747                         }
748                     }
749                 } while ( lt > 0 );
750             }
751             ++i;
752         }
753         if ( ids.isEmpty() ) {
754             // no message-ids found? skip the rest then
755             next();
756             return;
757         }
758     }
759 
760     if ( !d->findParents ) {
761         // send a query to find messages.id for each message-id we
762         // found above
763         d->findParents = new Query( "", this );
764         EString s = "select message, value "
765                     "from header_fields "
766                     "where field=";
767         s.appendNumber( HeaderField::MessageId );
768         if ( ids.count() < 100 ) {
769             s.append( " and (" );
770             bool first = true;
771             EStringList::Iterator i( ids );
772             uint n = 1;
773             while ( i ) {
774                 // use a series of value=$n instead of value=any($1),
775                 // because postgres uses a bad plan for the latter.
776                 if ( !first )
777                     s.append( " or " );
778                 first = false;
779                 s.append( "value=$" );
780                 s.appendNumber( n );
781                 d->findParents->bind( n, *i );
782                 n++;
783                 ++i;
784             }
785             s.append( ")" );
786         }
787         else {
788             s.append( " and value=any($1::text[])" );
789             d->findParents->bind( 1, ids );
790         }
791         d->findParents->setString( s );
792         d->transaction->enqueue( d->findParents );
793         d->transaction->execute();
794     }
795 
796     if ( !d->findParents->done() )
797         return;
798 
799     if ( !d->findReferences ) {
800         // once we've found  the message-ids and messages.id, map from
801         // the latter to the former so we can retrieve them
802         IntegerSet parents;
803         while ( d->findParents->hasResults() ) {
804             Row * r = d->findParents->nextRow();
805             d->outlookParentIds.insert( r->getInt( "message" ),
806                                         new EString(r->getEString( "value") ) );
807             parents.add( r->getInt( "message" ) );
808         }
809         if ( parents.isEmpty() ) {
810             // those message-ids weren't in the database? ok
811             next();
812             return;
813         }
814         // and send a new query to retrieve the References in those messages
815         d->findReferences = new Query( "select message, value "
816                                        "from header_fields "
817                                        "where message=any($1) and field=" +
818                                        fn( HeaderField::References ),
819                                        this );
820         d->findReferences->bind( 1, parents );
821         d->transaction->enqueue( d->findReferences );
822         d->transaction->execute();
823         // it would have been better to send both as one query, but
824         // postgres misplanned all our attempts to do that
825     }
826 
827     while ( d->findReferences->hasResults() ) {
828         Row * r = d->findReferences->nextRow();
829         // we have the references field, and just for sanity
830         EString *msgid = d->outlookParentIds.find( r->getInt( "message" ) );
831         if ( msgid ) {
832             // we have the message-id and the references field, so
833             // make a new child references
834             EString ref = r->getEString( "value" );
835             ref.append( " " );
836             ref.append( *msgid );
837             ref = ref.simplified().wrapped( 60, "", " ", false );
838             // ... and use that for each of the messages that claim to
839             // have this antecedent
840             List<Message>::Iterator m( d->outlooks.find( *msgid ) );
841             while ( m ) {
842                 if ( !m->header()->field( HeaderField::References ) )
843                     m->header()->add( "References", ref );
844                 ++m;
845             }
846         }
847     }
848 
849     if ( d->findReferences && !d->findReferences->done() )
850         return;
851 
852     next();
853 }
854 
855 
856 /*! Like convertInReplyTo(), except that it looks at other messages
857     being injected rather than messages already in the database.
858 
859     This is a no-op when messages are inserted using SMTP or LMTP, but
860     can matter for aoximport.
861 */
862 
addMoreReferences()863 void Injector::addMoreReferences()
864 {
865     List<Message> queue;
866     List<Injectee>::Iterator m( d->messages );
867     while ( m ) {
868         if ( m->header()->field( HeaderField::References ) )
869             queue.append( m );
870         ++m;
871     }
872 
873     while ( !queue.isEmpty() ) {
874         Message * parent = queue.shift();
875         EString msgid = parent->header()->messageId();
876         EString r = parent->header()->
877                     field( HeaderField::References )->rfc822( false );
878         r.append( " " );
879         r.append( msgid );
880         r = r.simplified().wrapped( 60, "", " ", false );
881         List<Message>::Iterator child( d->outlooks.find( msgid ) );
882         while ( child ) {
883             if ( !child->header()->field( HeaderField::References ) ) {
884                 child->header()->add( "References", r );
885                 queue.append( child );
886             }
887             ++child;
888         }
889     }
890 
891     d->outlooks.clear();
892 }
893 
894 
895 /*! Creates a proper References field for any messages sent by
896     Outlook*, ie. having Thread-Index instead of References.
897 */
898 
convertThreadIndex()899 void Injector::convertThreadIndex()
900 {
901     EStringList ids;
902     if ( d->outlooks.isEmpty() ) {
903         List<Injectee>::Iterator i( d->messages );
904         while ( i ) {
905             Header * h = i->header();
906             if ( !h->field( HeaderField::References ) ) {
907                 HeaderField * ti = h->field( "Thread-Index" );
908                 if ( ti ) {
909                     EString t = ti->value().utf8().de64();
910                     if ( t.length() > 22 ) {
911                         t = t.mid( 0, 22 ).e64();
912                         ids.append( t );
913                         if ( !d->outlooks.contains( t ) )
914                             d->outlooks.insert( t, new List<Message> );
915                         d->outlooks.find( t )->append( i );
916                     }
917                 }
918             }
919             ++i;
920         }
921         if ( ids.isEmpty() ) {
922             // no thread-indexes need fixing? skip the rest then
923             next();
924             return;
925         }
926 
927         ids.removeDuplicates();
928         d->findBlah = new Query( "select message "
929                                  "from thread_indexes "
930                                  "where thread_index=any($1::text[])", this );
931         d->findBlah->bind( 1, ids );
932         d->transaction->enqueue( d->findBlah );
933         d->transaction->execute();
934     }
935 
936     if ( !d->findMessagesInOutlookThreads ) {
937         if ( !d->findBlah->done() )
938             return;
939 
940         IntegerSet ante;
941         while ( d->findBlah->hasResults() ) {
942             Row * r = d->findBlah->nextRow();
943             ante.add( r->getInt( "message" ) );
944         }
945 
946         d->findMessagesInOutlookThreads
947             = new Query( "select message, field, value "
948                          "from header_fields "
949                          "where message=any($1) and part='' and ("
950                          "field=" + fn ( HeaderField::MessageId ) + " or "
951                          "field=" + fn ( HeaderField::References ) + " or "
952                          "field=" + fn ( d->fieldNameCreator->id(
953                                              "Thread-Index" ) ) + ") "
954                          "order by field desc",
955                          this );
956         d->findMessagesInOutlookThreads->bind( 1, ante );
957         d->transaction->enqueue( d->findMessagesInOutlookThreads );
958         d->transaction->execute();
959     }
960 
961     if ( !d->findMessagesInOutlookThreads->done() )
962         return;
963 
964     Dict<InjectorData::ThreadParentInfo> antecedents;
965     Map<InjectorData::ThreadParentInfo> antecedents2;
966 
967     while ( d->findMessagesInOutlookThreads->hasResults() ) {
968         Row * r = d->findMessagesInOutlookThreads->nextRow();
969         uint m = r->getInt( "message" );
970         uint field = r->getInt( "field" );
971         InjectorData::ThreadParentInfo * t = antecedents2.find( m );
972         if ( field == HeaderField::MessageId ) {
973             if ( t )
974                 t->messageId = r->getEString( "value" );
975         }
976         else if ( field == HeaderField::References ) {
977             if ( t )
978                 t->references = r->getEString( "value" );
979         }
980         else if ( !t ) {
981             // this will be run first because of "order by field desc" above
982             t = new InjectorData::ThreadParentInfo;
983             antecedents.insert( r->getEString( "value" ), t );
984             log( "antecedent <" + r->getEString( "value" ) + ">", Log::Debug );
985             antecedents2.insert( m, t );
986         }
987     }
988 
989     // at this time, we know the message-id, references and
990     // thread-index for a bunch of messages. if possible, we want to
991     // construct a references field for each message in outlooks now.
992 
993     Dict< List<Message> >::Iterator i( d->outlooks );
994     while ( i ) {
995         List<Message>::Iterator m( *i );
996         while ( m ) {
997             EString ref;
998             // we need to look for the full thread-index (indicating both
999             // thread and position within thread)
1000             HeaderField * ti = m->header()->field( "Thread-Index" );
1001             EString t = ti->value().utf8().de64();
1002             // we also look for the parent's and grandparent's thread-index
1003             EString pt = t.mid( 0, ( (t.length() - 22 - 1) / 5 ) * 5 + 22 );
1004             InjectorData::ThreadParentInfo * tpi
1005                 = antecedents.find( pt.e64() );
1006             if ( tpi ) {
1007                 // we have the parent's information
1008                 ref = tpi->references;
1009                 ref.append( " " );
1010                 ref.append( tpi->messageId );
1011             }
1012             if ( !tpi && t.length() > 27 ) {
1013                 // we don't, but maybe there is a grandparent?
1014                 EString gt = t.mid( 0, ( (t.length() - 22 - 6) / 5 ) * 5 + 22 );
1015                 log( "considering <" + gt.e64() + ">", Log::Debug );
1016                 tpi = antecedents.find( gt.e64() );
1017             }
1018             if ( tpi && ref.isEmpty() ) {
1019                 // we have the grandparent's information, and there is an
1020                 // in-reply-to field, so maybe we have the parent's
1021                 // message-id as well.
1022                 HeaderField * irtf
1023                     = m->header()->field( HeaderField::InReplyTo );
1024                 EString irt;
1025                 if ( irtf )
1026                     irt = irtf->rfc822( false );
1027                 int lt = irt.find( '<' );
1028                 int gt = irt.find( '>', lt );
1029                 if ( lt >= 0 && gt > lt ) {
1030                     AddressParser ap( irt.mid( lt, gt + 1 - lt ) );
1031                     ap.assertSingleAddress();
1032                     if ( ap.error().isEmpty() ) {
1033                         // yes, we have the parent's message-id, or a
1034                         // plausible message-id anyway.
1035                         Address * a = ap.addresses()->firstElement();
1036 
1037                         ref = tpi->references;
1038                         ref.append( " " );
1039                         ref.append( tpi->messageId );
1040                         ref.append( " <" );
1041                         ref.append( a->lpdomain() );
1042                         ref.append( ">" );
1043                     }
1044                 }
1045             }
1046             if ( !ref.isEmpty() )
1047                 m->header()->add( "References",
1048                                   ref.simplified().wrapped( 60, "", " ",
1049                                                             false ) );
1050             ++m;
1051         }
1052         ++i;
1053     }
1054 
1055     d->outlooks.clear();
1056     next();
1057 }
1058 
1059 
1060 /*! Inserts rows into the thread_indexes table, so that
1061     convertThreadIndex() will have fodder next time it runs.
1062 */
1063 
insertThreadIndexes()1064 void Injector::insertThreadIndexes()
1065 {
1066     Query * q = new Query( "copy thread_indexes (message, thread_index) "
1067                            "from stdin with binary", 0 );
1068 
1069     List<Injectee>::Iterator m( d->messages );
1070     while ( m ) {
1071         HeaderField * ti = m->header()->field( "Thread-Index" );
1072         if ( ti ) {
1073             EString t = ti->value().utf8().de64();
1074             if ( t.length() >= 22 ) {
1075                 q->bind( 1, m->databaseId() );
1076                 q->bind( 2, t.mid( 0, 22 ).e64() );
1077                 q->submitLine();
1078             }
1079         }
1080         ++m;
1081     }
1082 
1083     d->transaction->enqueue( q );
1084 }
1085 
1086 
1087 /*! Inserts rows into the thread_roots table, so that insertMessages()
1088     can reference what it needs to.
1089 */
1090 
insertThreadRoots()1091 void Injector::insertThreadRoots()
1092 {
1093     List<ThreadRootCreator::Message> * l
1094         = new List<ThreadRootCreator::Message>;
1095     List<Injectee>::Iterator i( d->messages );
1096     while ( i ) {
1097         l->append( new InjectorData::ThreadInjectee( i, d->transaction ) );
1098         ++i;
1099     }
1100     d->threads = new ThreadRootCreator( l, d->transaction );
1101     d->threads->execute();
1102 }
1103 
1104 
1105 /*! Inserts all unique bodyparts in the messages into the bodyparts
1106     table, and updates the in-memory objects with the newly-created
1107     bodyparts.ids. */
1108 
insertBodyparts()1109 void Injector::insertBodyparts()
1110 {
1111     uint last;
1112 
1113     do {
1114         last = d->substate;
1115 
1116         if ( d->substate == 0 ) {
1117             List<Injectee>::Iterator it( d->messages );
1118             while ( it ) {
1119                 Message * m = it;
1120                 List<Bodypart>::Iterator bi( m->allBodyparts() );
1121                 while ( bi ) {
1122                     addBodypartRow( bi );
1123                     ++bi;
1124                 }
1125                 ++it;
1126             }
1127 
1128             if ( d->bodyparts.isEmpty() )
1129                 d->substate = 5;
1130             else
1131                 d->substate++;
1132         }
1133 
1134         if ( d->substate == 1 ) {
1135             Query * create =
1136                 new Query( "create temporary table bp ("
1137                            "bid integer, bytes integer, "
1138                            "hash text, text text, data bytea, "
1139                            "i integer, n boolean default 'f')", 0 );
1140 
1141             Query * copy =
1142                 new Query( "copy bp (bytes,hash,text,data,i) "
1143                            "from stdin with binary", this );
1144 
1145             uint i = 0;
1146             List<BodypartRow>::Iterator bi( d->bodyparts );
1147             while ( bi ) {
1148                 BodypartRow * br = bi;
1149 
1150                 copy->bind( 1, br->bytes );
1151                 copy->bind( 2, br->hash );
1152                 if ( br->text )
1153                     copy->bind( 3, *br->text );
1154                 else
1155                     copy->bindNull( 3 );
1156                 if ( br->data )
1157                     copy->bind( 4, *br->data );
1158                 else
1159                     copy->bindNull( 4 );
1160                 copy->bind( 5, i++ );
1161                 copy->submitLine();
1162 
1163                 ++bi;
1164             }
1165 
1166             d->transaction->enqueue( create );
1167             d->transaction->enqueue( copy );
1168             d->subtransaction = d->transaction->subTransaction( this );
1169 
1170             d->substate++;
1171         }
1172 
1173         if ( d->substate == 2 ) {
1174             Query * setId =
1175                 new Query( "update bp set bid=b.id from bodyparts b where "
1176                            "bp.hash=b.hash and not bp.text is distinct from "
1177                            "b.text and not bp.data is distinct from b.data",
1178                            0 );
1179 
1180             Query * setNew =
1181                 new Query( "update bp set bid=nextval('bodypart_ids')::int, "
1182                            "n='t' where bid is null", 0 );
1183 
1184             d->insert =
1185                 new Query( "insert into bodyparts "
1186                            "(id,bytes,hash,text,data) "
1187                            "select bid,bytes,hash,text,data "
1188                            "from bp where n", this );
1189 
1190             d->substate++;
1191             d->subtransaction->enqueue( setId );
1192             d->subtransaction->enqueue( setNew );
1193             d->subtransaction->enqueue( d->insert );
1194             d->subtransaction->execute();
1195         }
1196 
1197         if ( d->substate == 3 ) {
1198             if ( !d->insert->done() )
1199                 return;
1200 
1201             if ( d->insert->failed() ) {
1202                 // this will fail only if there is some kind of
1203                 // serious, serious failure, the kind where retrying
1204                 // will fail again.
1205                 d->subtransaction->commit();
1206                 d->substate = 100;
1207             }
1208             else {
1209                 d->substate++;
1210                 d->subtransaction->commit();
1211                 d->select =
1212                     new Query( "select bid from bp order by i", this );
1213                 d->transaction->enqueue( d->select );
1214                 d->transaction->enqueue( new Query( "drop table bp", 0 ) );
1215                 d->transaction->execute();
1216             }
1217         }
1218 
1219         if ( d->substate == 4 ) {
1220             if ( !d->select->done() )
1221                 return;
1222 
1223             List<BodypartRow>::Iterator bi( d->bodyparts );
1224             while ( bi ) {
1225                 BodypartRow * br = bi;
1226                 Row * r = d->select->nextRow();
1227                 uint id = r->getInt( "bid" );
1228 
1229                 List<Bodypart>::Iterator it( br->bodyparts );
1230                 while ( it ) {
1231                     it->setId( id );
1232                     ++it;
1233                 }
1234 
1235                 ++bi;
1236             }
1237             d->substate++;
1238         }
1239     }
1240     while ( last != d->substate );
1241 
1242     d->select = 0;
1243     d->insert = 0;
1244     next();
1245 }
1246 
1247 
1248 /*! Returns a new Query to select \a num nextval()s as "id" from the
1249     named \a sequence. */
1250 
selectNextvals(const EString & sequence,uint num)1251 Query * Injector::selectNextvals( const EString & sequence, uint num )
1252 {
1253     Query * q =
1254         new Query( "select nextval('" + sequence + "')::int as id "
1255                    "from generate_series(1,$1)", this );
1256     q->bind( 1, num );
1257     return q;
1258 }
1259 
1260 
1261 /*! Adds \a b to the list of bodyparts if it's not there already. */
1262 
addBodypartRow(Bodypart * b)1263 void Injector::addBodypartRow( Bodypart * b )
1264 {
1265     bool storeText = false;
1266     bool storeData = false;
1267 
1268     // Do we need to store anything at all?
1269 
1270     ContentType *ct = b->contentType();
1271     if ( ct ) {
1272         if ( ct->type() == "text" ) {
1273             storeText = true;
1274             if ( ct->subtype() == "html" )
1275                 storeData = true;
1276         }
1277         else {
1278             storeData = true;
1279             if ( ct->type() == "multipart" && ct->subtype() != "signed" )
1280                 storeData = false;
1281             if ( ct->type() == "message" && ct->subtype() == "rfc822" )
1282                 storeData = false;
1283         }
1284     }
1285     else {
1286         storeText = true;
1287     }
1288 
1289     if ( !( storeText || storeData ) )
1290         return;
1291 
1292     // Yes. What exactly do we need to store?
1293 
1294     EString * s;
1295     EString hash;
1296     EString * text = 0;
1297     EString * data = 0;
1298     PgUtf8Codec u;
1299 
1300     if ( storeText ) {
1301         text = s = new EString( u.fromUnicode( b->text() ) );
1302 
1303         // For certain content types (whose names are "text/html"), we
1304         // store the contents as data and a plaintext representation as
1305         // text. (This code may need to move if we want to treat other
1306         // content types this way. But where to?)
1307 
1308         if ( storeData ) {
1309             data = s;
1310             text =
1311                 new EString( u.fromUnicode( HTML::asText( b->text() ) ) );
1312         }
1313     }
1314     else {
1315         data = s = new EString( b->data() );
1316     }
1317     hash = MD5::hash( *s ).hex();
1318 
1319     // And where does it fit in the list of bodyparts we know already?
1320     // Either we've seen it before (in which case we add it to the list
1321     // of bodyparts in the appropriate BodypartRow entry), or we haven't
1322     // (in which case we add a new BodypartRow).
1323 
1324     BodypartRow * br = d->hashes.find( hash );
1325 
1326     if ( !br ) {
1327         br = new BodypartRow;
1328         br->hash = hash;
1329         br->text = text;
1330         br->data = data;
1331         br->bytes = b->numBytes();
1332         d->hashes.insert( hash, br );
1333         d->bodyparts.append( br );
1334     }
1335     br->bodyparts.append( b );
1336 }
1337 
1338 
1339 /*! This function inserts rows into the messages table for each Message
1340     in d->messages, and updates the objects with the newly-created ids.
1341     It expects to be called repeatedly until it returns true, which it
1342     does only when the work is done, or an error occurs.
1343 */
1344 
selectMessageIds()1345 void Injector::selectMessageIds()
1346 {
1347     if ( !d->select ) {
1348         d->select = selectNextvals( "messages_id_seq", d->messages.count() );
1349         d->transaction->enqueue( d->select );
1350         d->transaction->execute();
1351     }
1352 
1353     if ( !d->select->done() )
1354         return;
1355 
1356     if ( d->select->failed() )
1357         return;
1358 
1359     Query * copy
1360         = new Query( "copy messages "
1361                      "(id,rfc822size,idate,thread_root) "
1362                      "from stdin with binary", this );
1363 
1364     List<Injectee>::Iterator m( d->messages );
1365     while ( m && d->select->hasResults() ) {
1366         Row * r = d->select->nextRow();
1367         m->setDatabaseId( r->getInt( "id" ) );
1368         copy->bind( 1, m->databaseId() );
1369         if ( !m->hasTrivia() ) {
1370             m->setRfc822Size( m->rfc822( false ).length() );
1371             m->setTriviaFetched( true );
1372         }
1373         copy->bind( 2, m->rfc822Size() );
1374         copy->bind( 3, internalDate( m ) );
1375         uint tr = d->threads->id( m->header()->messageId() );
1376         if ( tr )
1377             copy->bind( 4, tr );
1378         else
1379             copy->bindNull( 4 );
1380         copy->submitLine();
1381         ++m;
1382     }
1383 
1384     d->transaction->enqueue( copy );
1385 
1386     next();
1387 }
1388 
1389 
1390 /*! This private function is responsible for fetching a uid and modseq
1391     value for each message in each mailbox and incrementing uidnext and
1392     nextmodseq appropriately.
1393 */
1394 
selectUids()1395 void Injector::selectUids()
1396 {
1397     // We are given a number of messages, each of which has its own list
1398     // of target mailboxes. There may be many messages, but chances are
1399     // that there are few mailboxes (the overwhelmingly common case is
1400     // just one mailbox).
1401     //
1402     // In principle, we could loop over d->messages/m->mailboxes() as we
1403     // do elsewhere, enqueue-ing a select/increment for each one. Things
1404     // would work so long as the increment for one message was executed
1405     // before the select for the next one. But we don't do that, because
1406     // then injecting ten thousand messages into one mailbox would need
1407     // ten thousand selects and, worse still, ten thousand updates too.
1408     //
1409     // So we turn the loop inside out, build a list of mailboxes, count
1410     // the messages to be injected into each one, and increment uidnext
1411     // and modseq by that number, once per mailbox instead of once per
1412     // message.
1413     //
1414     // To protect against concurrent injection into the same
1415     // mailboxes, we hold a write lock on the mailboxes during
1416     // injection; thus, the Injectors try to acquire locks in the same
1417     // order to avoid deadlock.
1418 
1419     if ( !d->lockUidnext ) {
1420         if ( d->mailboxes.isEmpty() ) {
1421             next();
1422             return;
1423         }
1424 
1425         IntegerSet ids;
1426         Map<InjectorData::Mailbox>::Iterator mi( d->mailboxes );
1427         while ( mi ) {
1428             ids.add( mi->mailbox->id() );
1429             ++mi;
1430         }
1431 
1432         d->lockUidnext = new Query(
1433             "select id,uidnext,nextmodseq,first_recent from mailboxes "
1434             "where id=any($1) order by id for update", this );
1435         d->lockUidnext->bind( 1, ids );
1436         d->transaction->enqueue( d->lockUidnext );
1437         d->transaction->execute();
1438     }
1439 
1440     while ( d->lockUidnext->hasResults() ) {
1441         Row * r = d->lockUidnext->nextRow();
1442         InjectorData::Mailbox * mb = d->mailboxes.find( r->getInt( "id" ) );
1443         uint uidnext = r->getInt( "uidnext" );
1444         int64 nextms = r->getBigint( "nextmodseq" );
1445 
1446         if ( uidnext > 0x7ff00000 ) {
1447             Log::Severity level = Log::Significant;
1448             if ( uidnext > 0x7fffff00 )
1449                 level = Log::Error;
1450             log( "Note: Mailbox " + mb->mailbox->name().ascii() +
1451                  " only has " + fn ( 0x7fffffff - uidnext ) +
1452                  " more usable UIDs. Please contact info@aox.org"
1453                  " to resolve this problem.", level );
1454         }
1455 
1456         // Any messages in this mailbox are assigned consecutive uids
1457         // starting with uidnext, but all of them get the same modseq.
1458 
1459         uint n = 0;
1460         List<Injectee>::Iterator it( mb->messages );
1461         while ( it ) {
1462             Injectee * m = it;
1463             m->setUid( mb->mailbox, uidnext+n );
1464             m->setModSeq( mb->mailbox, nextms );
1465             n++;
1466             ++it;
1467         }
1468         if ( n )
1469             log( "Using UIDs " + fn( uidnext ) + "-" + fn( uidnext + n - 1 ) +
1470                  " in mailbox " + mb->mailbox->name().utf8() );
1471 
1472         // If we have sessions listening to the mailbox, then they get
1473         // to see the messages as \Recent. Otherwise, whoever opens
1474         // the mailbox next will update first_recent.
1475 
1476         bool recentIn = false;
1477         if ( n && r->getInt( "uidnext" ) == r->getInt( "first_recent" ) ) {
1478             List<Session>::Iterator si( mb->mailbox->sessions() );
1479             if ( si ) {
1480                 recentIn = true;
1481                 si->addRecent( uidnext, n );
1482             }
1483         }
1484 
1485         // Update uidnext and nextmodseq based on what we did above.
1486 
1487         Query * u;
1488         if ( recentIn )
1489             u = new Query( "update mailboxes "
1490                            "set uidnext=uidnext+$2,"
1491                            "nextmodseq=nextmodseq+1,"
1492                            "first_recent=first_recent+$2 "
1493                            "where id=$1", 0 );
1494         else
1495             u = new Query( "update mailboxes "
1496                            "set uidnext=uidnext+$2,nextmodseq=nextmodseq+1 "
1497                            "where id=$1", 0 );
1498         u->bind( 1, mb->mailbox->id() );
1499         u->bind( 2, n );
1500         d->transaction->enqueue( u );
1501     }
1502 
1503     if ( d->lockUidnext->done() )
1504         next();
1505 }
1506 
1507 
1508 /*! Injects messages into the correct tables. */
1509 
insertMessages()1510 void Injector::insertMessages()
1511 {
1512     Query * qp =
1513         new Query( "copy part_numbers (message,part,bodypart,bytes,lines) "
1514                    "from stdin with binary", 0 );
1515     Query * qh =
1516         new Query( "copy header_fields (message,part,position,field,value) "
1517                    "from stdin with binary", 0 );
1518     Query * qa =
1519         new Query( "copy address_fields "
1520                    "(message,part,position,field,number,address) "
1521                    "from stdin with binary", 0 );
1522     Query * qd =
1523         new Query( "copy date_fields (message,value) from stdin", 0 );
1524 
1525     Query * qm =
1526         new Query( "copy mailbox_messages "
1527                    "(mailbox,uid,message,modseq,seen,deleted) "
1528                    "from stdin with binary", 0 );
1529     Query * qf =
1530         new Query( "copy flags (mailbox,uid,flag) "
1531                    "from stdin with binary", 0 );
1532     Query * qn =
1533         new Query( "copy annotations (mailbox,uid,name,value,owner) "
1534                    "from stdin with binary", 0 );
1535     Query * qw =
1536         new Query( "copy unparsed_messages (bodypart) "
1537                    "from stdin with binary", 0 );
1538 
1539     uint flags = 0;
1540     uint wrapped = 0;
1541     uint mailboxes = 0;
1542     uint annotations = 0;
1543 
1544     List<Injectee>::Iterator it( d->messages );
1545     while ( it ) {
1546         Message * m = it;
1547         uint mid = m->databaseId();
1548 
1549         // The top-level RFC 822 header fields are linked to a special
1550         // part named "" that does not correspond to any entry in the
1551         // bodyparts table.
1552 
1553         addPartNumber( qp, mid, "" );
1554         addHeader( qh, qa, qd, mid, "", m->header() );
1555 
1556         // Since the MIME header fields belonging to the first-child of
1557         // a single-part Message are appended to the RFC 822 header, we
1558         // don't need to inject them into the database again.
1559 
1560         bool skip = false;
1561         ContentType *ct = m->header()->contentType();
1562         if ( !ct || ct->type() != "multipart" )
1563             skip = true;
1564 
1565         // Now we insert the headers and bodies of every MIME bodypart.
1566 
1567         List<Bodypart>::Iterator bi( m->allBodyparts() );
1568         while ( bi ) {
1569             Bodypart * b = bi;
1570             EString pn( m->partNumber( b ) );
1571 
1572             addPartNumber( qp, mid, pn, b );
1573             if ( !skip )
1574                 addHeader( qh, qa, qd, mid, pn, b->header() );
1575             else
1576                 skip = false;
1577 
1578             // message/rfc822 bodyparts get a special part number too.
1579 
1580             if ( b->message() ) {
1581                 EString rpn( pn + ".rfc822" );
1582                 addPartNumber( qp, mid, rpn, b );
1583                 addHeader( qh, qa, qd, mid, rpn, b->message()->header() );
1584             }
1585 
1586             // If the message we're injecting is a wrapper around a
1587             // message we couldn't parse, record that fact too.
1588 
1589             if ( m->isWrapped() && pn == "2" ) {
1590                 qw->bind( 1, b->id() );
1591                 qw->submitLine();
1592                 wrapped++;
1593             }
1594 
1595             ++bi;
1596         }
1597 
1598         ++it;
1599     }
1600 
1601     List<Injectee>::Iterator imi( d->injectables );
1602     while ( imi ) {
1603         Injectee * m = imi;
1604         ++imi;
1605 
1606         // Then record any mailbox-specific information (e.g. flags).
1607 
1608         List<Mailbox>::Iterator mi( m->mailboxes() );
1609         while ( mi ) {
1610             Mailbox *mb = mi;
1611 
1612             mailboxes++;
1613             addMailbox( qm, m, mb );
1614 
1615             flags += addFlags( qf, m, mb );
1616             annotations += addAnnotations( qn, m, mb );
1617 
1618             ++mi;
1619         }
1620     }
1621 
1622     d->transaction->enqueue( qp );
1623     d->transaction->enqueue( qh );
1624     d->transaction->enqueue( qa );
1625     d->transaction->enqueue( qd );
1626     if ( mailboxes )
1627         d->transaction->enqueue( qm );
1628     if ( flags )
1629         d->transaction->enqueue( qf );
1630     if ( annotations )
1631         d->transaction->enqueue( qn );
1632     if ( wrapped )
1633         d->transaction->enqueue( qw );
1634 }
1635 
1636 
1637 /*! Adds a single part_numbers row for the given \a part number,
1638     belonging to the message with id \a mid and the bodypart \a b
1639     (which may be 0) to the query \a q.
1640 */
1641 
addPartNumber(Query * q,uint mid,const EString & part,Bodypart * b)1642 void Injector::addPartNumber( Query * q, uint mid, const EString &part,
1643                               Bodypart * b )
1644 {
1645     q->bind( 1, mid );
1646     q->bind( 2, part );
1647 
1648     if ( b ) {
1649         if ( b->id() )
1650             q->bind( 3, b->id() );
1651         else
1652             q->bindNull( 3 );
1653         q->bind( 4, b->numEncodedBytes() );
1654         q->bind( 5, b->numEncodedLines() );
1655     }
1656     else {
1657         q->bindNull( 3 );
1658         q->bindNull( 4 );
1659         q->bindNull( 5 );
1660     }
1661 
1662     q->submitLine();
1663 }
1664 
1665 
1666 /*! Add each field from the header \a h (belonging to the given \a part
1667     of the message with id \a mid) to one of the queries \a qh, \a qa,
1668     or \a qd, depending on their type.
1669 */
1670 
addHeader(Query * qh,Query * qa,Query * qd,uint mid,const EString & part,Header * h)1671 void Injector::addHeader( Query * qh, Query * qa, Query * qd, uint mid,
1672                           const EString & part, Header * h )
1673 {
1674     List< HeaderField >::Iterator it( h->fields() );
1675     while ( it ) {
1676         HeaderField * hf = it;
1677 
1678         if ( hf->type() <= HeaderField::LastAddressField ) {
1679             List< Address > * al = ((AddressField *)hf)->addresses();
1680             List< Address >::Iterator ai( al );
1681             uint n = 0;
1682             while ( ai ) {
1683                 qa->bind( 1, mid );
1684                 qa->bind( 2, part );
1685                 qa->bind( 3, hf->position() );
1686                 qa->bind( 4, hf->type() );
1687                 qa->bind( 5, n );
1688                 qa->bind( 6, addressId( ai ) );
1689                 qa->submitLine();
1690                 ++ai;
1691                 ++n;
1692             }
1693         }
1694         else {
1695             uint t = 0;
1696             if ( d->fieldNameCreator )
1697                 t = d->fieldNameCreator->id( hf->name() );
1698             if ( !t )
1699                 t = hf->type();
1700 
1701             qh->bind( 1, mid );
1702             qh->bind( 2, part );
1703             qh->bind( 3, hf->position() );
1704             qh->bind( 4, t );
1705             qh->bind( 5, hf->value() );
1706             qh->submitLine();
1707 
1708             if ( part.isEmpty() && hf->type() == HeaderField::Date ) {
1709                 DateField * df = (DateField *)hf;
1710                 qd->bind( 1, mid );
1711                 qd->bind( 2, df->date()->isoDateTime() );
1712                 qd->submitLine();
1713             }
1714         }
1715 
1716         ++it;
1717     }
1718 }
1719 
1720 
1721 /*! Adds a mailbox_messages row for the message \a m in mailbox \a mb to
1722     the query \a q. */
1723 
addMailbox(Query * q,Injectee * m,Mailbox * mb)1724 void Injector::addMailbox( Query * q, Injectee * m, Mailbox * mb )
1725 {
1726     if ( !mb->id() ) {
1727         log( "Asked to inject into synthetic mailbox " + mb->name().ascii() );
1728         return;
1729     }
1730     q->bind( 1, mb->id() );
1731     q->bind( 2, m->uid( mb ) );
1732     q->bind( 3, m->databaseId() );
1733     q->bind( 4, m->modSeq( mb ) );
1734     EStringList::Iterator i( m->flags( mb ) );
1735     bool seen = false;
1736     bool deleted = false;
1737     while ( i ) {
1738         uint id = Flag::id( *i );
1739         ++i;
1740         if ( Flag::isSeen( id ) )
1741             seen = true;
1742         else if ( Flag::isDeleted( id ) )
1743             deleted = true;
1744     }
1745     q->bind( 5, seen );
1746     q->bind( 6, deleted );
1747     q->submitLine();
1748 }
1749 
1750 
1751 /*! Adds flags rows for the message \a m in mailbox \a mb to the query
1752     \a q, and returns the number of flags (which may be 0). */
1753 
addFlags(Query * q,Injectee * m,Mailbox * mb)1754 uint Injector::addFlags( Query * q, Injectee * m, Mailbox * mb )
1755 {
1756     uint n = 0;
1757     EStringList::Iterator it( m->flags( mb ) );
1758     while ( it ) {
1759         uint flag = 0;
1760         if ( d->flagCreator )
1761             flag = d->flagCreator->id( *it );
1762         if ( !flag )
1763             flag = Flag::id( *it );
1764         if ( !Flag::isSeen( flag ) && !Flag::isDeleted( flag ) ) {
1765             n++;
1766             q->bind( 1, mb->id() );
1767             q->bind( 2, m->uid( mb ) );
1768             q->bind( 3, flag );
1769             q->submitLine();
1770         }
1771         ++it;
1772     }
1773     return n;
1774 }
1775 
1776 
1777 /*! Adds annotations rows for the message \a m in mailbox \a mb to the
1778     query \a q, and returns the number of annotations (may be 0). */
1779 
addAnnotations(Query * q,Injectee * m,Mailbox * mb)1780 uint Injector::addAnnotations( Query * q, Injectee * m, Mailbox * mb )
1781 {
1782     if ( !d->annotationNameCreator )
1783         return 0;
1784     uint n = 0;
1785     List<Annotation>::Iterator ai( m->annotations( mb ) );
1786     while ( ai ) {
1787         uint aid = d->annotationNameCreator->id( ai->entryName() );
1788         n++;
1789         q->bind( 1, mb->id() );
1790         q->bind( 2, m->uid( mb ) );
1791         q->bind( 3, aid );
1792         q->bind( 4, ai->value() );
1793         if ( ai->ownerId() == 0 )
1794             q->bindNull( 5 );
1795         else
1796             q->bind( 5, ai->ownerId() );
1797         q->submitLine();
1798         ++ai;
1799     }
1800     return n;
1801 }
1802 
1803 
1804 /*! This private function inserts one row per remote recipient into
1805     the deliveries table.
1806 */
1807 
insertDeliveries()1808 void Injector::insertDeliveries()
1809 {
1810     if ( d->deliveries.isEmpty() )
1811         return;
1812 
1813     List<InjectorData::Delivery>::Iterator di( d->deliveries );
1814     while ( di ) {
1815         Address * sender =
1816             d->addresses.find( AddressCreator::key( di->sender ) );
1817 
1818         Query * q =
1819             new Query( "insert into deliveries "
1820                        "(sender,message,injected_at,expires_at,deliver_after) "
1821                        "values ($1,$2,current_timestamp,"
1822                        "current_timestamp+interval '2 weeks',$3)", 0 );
1823         q->bind( 1, sender->id() );
1824         q->bind( 2, di->message->databaseId() );
1825         if ( di->later )
1826             q->bind( 3, di->later->isoDateTime() );
1827         else
1828             q->bindNull( 3 );
1829         d->transaction->enqueue( q );
1830 
1831         uint n = 0;
1832         List<Address>::Iterator it( di->recipients );
1833         EStringList domains;
1834         while ( it ) {
1835             Address * a = d->addresses.find( AddressCreator::key( it ) );
1836             domains.append( a->domain().utf8().lower() );
1837             Query * q =
1838                 new Query(
1839                     "insert into delivery_recipients (delivery,recipient) "
1840                     "values ("
1841                     "currval(pg_get_serial_sequence('deliveries','id')),"
1842                     "$1)", 0
1843                 );
1844             q->bind( 1, a->id() );
1845             d->transaction->enqueue( q );
1846             n++;
1847             ++it;
1848         }
1849 
1850         Header * h = di->message->header();
1851         if ( h && h->field( "Auto-Submitted" ) && !di->later ) {
1852             q = new Query( "update deliveries "
1853                            "set deliver_after=injected_at+'1 minute'::interval "
1854                            "where message=$1 and exists ("
1855                            "(select dr.id from delivery_recipients dr"
1856                            " join addresses a on (dr.recipient=a.id)"
1857                            " where dr.action>$2"
1858                            " and dr.last_attempt > current_timestamp-'1 minute'::interval"
1859                            " and a.domain=any($3::text[])))", 0 );
1860             q->bind( 1, di->message->databaseId() );
1861             q->bind( 2, Recipient::Delayed );
1862             domains.removeDuplicates();
1863             q->bind( 3, domains );
1864             d->transaction->enqueue( q );
1865         }
1866 
1867         log( "Spooling message " + fn( di->message->databaseId() ) +
1868              " for delivery to " + fn( n ) +
1869              " remote recipients", Log::Significant );
1870 
1871         ++di;
1872     }
1873 
1874     d->transaction->enqueue( new Query( "notify deliveries_updated", 0 ) );
1875 }
1876 
1877 
1878 /*! Logs a little information about the messages to be injected, and a
1879     little more for the special case of a single message being injected
1880     into a single mailbox.
1881 */
1882 
logDescription()1883 void Injector::logDescription()
1884 {
1885     List<Injectee>::Iterator im( d->injectables );
1886     while ( im ) {
1887         Injectee * m = im;
1888         ++im;
1889 
1890         EString msg( "Injecting message " );
1891         msg.append( m->header()->messageId().forlog() );
1892         msg.append( " into " );
1893 
1894         EStringList into;
1895         List<Mailbox>::Iterator mb( m->mailboxes() );
1896         while ( mb ) {
1897             into.append( mb->name().utf8() );
1898             ++mb;
1899         }
1900         msg.append( into.join( ", " ) );
1901         log( msg, Log::Significant );
1902     }
1903     List<InjectorData::Delivery>::Iterator dm( d->deliveries );
1904     while ( dm ) {
1905         InjectorData::Delivery * del = dm;
1906         ++dm;
1907 
1908         EString msg( "Spooling message " );
1909         msg.append( del->message->header()->messageId().forlog() );
1910         msg.append( " from " );
1911         msg.append( del->sender->lpdomain() );
1912         msg.append( " to " );
1913 
1914         EStringList to;
1915         List<Address>::Iterator a( del->recipients );
1916         while ( a ) {
1917             to.append( a->lpdomain() );
1918             ++a;
1919         }
1920         msg.append( to.join( ", " ) );
1921         log( msg, Log::Significant );
1922     }
1923 }
1924 
1925 
1926 struct MailboxAnnouncement {
MailboxAnnouncementMailboxAnnouncement1927     MailboxAnnouncement(): mailbox( 0 ), uidnext( 0 ), nextmodseq( 0 ) {}
1928     Mailbox * mailbox;
1929     uint uidnext;
1930     int64 nextmodseq;
1931 };
1932 
1933 
1934 /*! Inserts this/these message/s into the MessageCache. If the
1935     transaction fails, the cache has to be cleared.
1936 */
1937 
cache()1938 void Injector::cache()
1939 {
1940     List<Injectee>::Iterator it( d->injectables );
1941     while ( it ) {
1942         Injectee * m = it;
1943         ++it;
1944         m->setBodiesFetched();
1945         m->setBytesAndLinesFetched();
1946         m->setAddressesFetched();
1947         m->setHeadersFetched();
1948         List<Mailbox>::Iterator mi( m->mailboxes() );
1949         while ( mi ) {
1950             Mailbox * mb = mi;
1951             ++mi;
1952             uint uid = m->uid( mb );
1953 
1954             MessageCache::insert( mb, uid, m );
1955         }
1956     }
1957 }
1958 
1959 
1960 /*! Returns a sensible internaldate for \a m. If
1961     Message::internalDate() is not null, it is used, otherwise this
1962     function tries to obtain a date heuristically.
1963 */
1964 
internalDate(Message * m) const1965 uint Injector::internalDate( Message * m ) const
1966 {
1967     if ( !m )
1968         return 0;
1969     if ( m->internalDate() )
1970         return m->internalDate();
1971 
1972     // first: try the most recent received field. this should be
1973     // very close to the correct internaldate.
1974     Date id;
1975     List< HeaderField >::Iterator it( m->header()->fields() );
1976     while ( it && !id.valid() ) {
1977         if ( it->type() == HeaderField::Received ) {
1978             EString v = it->rfc822( false );
1979             int i = 0;
1980             while ( v.find( ';', i+1 ) > 0 )
1981                 i = v.find( ';', i+1 );
1982             if ( i >= 0 )
1983                 id.setRfc822( v.mid( i+1 ) );
1984         }
1985         ++it;
1986     }
1987 
1988     // if that fails, try the message's date.
1989     if ( !id.valid() ) {
1990         Date * date = m->header()->date();
1991         if ( date )
1992             id.setUnixTime( date->unixTime() ); // ick
1993     }
1994 
1995     // and if all else fails, now.
1996     if ( !id.valid() )
1997         id.setCurrentTime();
1998 
1999     m->setInternalDate( id.unixTime() );
2000     return id.unixTime();
2001 }
2002 
2003 
2004 class InjecteeData
2005     : public Garbage
2006 {
2007 public:
InjecteeData()2008     InjecteeData(): Garbage() {}
2009 
2010     class Mailbox
2011         : public Garbage
2012     {
2013     public:
Mailbox()2014         Mailbox()
2015             : Garbage(),
2016               mailbox( 0 ), uid( 0 ), modseq( 0 ),
2017               flags( new EStringList ), annotations( new List<Annotation> ) {}
2018         ::Mailbox * mailbox;
2019         uint uid;
2020         int64 modseq;
2021         EStringList * flags;
2022         List<Annotation> * annotations;
2023     };
2024 
2025     List<Mailbox> mailboxes;
2026 
mailbox(::Mailbox * mb,bool create=false)2027     Mailbox * mailbox( ::Mailbox * mb, bool create = false ) {
2028         if ( mailboxes.firstElement() &&
2029              mailboxes.firstElement()->mailbox == mb )
2030             return mailboxes.firstElement();
2031         List<Mailbox>::Iterator i( mailboxes );
2032         while ( i && i->mailbox != mb )
2033             ++i;
2034         if ( i || !create )
2035             return i;
2036         Mailbox * n = new Mailbox;
2037         n->mailbox = mb;
2038         mailboxes.append( n );
2039         return n;
2040     }
2041 };
2042 
2043 
2044 /*! \class Injectee injector.h
2045     Represents a message and all its associated mailbox-specific data.
2046 
2047     A message doesn't, by itself, have any mailbox-specific properties
2048     (uid, flags, annotations, and so on). This subclass ties a message
2049     to all such (variant, as opposed to the header/bodies) metadata.
2050 
2051     The Injector takes a list of Injectee objects to insert
2052     into the database.
2053 */
2054 
2055 
2056 /*!  Constructs an empty injectable message. The caller has to do
2057      more.
2058 */
2059 
Injectee()2060 Injectee::Injectee()
2061     : Message(), d( new InjecteeData )
2062 {
2063 }
2064 
2065 
2066 /*! Notifies the message that it has \a uid in \a mailbox. */
2067 
setUid(Mailbox * mailbox,uint uid)2068 void Injectee::setUid( Mailbox * mailbox, uint uid )
2069 {
2070     InjecteeData::Mailbox * m = d->mailbox( mailbox, true );
2071     m->uid = uid;
2072 }
2073 
2074 
2075 /*! Returns what setUid() set for \a mailbox, or 0. */
2076 
uid(Mailbox * mailbox) const2077 uint Injectee::uid( Mailbox * mailbox ) const
2078 {
2079     InjecteeData::Mailbox * m = d->mailbox( mailbox );
2080     if ( !m )
2081         return 0;
2082     return m->uid;
2083 }
2084 
2085 
2086 /*! Notifies the message that it has \a modseq in \a mailbox. */
2087 
setModSeq(Mailbox * mailbox,int64 modseq)2088 void Injectee::setModSeq( Mailbox * mailbox , int64 modseq )
2089 {
2090     InjecteeData::Mailbox * m = d->mailbox( mailbox, true );
2091     m->modseq = modseq;
2092 }
2093 
2094 
2095 /*! Returns what setModSeq() set for \a mailbox, or 0. */
2096 
modSeq(Mailbox * mailbox) const2097 int64 Injectee::modSeq( Mailbox * mailbox ) const
2098 {
2099     InjecteeData::Mailbox * m = d->mailbox( mailbox );
2100     if ( !m )
2101         return 0;
2102     return m->modseq;
2103 }
2104 
2105 
2106 /*! Returns a pointer to this message's flags in \a mailbox. The
2107     return value is never null.
2108 */
2109 
flags(Mailbox * mailbox) const2110 EStringList * Injectee::flags( Mailbox * mailbox ) const
2111 {
2112     return d->mailbox( mailbox, true )->flags;
2113 }
2114 
2115 
2116 /*! Notifies this message that its flags in \a mailbox are exactly \a
2117     list.
2118 */
2119 
setFlags(Mailbox * mailbox,const EStringList * list)2120 void Injectee::setFlags( Mailbox * mailbox, const EStringList * list )
2121 {
2122     InjecteeData::Mailbox * m = d->mailbox( mailbox, true );
2123     m->flags->clear();
2124     EStringList::Iterator i( list );
2125     while ( i ) {
2126         m->flags->append( i );
2127         ++i;
2128     }
2129 }
2130 
2131 
2132 /*! Notifies this message that its flags in \a mailbox are exactly \a
2133     list.
2134 
2135     \a list is a UStringList, but since IMAP does not allow non-ASCII
2136     flags, any non-ASCII strings in \a list are silently discarded.
2137 
2138 */
2139 
setFlags(Mailbox * mailbox,const UStringList * list)2140 void Injectee::setFlags( Mailbox * mailbox, const UStringList * list )
2141 {
2142     InjecteeData::Mailbox * m = d->mailbox( mailbox, true );
2143     m->flags->clear();
2144     UStringList::Iterator i( list );
2145     while ( i ) {
2146         if ( i->isAscii() )
2147             m->flags->append( i->ascii() );
2148         ++i;
2149     }
2150 }
2151 
2152 
2153 /*! Returns a pointer to this message's annotations in \a
2154     mailbox. Never returns a null pointer.
2155 */
2156 
annotations(Mailbox * mailbox) const2157 List<Annotation> * Injectee::annotations( Mailbox * mailbox ) const
2158 {
2159     return d->mailbox( mailbox, true )->annotations;
2160 }
2161 
2162 
2163 /*! Notifies this message that its annotations in \a mailbox are
2164     exactly \a list.
2165 */
2166 
setAnnotations(Mailbox * mailbox,List<Annotation> * list)2167 void Injectee::setAnnotations( Mailbox * mailbox,
2168                                         List<Annotation> * list )
2169 {
2170     InjecteeData::Mailbox * m = d->mailbox( mailbox, true );
2171     m->annotations = list;
2172 }
2173 
2174 
2175 /*! Allocates and return a sorted list of all Mailbox objects to which
2176     this Message belongs. setUid() and friends cause the Message to
2177     belong to one or more Mailbox objects.
2178 
2179     This may return an empty list, but it never returns a null pointer.
2180 */
2181 
mailboxes() const2182 List<Mailbox> * Injectee::mailboxes() const
2183 {
2184     List<Mailbox> * m = new List<Mailbox>;
2185     List<InjecteeData::Mailbox>::Iterator i( d->mailboxes );
2186     while ( i ) {
2187         m->append( i->mailbox );
2188         ++i;
2189     }
2190     return m;
2191 }
2192 
2193 
2194 // scans the message for a header field of the appropriate name, and
2195 // returns the field value. The name must not contain the trailing ':'.
2196 
invalidField(const EString & message,const EString & name)2197 static EString invalidField( const EString & message, const EString & name )
2198 {
2199     uint i = 0;
2200     while ( i < message.length() ) {
2201         uint j = i;
2202         while ( i < message.length() &&
2203                 message[i] != '\n' && message[i] != ':' )
2204             i++;
2205         if ( message[i] != ':' )
2206             return "";
2207         EString h = message.mid( j, i-j ).headerCased();
2208         i++;
2209         j = i;
2210         while ( i < message.length() &&
2211                 ( message[i] != '\n' ||
2212                   ( message[i] == '\n' &&
2213                     ( message[i+1] == ' ' || message[i+1] == '\t' ) ) ) )
2214             i++;
2215         if ( h == name )
2216             return message.mid( j, i-j );
2217         i++;
2218         if ( message[i] == 10 || message[i] == 13 )
2219             return "";
2220     }
2221     return "";
2222 }
2223 
2224 
2225 // looks for field in message and adds it to wrapper, if valid.
2226 
addField(EString & wrapper,const EString & field,const EString & message,const EString & dflt="")2227 static void addField( EString & wrapper,
2228                       const EString & field, const EString & message,
2229                       const EString & dflt = "" )
2230 {
2231     EString value = invalidField( message, field );
2232     HeaderField * hf = 0;
2233     if ( !value.isEmpty() )
2234         hf = HeaderField::create( field, value );
2235     if ( hf && hf->valid() ) {
2236         wrapper.append( field );
2237         wrapper.append( ": " );
2238         wrapper.append( hf->rfc822( false ) );
2239         wrapper.append( "\r\n" );
2240     }
2241     else if ( !dflt.isEmpty() ) {
2242         wrapper.append( field );
2243         wrapper.append( ": " );
2244         wrapper.append( dflt );
2245         wrapper.append( "\r\n" );
2246     }
2247 }
2248 
2249 
2250 /*! Wraps an unparsable \a message up in another, which contains a short
2251   \a error message, a little helpful text (or so one hopes), and the
2252   original message in a blob.
2253 
2254   \a defaultSubject is the subject text to use if no halfway
2255   sensible text can be extracted from \a message. \a id is used as
2256   content-disposition filename if supplied and nonempty.
2257 */
2258 
wrapUnparsableMessage(const EString & message,const EString & error,const EString & defaultSubject,const EString & id)2259 Injectee * Injectee::wrapUnparsableMessage( const EString & message,
2260                                             const EString & error,
2261                                             const EString & defaultSubject,
2262                                             const EString & id )
2263 {
2264     EString boundary = acceptableBoundary( message );
2265     EString wrapper;
2266 
2267     addField( wrapper, "From", message,
2268               "Mail Storage Database <invalid@invalid.invalid>" );
2269 
2270     EString subject = invalidField( message, "Subject" );
2271     HeaderField * hf = 0;
2272     if ( !subject.isEmpty() )
2273         hf = HeaderField::create( "Subject", subject );
2274     uint n = 0;
2275     while ( n < subject.length() && subject[n] < 127 && subject[n] >= 32 )
2276         n++;
2277     if ( hf && hf->valid() && n >= subject.length() )
2278         subject = "Unparsable message: " + hf->rfc822( false );
2279     else
2280         subject = defaultSubject;
2281     if ( !subject.isEmpty() )
2282         wrapper.append( "Subject: " + subject + "\r\n" );
2283 
2284     Date now;
2285     now.setCurrentTime();
2286     addField( wrapper, "Date", message, now.rfc822() );
2287     addField( wrapper, "To", message, "Unknown-Recipients:;" );
2288     addField( wrapper, "Cc", message );
2289     addField( wrapper, "References", message );
2290     addField( wrapper, "In-Reply-To", message );
2291 
2292     wrapper.append( "MIME-Version: 1.0\r\n"
2293                     "Content-Type: multipart/mixed; boundary=\"" +
2294                     boundary + "\"\r\n"
2295                     "\r\n\r\nYou are looking at an easter egg\r\n"
2296                     "--" + boundary + "\r\n"
2297                     "Content-Type: text/plain; format=flowed" ); // contd..
2298 
2299     EString report = "The appended message was received, "
2300                     "but could not be stored in the mail \r\n"
2301                     "database on " + Configuration::hostname() +
2302                     ".\r\n\r\nThe error detected was: \r\n";
2303     report.append( error );
2304     report.append( "\r\n\r\n"
2305                    "Here are a few header fields from the message "
2306                    "(possibly corrupted due \r\nto syntax errors):\r\n"
2307                    "\r\n" );
2308     if ( !invalidField( message, "From" ).isEmpty() ) {
2309         report.append( "From:" );
2310         report.append( invalidField( message, "From" ) );
2311         report.append( "\r\n" );
2312     }
2313     if ( !invalidField( message, "Subject" ).isEmpty() ) {
2314         report.append( "Subject:" );
2315         report.append( invalidField( message, "Subject" ) );
2316         report.append( "\r\n" );
2317     }
2318     if ( !invalidField( message, "To" ).isEmpty() ) {
2319         report.append( "To:" );
2320         report.append( invalidField( message, "To" ) );
2321         report.append( "\r\n" );
2322     }
2323     report.append( "\r\n"
2324                    "The complete message as received is appended." );
2325 
2326     // but which charset does the report use?
2327     n = 0;
2328     while ( n < report.length() && report[n] < 128 )
2329         n++;
2330     if ( n < report.length() )
2331         wrapper.append( "; charset=unknown-8bit" ); // ... continues c-t
2332     wrapper.append( "\r\n\r\n" );
2333     wrapper.append( report );
2334     wrapper.append( "\r\n\r\n--" + boundary + "\r\n" );
2335     n = 0;
2336     while ( n < message.length() &&
2337             message[n] < 128 &&
2338             ( message[n] >= 32 ||
2339               message[n] == 10 ||
2340               message[n] == 13 ) )
2341         n++;
2342     if ( n < message.length() )
2343         wrapper.append( "Content-Type: application/octet-stream\r\n"
2344                         "Content-Transfer-Encoding: 8bit\r\n" );
2345     else
2346         wrapper.append( "Content-Type: text/plain\r\n" );
2347     wrapper.append( "Content-Disposition: attachment" );
2348     if ( !id.isEmpty() ) {
2349         wrapper.append( "; filename=" );
2350         if ( id.boring() )
2351             wrapper.append( id );
2352         else
2353             wrapper.append( id.quoted() );
2354     }
2355     wrapper.append( "\r\n\r\n" );
2356     wrapper.append( message );
2357     wrapper.append( "\r\n--" + boundary + "--\r\n" );
2358 
2359     Injectee * m = new Injectee;
2360     m->parse( wrapper );
2361     m->setWrapped( true );
2362     return m;
2363 }
2364