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