FETCH CHANGEDSINCE x is >x, not >=x
[aox:aox.git] / imap / handlers / fetch.cpp
1 // Copyright 2009 The Archiveopteryx Developers <info@aox.org>
2
3 #include "fetch.h"
4
5 #include "messagecache.h"
6 #include "imapsession.h"
7 #include "transaction.h"
8 #include "annotation.h"
9 #include "integerset.h"
10 #include "estringlist.h"
11 #include "mimefields.h"
12 #include "imapparser.h"
13 #include "bodypart.h"
14 #include "address.h"
15 #include "mailbox.h"
16 #include "message.h"
17 #include "ustring.h"
18 #include "section.h"
19 #include "listext.h"
20 #include "fetcher.h"
21 #include "iso8859.h"
22 #include "codec.h"
23 #include "query.h"
24 #include "scope.h"
25 #include "store.h"
26 #include "timer.h"
27 #include "imap.h"
28 #include "date.h"
29 #include "user.h"
30 #include "dict.h"
31 #include "map.h"
32 #include "utf.h"
33
34
35
36 static const char * legalAnnotationAttributes[] = {
37     "value",
38     "value.priv",
39     "value.shared",
40     "size",
41     "size.priv",
42     "size.shared",
43     0
44 };
45
46
47 class FetchData
48     : public Garbage
49 {
50 public:
51     FetchData()
52         : state( 0 ), peek( true ), processed( 0 ),
53           changedSince( 0 ), those( 0 ), findIds( 0 ),
54           deleted( 0 ), store( 0 ),
55           uid( false ),
56           flags( false ), envelope( false ),
57           body( false ), bodystructure( false ),
58           internaldate( false ), rfc822size( false ),
59           annotation( false ), modseq( false ),
60           databaseId( false ), threadId( false ), vanished( false ),
61           needsHeader( false ), needsAddresses( false ),
62           needsBody( false ), needsPartNumbers( false ),
63           seenDeletedFetcher( 0 ), flagFetcher( 0 ),
64           annotationFetcher( 0 ), modseqFetcher( 0 )
65     {}
66
67     int state;
68     bool peek;
69     IntegerSet set;
70     IntegerSet remaining;
71     IntegerSet expunged;
72     Map<Message> messages;
73     uint processed;
74     int64 changedSince;
75     Query * those;
76     Query * findIds;
77     Query * deleted;
78     Store * store;
79
80     // we want to ask for...
81     bool uid;
82     bool flags;
83     bool envelope;
84     bool body;
85     bool bodystructure;
86     bool internaldate;
87     bool rfc822size;
88     bool annotation;
89     bool modseq;
90     bool databaseId;
91     bool threadId;
92     bool vanished;
93     List<Section> sections;
94
95     // and the sections imply that we...
96     bool needsHeader;
97     bool needsAddresses;
98     bool needsBody;
99     bool needsPartNumbers;
100
101     EStringList entries;
102     EStringList attribs;
103
104     struct DynamicData
105         : public Garbage
106     {
107     public:
108         DynamicData(): modseq( 0 ) {}
109         int64 modseq;
110         Dict<EString> flags;
111         List<Annotation> annotations;
112     };
113     Map<DynamicData> dynamics;
114     Query * seenDeletedFetcher;
115     Query * flagFetcher;
116     Query * annotationFetcher;
117     Query * modseqFetcher;
118 };
119
120
121 /*! \class Fetch fetch.h
122
123     Returns message data (RFC 3501, section 6.4.5, extended by RFC
124     4551 and RFC 5257).
125
126     Our parser used to be slightly more permissive than the RFC. This
127     is a bug (is it? why?), and many of the problems have been
128     corrected (but not tested).
129 */
130
131
132 /*! Creates a new handler for FETCH if \a u is false, or for UID FETCH
133     if \a u is true.
134 */
135
136 Fetch::Fetch( bool u )
137     : Command(), d( new FetchData )
138 {
139     d->uid = u;
140     if ( u )
141         setGroup( 1 );
142     else
143         setGroup( 2 );
144 }
145
146
147 /*! Constructs a handler for the implicit fetch which is executed by
148     ImapSession for flag updates, etc. If \a f is true the updates
149     will include FLAGS sections and if \a a is true, ANNOTATION. The
150     handler starts fetching those messagges in \a set that have a
151     modseq greater than \a limit. The responses are sent via \a i.
152
153     If \a t is non-zero, the fetch operates within a subtransaction
154     of \a t.
155 */
156
157 Fetch::Fetch( bool f, bool a, const IntegerSet & set,
158               int64 limit, IMAP * i, Transaction * t )
159     : Command( i ), d( new FetchData )
160 {
161     setLog( new Log );
162     Scope x( log() );
163     d->uid = true;
164     d->flags = f;
165     d->annotation = a;
166     d->set = set;
167     d->changedSince = limit;
168     d->modseq = i->clientSupports( IMAP::Condstore );
169     if ( t )
170         setTransaction( t->subTransaction( this ) );
171
172     d->peek = true;
173
174     List<Command>::Iterator c( i->commands() );
175     while ( c && c->state() == Command::Retired )
176         ++c;
177     while ( c && c->tag().isEmpty() )
178         ++c;
179     if ( c && ( c->state() == Command::Finished ||
180                 c->state() == Command::Executing ) ) {
181         log( "Inserting flag update for modseq>" + fn( limit ) +
182              " and UIDs " + set.set() + " before " +
183              c->tag() + " " + c->name() );
184         i->commands()->insert( c, this );
185         if ( c->group() == 1 || c->group() == 2 )
186             setGroup( c->group() );
187     }
188     else {
189         log( "Appending flag update for modseq>" + fn( limit ) +
190              " and UIDs " + set.set() );
191         i->commands()->append( this );
192     }
193
194     setAllowedState( IMAP::Selected );
195     setState( Executing );
196 }
197
198
199 // fetch           = "FETCH" SP set SP ("ALL" / "FULL" / "FAST" / fetch-att /
200 //                   "(" fetch-att *(SP fetch-att) ")")
201 // fetch-att       = "ENVELOPE" / "FLAGS" / "INTERNALDATE" /
202 //                   "RFC822" [".HEADER" / ".SIZE" / ".TEXT"] /
203 //                   "BODY" ["STRUCTURE"] / "UID" /
204 //                   "BODY" [".PEEK"] section ["<" number "." nz-number ">"]
205 //                 / "MODSEQ" ; 4551
206 // section         = "[" [section-spec] "]"
207 // section-spec    = section-msgtext / (section-part ["." section-text])
208 // section-msgtext = "HEADER" / "HEADER.FIELDS" [".NOT"] SP header-list /
209 //                   "TEXT"
210 // section-part    = nz-number *("." nz-number)
211 // section-text    = section-msgtext / "MIME"
212 // header-list     = "(" header-fld-name *(SP header-fld-name) ")"
213 // header-fld-name = astring
214
215
216 void Fetch::parse()
217 {
218     space();
219     d->set = set( !d->uid );
220     space();
221     if ( nextChar() == '(' ) {
222         // "(" fetch-att *(SP fetch-att) ")")
223         step();
224         parseAttribute( false );
225         while( nextChar() == ' ' ) {
226             step();
227             parseAttribute( false );
228         }
229         require( ")" );
230     }
231     else {
232         // single fetch-att, or the macros
233         parseAttribute( true );
234     }
235     if ( present( " (" ) ) {
236         // RFC 4466 fetch-modifiers
237         parseFetchModifier();
238         while ( present( " " ) )
239             parseFetchModifier();
240         require( ")" );
241     }
242     end();
243     if ( d->envelope ) {
244         d->needsHeader = true;
245         d->needsAddresses = true;
246     }
247     if ( d->body || d->bodystructure ) {
248         // message/rfc822 body[structure] includes envelope in some
249         // cases, so we need both here too.
250         d->needsHeader = true;
251         d->needsAddresses = true;
252         // and we even need some data about the bodies
253         d->needsPartNumbers = true;
254     }
255     if ( d->needsBody )
256         d->needsHeader = true; // Bodypart::asText() needs mime type etc
257     if ( !ok() )
258         return;
259     EStringList l;
260     l.append( new EString( "Fetch <=" + fn( d->set.count() ) + " messages: " ) );
261     if ( d->needsAddresses )
262         l.append( "address" );
263     if ( d->needsHeader )
264         l.append( "header" );
265     if ( d->needsBody )
266         l.append( "body" );
267     if ( d->flags )
268         l.append( "flags" );
269     if ( d->internaldate || d->rfc822size || d->databaseId || d->threadId )
270         l.append( "trivia" );
271     if ( d->needsPartNumbers )
272         l.append( "bytes/lines" );
273     if ( d->annotation )
274         l.append( "annotations" );
275     log( l.join( " " ) );
276 }
277
278
279 /*! This helper is responsible for parsing a single attribute from the
280     fetch arguments. If \a alsoMacro is true, this function parses a
281     macro as well as a single attribute.
282 */
283
284 void Fetch::parseAttribute( bool alsoMacro )
285 {
286     EString keyword = dotLetters( 3, 13 ).lower(); // UID/ALL, RFC822.HEADER
287     if ( alsoMacro && keyword == "all" ) {
288         // equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE)
289         d->flags = true;
290         d->envelope = true;
291         d->internaldate = true;
292         d->rfc822size = true;
293     }
294     else if ( alsoMacro && keyword == "full" ) {
295         // equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY)
296         d->flags = true;
297         d->envelope = true;
298         d->body = true;
299         d->internaldate = true;
300         d->rfc822size = true;
301     }
302     else if ( alsoMacro && keyword == "fast" ) {
303         // equivalent to: (FLAGS INTERNALDATE RFC822.SIZE)
304         d->flags = true;
305         d->internaldate = true;
306         d->rfc822size = true;
307     }
308     else if ( keyword == "envelope" ) {
309         d->envelope = true;
310     }
311     else if ( keyword == "flags" ) {
312         d->flags = true;
313     }
314     else if ( keyword == "internaldate" ) {
315         d->internaldate = true;
316     }
317     else if ( keyword == "rfc822" ) {
318         d->peek = false;
319         d->needsAddresses = true;
320         d->needsHeader = true;
321         d->needsBody = true;
322         Section * s = new Section;
323         s->id = keyword;
324         d->sections.append( s );
325     }
326     else if ( keyword == "rfc822.header" ) {
327         d->needsAddresses = true;
328         d->needsHeader = true;
329         Section * s = new Section;
330         s->id = keyword;
331         d->sections.append( s );
332     }
333     else if ( keyword == "rfc822.size" ) {
334         d->rfc822size = true;
335     }
336     else if ( keyword == "annotation" ) {
337         d->annotation = true;
338         require( " " );
339         parseAnnotation();
340     }
341     else if ( keyword == "rfc822.text" ) {
342         d->peek = false;
343         d->needsHeader = true;
344         d->needsBody = true;
345         Section * s = new Section;
346         s->id = keyword;
347         d->sections.append( s );
348     }
349     else if ( keyword == "body.peek" && nextChar() == '[' ) {
350         step();
351         parseBody( false );
352     }
353     else if ( keyword == "body" ) {
354         if ( nextChar() == '[' ) {
355             d->peek = false;
356             step();
357             parseBody( false );
358         }
359         else {
360             d->body = true;
361             // poor man's bodystructure
362         }
363     }
364     else if ( keyword == "bodystructure" ) {
365         d->bodystructure = true;
366         // like body, but with bells and whistles
367     }
368     else if ( keyword == "uid" ) {
369         d->uid = true;
370     }
371     else if ( keyword == "binary.peek" && nextChar() == '[' ) {
372         step();
373         parseBody( true );
374     }
375     else if ( keyword == "binary" && nextChar() == '[' ) {
376         d->peek = false;
377         step();
378         parseBody( true );
379     }
380     else if ( keyword == "binary.size" && nextChar() == '[' ) {
381         step();
382         parseBody( true );
383         Section * s = d->sections.last();
384         s->id = "size";
385         if ( s->partial )
386             error( Bad, "Fetching partial BINARY.SIZE is not meaningful" );
387         if ( s->part.isEmpty() )
388             d->rfc822size = true;
389     }
390     else if ( keyword == "modseq" ) {
391         d->modseq = true;
392     }
393     else if ( keyword == "msgid" ) {
394         d->databaseId = true;
395     }
396     else if ( keyword == "thrid" ) {
397         d->threadId = true;
398     }
399     else {
400         error( Bad, "expected fetch attribute, saw word " + keyword );
401     }
402 }
403
404
405 /*! This utility function fetches at least \a min, at most \a max
406     characters, all of which must be a letter, a digit or a dot.
407     Consecutive dots ARE allowed.
408 */
409
410 EString Fetch::dotLetters( uint min, uint max )
411 {
412     EString r( parser()->dotLetters( min, max ) );
413     if ( !parser()->ok() )
414         error( Bad, parser()->error() );
415     return r;
416 }
417
418
419 /*! Uses the ImapParser \a ip to parse a section-text production, and
420     returns a pointer to a suitably constructed Section object. Upon
421     return, the ImapParser's cursor is advanced to point past the end
422     of the section-text. \a ip must not be 0; and the return value of
423     this function is also guaranteed to be non-zero.
424
425     If \a binary is false (the default), then the BINARY extensions of
426     RFC 3516 are summarily ignored.
427
428     If there were any parsing errors, Section::error will be non-empty.
429 */
430
431 Section * Fetch::parseSection( ImapParser * ip, bool binary )
432 {
433     Section * s = new Section;
434     s->binary = binary;
435
436     // section-spec    = section-msgtext / (section-part ["." section-text])
437     // section-msgtext = "HEADER" /
438     //                   "HEADER.FIELDS" [".NOT"] SP header-list /
439     //                   "TEXT"
440     // section-part    = nz-number *("." nz-number)
441     // section-text    = section-msgtext / "MIME"
442
443     // Parse a section-part.
444     bool dot = false;
445     if ( ip->nextChar() >= '0' && ip->nextChar() <= '9' ) {
446         EString part;
447         part.append( fn( ip->nzNumber() ) );
448         while ( ip->nextChar() == '.' ) {
449             ip->step();
450             if ( ip->nextChar() >= '0' && ip->nextChar() <= '9' ) {
451                 part.append( "." );
452                 part.appendNumber( ip->nzNumber() );
453             }
454             else {
455                 dot = true;
456                 break;
457             }
458         }
459         s->part = part;
460     }
461
462     // Parse any section-text.
463     EString item = ip->dotLetters( 0, 17 ).lower();
464     if ( binary && !item.isEmpty() ) {
465         s->error = "BINARY with section-text is not legal, saw " + item;
466     }
467     else if ( item.isEmpty() || item == "text" ) {
468         s->needsBody = true;
469         // and because we might need headers and addresses of subparts:
470         s->needsHeader = true;
471         s->needsAddresses = true;
472     }
473     else if ( item == "header" ) {
474         s->needsHeader = true;
475         s->needsAddresses = true;
476     }
477     else if ( item == "header.fields" ||
478               item == "header.fields.not" )
479     {
480         ip->require( " (" );
481         s->fields.append( new EString( ip->astring().headerCased() ) );
482         while ( ip->nextChar() == ' ' ) {
483             ip->require( " " );
484             s->fields.append( new EString( ip->astring().headerCased() ) );
485         }
486         ip->require( ")" );
487         if ( item == "header.fields.not" ) {
488             // if we need to hand out "all other" fields...
489             s->needsAddresses = true;
490             s->needsHeader = true;
491         }
492         EStringList::Iterator i( s->fields );
493         while ( i && ( !s->needsAddresses || !s->needsHeader ) ) {
494             uint t = HeaderField::fieldType( *i );
495             if ( t > 0 && t <= HeaderField::LastAddressField )
496                 s->needsAddresses = true;
497             else
498                 s->needsHeader = true;
499             ++i;
500         }
501     }
502     else if ( item == "mime" ) {
503         if ( s->part.isEmpty() )
504             s->error = "MIME requires a section-part.";
505         s->needsHeader = true;
506     }
507     else if ( dot ) {
508         s->error =
509             "Expected text, header, header.fields etc, not " + item +
510             ip->following();
511     }
512
513     s->id = item;
514     return s;
515 }
516
517
518 /*! Parses a bodypart description - the bit following "body[" in an
519     attribute. The cursor must be after '[' on entry, and is left
520     after the trailing ']'.
521
522     If \a binary is true, the parsed section will be sent using the
523     BINARY extension (RFC 3516). If not, it'll be sent using a normal
524     BODY.
525 */
526
527 void Fetch::parseBody( bool binary )
528 {
529     Section * s = parseSection( parser(), binary );
530     if ( !s->error.isEmpty() ) {
531         error( Bad, s->error );
532         return;
533     }
534
535     require( "]" );
536
537     // Parse any range specification.
538     if ( nextChar() == '<' ) {
539         s->partial = true;
540         step();
541         s->offset = number();
542         require( "." );
543         s->length = nzNumber();
544         require( ">" );
545     }
546
547     d->sections.append( s );
548     if ( s->needsAddresses )
549         d->needsAddresses = true;
550     if ( s->needsHeader )
551         d->needsHeader = true;
552     if ( s->needsBody )
553         d->needsBody = true;
554 }
555
556
557 void record( EStringList & l, Dict<void> & d, const EString & a )
558 {
559     if ( !d.contains( a.lower() ) )
560         l.append( new EString( a ) );
561     d.insert( a.lower(), (void *)1 );
562 }
563
564
565 /*! Parses the entries and attributes from an ANNOTATION fetch-att.
566     Expects the cursor to be on the first parenthesis, and advances
567     it to past the last one.
568 */
569
570 void Fetch::parseAnnotation()
571 {
572     bool atEnd;
573     bool paren;
574
575     // Simplified ABNF from draft-ietf-imapext-annotate-15:
576     //
577     //  fetch-att =/ "ANNOTATION" SP "(" entries SP attribs ")"
578     //  entries   = list-mailbox /
579     //              "(" list-mailbox *(SP list-mailbox) ")"
580     //  attribs   = astring /
581     //              "(" astring *(SP astring) ")"
582
583     require( "(" );
584
585     paren = false;
586     if ( nextChar() == '(' ) {
587         step();
588         paren = true;
589     }
590
591     atEnd = false;
592     while ( !atEnd ) {
593         d->entries.append( new EString( parser()->listMailbox() ) );
594         if ( !parser()->ok() )
595             error( Bad, parser()->error() );
596
597         if ( paren ) {
598             if ( nextChar() == ')' ) {
599                 step();
600                 atEnd = true;
601             }
602             else {
603                 space();
604             }
605         }
606         else {
607             atEnd = true;
608         }
609     }
610
611     require( " " );
612
613     paren = false;
614     if ( nextChar() == '(' ) {
615         step();
616         paren = true;
617     }
618
619     Dict<void> attribs;
620
621     atEnd = false;
622     while ( !atEnd ) {
623         EString a( astring() );
624
625         // XXX: This check (and the legalAnnotationAttributes table) is
626         // duplicated in Search::parseKey(). But where should a common
627         // attribute-checking function live?
628         uint i = 0;
629         while ( ::legalAnnotationAttributes[i] &&
630                 a != ::legalAnnotationAttributes[i] )
631             i++;
632         if ( !::legalAnnotationAttributes[i] )
633             error( Bad, "Unknown annotation attribute: " + a );
634
635         if ( a.endsWith( ".priv" ) || a.endsWith( ".shared" ) ) {
636             record( d->attribs, attribs, a );
637         }
638         else {
639             record( d->attribs, attribs, a + ".priv" );
640             record( d->attribs, attribs, a + ".shared" );
641         }
642
643         if ( paren ) {
644             if ( nextChar() == ')' ) {
645                 step();
646                 atEnd = true;
647             }
648             else {
649                 space();
650             }
651         }
652         else {
653             atEnd = true;
654         }
655     }
656
657     require( ")" );
658 }
659
660
661 void Fetch::execute()
662 {
663     if ( state() != Executing )
664         return;
665
666     ImapSession * s = session();
667
668     if ( !d->peek && s->readOnly() )
669         d->peek = true;
670
671     if ( d->state == 0 ) {
672         if ( !transaction() &&
673              ( !d->peek ||
674                ( d->modseq && ( d->flags || d->annotation || d->vanished ) ) ) )
675             setTransaction( new Transaction( this ) );
676
677         if ( d->vanished && d->changedSince > 0 && !d->deleted ) {
678             d->deleted = new Query( "select uid from deleted_messages "
679                                      "where mailbox=$1 and modseq>$2 "
680                                      "and uid=any($3)",
681                                      this );
682             d->deleted->bind( 1, s->mailbox()->id() );
683             d->deleted->bind( 2, d->changedSince );
684             IntegerSet s( d->set );
685             s.remove( session()->messages() );
686             d->deleted->bind( 3, s );
687             transaction()->enqueue( d->deleted );
688         }
689
690         Mailbox * mb = s->mailbox();
691         if ( !d->those ) {
692             d->set = d->set.intersection( session()->messages() );
693             if ( d->changedSince ) {
694                 d->those = new Query( "select uid, message "
695                                       "from mailbox_messages "
696                                       "where mailbox=$1 and uid=any($2) "
697                                       "and modseq>$3",
698                                       this );
699                 d->those->bind( 1, s->mailbox()->id() );
700                 d->those->bind( 2, d->set );
701             }
702             else if ( d->modseq ||
703                       d->needsAddresses || d->needsHeader ||
704                       d->needsBody || d->needsPartNumbers ||
705                       d->rfc822size || d->internaldate ||
706                       d->databaseId || d->threadId ) {
707                 IntegerSet r;
708                 IntegerSet s( d->set );
709                 while ( !s.isEmpty() ) {
710                     uint uid = s.smallest();
711                     s.remove( uid );
712                     Message * m = MessageCache::find( mb, uid );
713                     if ( m )
714                         d->messages.insert( uid, m );
715                     if ( !m || !m->databaseId() || d->modseq )
716                         r.add( uid );
717                 }
718                 if ( !r.isEmpty() ) {
719                     d->those = new Query( "select uid, message "
720                                           "from mailbox_messages "
721                                           "where mailbox=$1 and uid=any($2)",
722                                           this );
723                     d->those->bind( 1, session()->mailbox()->id() );
724                     d->those->bind( 2, d->set );
725                 }
726             }
727             if ( d->those ) {
728                 if ( d->changedSince )
729                     d->those->bind( 3, d->changedSince );
730                 if ( d->modseq ) {
731                     if ( !d->peek ) {
732                         // if we aren't peeking, then we have to lock
733                         // the mailbox before we lock the messages,
734                         // otherwise we might deadlock with Store or
735                         // Expunge.
736                         Query * q = new Query( "select nextmodseq "
737                                                "from mailboxes "
738                                                "where id=$1 for update", 0 );
739                         q->bind( 1, mb->id() );
740                         transaction()->enqueue( q );
741                     }
742                     EString s = d->those->string();
743                     s.append( " order by uid for update" );
744                     d->those->setString( s );
745                 }
746                 enqueue( d->those );
747                 d->set.clear();
748                 if ( transaction() )
749                     transaction()->execute();
750             }
751         }
752         if ( d->those ) {
753             Row * r;
754             while ( d->those->hasResults() ) {
755                 r = d->those->nextRow();
756                 uint uid = r->getInt( "uid" );
757                 d->set.add( uid );
758                 Message * m = d->messages.find( uid );
759                 if ( !m ) {
760                     m = MessageCache::provide( mb, uid );
761                     d->messages.insert( uid, m );
762                 }
763                 m->setDatabaseId( r->getInt( "message" ) );
764                 if ( d->modseq || d->flags || d->annotation ) {
765                     FetchData::DynamicData * dd = new FetchData::DynamicData;
766                     d->dynamics.insert( uid, dd );
767                 }
768             }
769             if ( !d->those->done() )
770                 return;
771         }
772         else {
773             IntegerSet r( d->set );
774             while ( !r.isEmpty() ) {
775                 uint uid = r.smallest();
776                 r.remove( uid );
777                 d->dynamics.insert( uid, new FetchData::DynamicData );
778             }
779         }
780         d->state = 1;
781     }
782
783     if ( d->deleted && d->deleted->done() ) {
784         IntegerSet vanished;
785         while ( d->deleted->hasResults() ) {
786             Row * r = d->deleted->nextRow();
787             uint uid = r->getInt( "uid" );
788             vanished.add( uid );
789         }
790         if ( !vanished.isEmpty() )
791             respond( "VANISHED (EARLIER) " + vanished.set() );
792         d->deleted = 0;
793     }
794
795     if ( d->state == 1 ) {
796         if ( group() == 2 ) // then RFC 2180 section 4.1.2 applies
797             d->expunged = s->expunged().intersection( d->set );
798         shrink( &d->set );
799         d->remaining = d->set;
800         d->state = 2;
801         if ( d->set.isEmpty() ) {
802             d->state = 5;
803             if ( transaction() )
804                 transaction()->commit();
805         }
806     }
807
808     if ( d->state == 2 ) {
809         if ( d->peek ) {
810             d->state = 3;
811         }
812         else {
813             if ( !d->store ) {
814                 List<Command>::Iterator c = imap()->commands()->find( this );
815                 if ( c ) {
816                     d->store = new Store( imap(), d->set, d->flags,
817                                           transaction() );
818                     d->store->setState( Executing );
819                     imap()->commands()->insert( c, d->store );
820                     // should we feed the Store a subtransaction, if
821                     // we're using one? I don't know.
822                     d->store->execute();
823                 }
824             }
825             if ( d->store && d->store->state() == Executing )
826                 return;
827             d->state = 3;
828         }
829     }
830
831     if ( d->state == 3 ) {
832         d->state = 4;
833         sendFetchQueries();
834         if ( d->flags )
835             sendFlagQuery();
836         if ( d->annotation )
837             sendAnnotationsQuery();
838         if ( d->modseq )
839             sendModSeqQuery();
840         if ( transaction() )
841             transaction()->commit();
842     }
843
844     if ( d->state < 4 )
845         return;
846
847     pickup();
848
849     if ( d->processed < d->set.largest() )
850         return;
851
852     if ( !d->expunged.isEmpty() ) {
853         s->recordExpungedFetch( d->expunged );
854         error( No, "UID(s) " + d->expunged.set() + " has/have been expunged" );
855     }
856     finish();
857 }
858
859
860 /*! Issues queries to resolve any questions this FETCH needs to answer.
861 */
862
863 void Fetch::sendFetchQueries()
864 {
865     bool haveAddresses = true;
866     bool haveHeader = true;
867     bool haveBody = true;
868     bool havePartNumbers = true;
869     bool haveTrivia = true;
870
871     List<Message> * l = new List<Message>;
872
873     Map<Message>::Iterator i( d->messages );
874     while ( i ) {
875         Message * m = i;
876         ++i;
877         if ( !m->hasAddresses() )
878             haveAddresses = false;
879         if ( !m->hasHeaders() )
880             haveHeader = false;
881         if ( !m->hasBytesAndLines() )
882             havePartNumbers = false;
883         if ( !m->hasBodies() )
884             haveBody = false;
885         if ( !m->hasTrivia() )
886             haveTrivia = false;
887         l->append( m );
888     }
889
890     Fetcher * f = new Fetcher( l, this, imap() );
891     if ( d->needsAddresses && !haveAddresses )
892         f->fetch( Fetcher::Addresses );
893     if ( d->needsHeader && !haveHeader )
894         f->fetch( Fetcher::OtherHeader );
895     if ( d->needsBody && !haveBody )
896         f->fetch( Fetcher::Body );
897     if ( ( d->rfc822size || d->internaldate ||
898            d->databaseId || d->threadId ) && !haveTrivia )
899         f->fetch( Fetcher::Trivia );
900     if ( d->needsPartNumbers && !havePartNumbers )
901         f->fetch( Fetcher::PartNumbers );
902     f->execute();
903 }
904
905
906 /*! This function returns the text of that portion of the Message \a m
907     that is described by the Section \a s. It is publicly available so
908     that Append may use it for CATENATE.
909
910     If \a unicodable is true, the result may contain unquoted unicode.
911 */
912
913 EString Fetch::sectionData( Section * s, Message * m, bool unicodable )
914 {
915     EString item, data;
916
917     if ( s->id == "rfc822" ) {
918         item = s->id.upper();
919         data = m->rfc822( !unicodable );
920     }
921
922     else if ( s->id == "mime" ||
923               s->id == "rfc822.header" ||
924               s->id.startsWith( "header" ) ) {
925         bool rfc822 = s->id == "rfc822.header";
926         bool fields = s->id.startsWith( "header.fields" );
927         bool exclude = s->id.endsWith( ".not" );
928
929         data.reserve( 80 * s->fields.count() ); // suboptimal for .not, but...
930
931         Header * hdr = m->header();
932         if ( !s->part.isEmpty() ) {
933             Bodypart * bp = m->bodypart( s->part, false );
934             if ( bp && bp->header() )
935                 hdr = bp->header();
936             else
937                 hdr = 0;
938         }
939
940         List< HeaderField >::Iterator it;
941         if ( hdr )
942             it = hdr->fields()->first();
943         while ( it ) {
944             bool include = false;
945             if ( !fields ) {
946                 include = true;
947             }
948             else {
949                 bool listed = s->fields.find( it->name() );
950                 if ( exclude )
951                     include = !listed;
952                 else
953                     include = listed;
954             }
955             if ( include ) {
956                 EString n = it->name().headerCased();
957                 data.append( n );
958                 data.append( ": " );
959                 data.append( it->rfc822( !unicodable ) );
960                 data.append( "\r\n" );
961             }
962             ++it;
963         }
964
965         item = s->id.upper();
966         if ( !rfc822 ) {
967             if ( !s->part.isEmpty() )
968                 item = s->part + "." + item;
969             item = "BODY[" + item;
970             if ( fields )
971                 item.append( " (" + s->fields.join( " " ) + ")" );
972             item.append( "]" );
973         }
974         data.append( "\r\n" );
975     }
976
977     else if ( s->id == "rfc822.text" ) {
978         item = s->id.upper();
979         data = m->body( !unicodable );
980     }
981
982     else if ( s->id == "text" ) {
983         if ( s->part.isEmpty() ) {
984             item = "TEXT";
985             data = m->body( !unicodable );
986         }
987         else {
988             item = s->part + ".TEXT";
989             Bodypart *bp = m->bodypart( s->part, false );
990             if ( bp && bp->message() )
991                 data = bp->message()->body( !unicodable );
992         }
993         item = "BODY[" + item + "]";
994     }
995
996     else if ( ( s->id.isEmpty() || s->id == "size" ) &&
997               s->part.isEmpty() )
998     {
999         if ( s->id == "size" ) {
1000             item = "BINARY.SIZE[]";
1001             data = fn( m->rfc822Size() );
1002         }
1003         else {
1004             item = "BODY[]";
1005             data = m->rfc822( !unicodable );
1006         }
1007     }
1008
1009     else if ( s->id.isEmpty() || s->id == "size" ) {
1010         item = "BODY";
1011         Bodypart * bp = m->bodypart( s->part, false );
1012         if ( !bp ) {
1013             // nonexistent part number
1014             if ( s->binary )
1015                 item = "BINARY";
1016             // should we report an error?  the fetch responses will be
1017             // sent anyway.
1018             // error( No, "No such bodypart: " + s->part );
1019         }
1020         else if ( bp->message() ) {
1021             // message/rfc822 part
1022             data = bp->message()->rfc822( !unicodable );
1023         }
1024         else if ( bp->children()->isEmpty() ) {
1025             // leaf part
1026             data = bp->data();
1027
1028             ContentType * ct = bp->contentType();
1029             if ( !ct || ct->type() == "text" ) {
1030                 UString text;
1031
1032                 if ( data.isEmpty() ) {
1033                     text = bp->text();
1034                 }
1035                 else {
1036                     Codec * c = new Utf8Codec;
1037                     text = c->toUnicode( data );
1038                 }
1039
1040                 Codec * c = 0;
1041                 if ( ct )
1042                     c = Codec::byName( ct->parameter( "charset" ) );
1043                 if ( !c && ct && ct->subtype() == "html" )
1044                     c = new Iso88591Codec;
1045                 if ( !c )
1046                     c = new Utf8Codec;
1047                 data = c->fromUnicode( text );
1048             }
1049             if ( !s->binary )
1050                 data = data.encoded( bp->contentTransferEncoding(), 70 );
1051         }
1052         else {
1053             // nonleaf part. probably wrong - this might use the wrong
1054             // content-transfer-encoding.
1055             data = bp->asText( !unicodable );
1056         }
1057
1058         if ( s->binary )
1059             item = "BINARY";
1060
1061         if ( s->id == "size" ) {
1062             item = "BINARY.SIZE";
1063             data = fn( data.length() );
1064         }
1065
1066         item = item + "[" + s->part + "]";
1067     }
1068
1069     if ( s->partial ) {
1070         item.append( "<" + fn( s->offset ) + ">" );
1071         data = data.mid( s->offset, s->length );
1072     }
1073
1074     s->item = item;
1075     return data;
1076 }
1077
1078
1079 /* This function returns the response data for an element in
1080    d->sections, to be included in the FETCH response by
1081    fetchResponses() below. If \a unicode is false, the result will be
1082    downgraded rather than contain unicode.
1083 */
1084
1085 static EString sectionResponse( Section * s, Message * m, bool unicode )
1086 {
1087     EString data( Fetch::sectionData( s, m, unicode ) );
1088     if ( !s->item.startsWith( "BINARY.SIZE" ) )
1089         data = Command::imapQuoted( data, Command::NString );
1090     EString r;
1091     r.reserve( data.length() + s->item.length() + 1 );
1092     r.append( s->item );
1093     r.append( " " );
1094     r.append( data );
1095     return r;
1096 }
1097
1098
1099 /*! Emits a single FETCH response for the message \a m, which is
1100     trusted to have UID \a uid and MSN \a msn.
1101
1102     The message must have all necessary content.
1103 */
1104
1105 EString Fetch::makeFetchResponse( Message * m, uint uid, uint msn )
1106 {
1107     EStringList l;
1108     if ( d->uid )
1109         l.append( "UID " + fn( uid ) );
1110     if ( d->databaseId )
1111         l.append( "MSGID " + fn( m->databaseId() ) );
1112     if ( d->threadId )
1113         l.append( "THRID " + fn( m->threadId() ) );
1114     if ( d->rfc822size )
1115         l.append( "RFC822.SIZE " + fn( m->rfc822Size() ) );
1116     if ( d->flags )
1117         l.append( "FLAGS (" + flagList( uid ) + ")" );
1118     if ( d->internaldate )
1119         l.append( "INTERNALDATE " + internalDate( m ) );
1120     if ( d->envelope )
1121         l.append( "ENVELOPE " + envelope( m ) );
1122     if ( d->body )
1123         l.append( "BODY " + bodyStructure( m, false ) );
1124     if ( d->bodystructure )
1125         l.append( "BODYSTRUCTURE " + bodyStructure( m, true ) );
1126     if ( d->annotation )
1127         l.append( "ANNOTATION " + annotation( imap()->user(), uid,
1128                                               d->entries, d->attribs ) );
1129     if ( d->modseq ) {
1130         FetchData::DynamicData * dd = d->dynamics.find( uid );
1131         if ( dd && dd->modseq )
1132             l.append( "MODSEQ (" + fn( dd->modseq ) + ")" );
1133     }
1134
1135     List< Section >::Iterator it( d->sections );
1136     bool unicode = imap()->clientSupports( IMAP::Unicode );
1137     while ( it ) {
1138         l.append( sectionResponse( it, m, unicode ) );
1139         ++it;
1140     }
1141
1142     EString r;
1143     EString payload = l.join( " " );
1144     r.reserve( payload.length() + 30 );
1145     r.appendNumber( msn );
1146     r.append( " FETCH (" );
1147     r.append( payload );
1148     r.append( ")" );
1149     return r;
1150 }
1151
1152
1153 /*! Returns a string containing all the flags that are set for the
1154     message with \a uid.
1155 */
1156
1157 EString Fetch::flagList( uint uid )
1158 {
1159     EStringList r;
1160
1161     FetchData::DynamicData * dd = d->dynamics.find( uid );
1162     if ( dd ) {
1163         if ( session()->isRecent( uid ) )
1164             dd->flags.insert( "\\recent", new EString( "\\Recent" ) );
1165         Dict<EString>::Iterator i( dd->flags );
1166         while ( i ) {
1167             r.append( *i );
1168             ++i;
1169         }
1170     }
1171
1172     return r.join( " " );
1173 }
1174
1175
1176 /*! Returns the internaldate of \a m in IMAP format. */
1177
1178 EString Fetch::internalDate( Message * m )
1179 {
1180     Date date;
1181     date.setUnixTime( m->internalDate() );
1182     return "\"" + date.imap() + "\"";
1183 }
1184
1185
1186 static EString hf( Header * f, HeaderField::Type t, bool unicodable )
1187 {
1188     List<Address> * a = f->addresses( t );
1189     if ( !a || a->isEmpty() )
1190         return "NIL ";
1191     EString r;
1192     r.reserve( 50 );
1193     r.append( "(" );
1194     List<Address>::Iterator it( a );
1195     while ( it ) {
1196         r.append( "(" );
1197         if ( it->type() == Address::EmptyGroup ) {
1198             r.append( "NIL NIL " );
1199             r.append( Command::imapQuoted( it->name( !unicodable ),
1200                                            Command::NString ) );
1201             r.append( " NIL)(NIL NIL NIL NIL" );
1202         } else if ( it->type() == Address::Local ||
1203                     it->type() == Address::Normal ) {
1204             UString u = it->uname();
1205             EString eu;
1206             if ( u.isAscii() || unicodable )
1207                 eu = u.simplified().utf8();
1208             else
1209                 eu = HeaderField::encodePhrase( u );
1210             r.append( Command::imapQuoted( eu, Command::NString ) );
1211             r.append( " NIL " );
1212             if ( unicodable ||
1213                  ( it->localpart().isAscii() && it->domain().isAscii() ) ) {
1214                 r.append( Command::imapQuoted( it->localpart().utf8(),
1215                                                Command::NString ) );
1216                 r.append( " " );
1217                 if ( it->domain().isEmpty() )
1218                     r.append( "\" \"" ); // RFC 3501, page 77 near bottom
1219                 else
1220                     r.append( Command::imapQuoted( it->domain().utf8(),
1221                                                    Command::NString ) );
1222             }
1223             else {
1224                 r.append( "noreply unicode-needed.invalid" );
1225             }
1226         }
1227         r.append( ")" );
1228         ++it;
1229     }
1230     r.append( ") " );
1231     return r;
1232 }
1233
1234
1235 /*! Returns the IMAP envelope for \a m. */
1236
1237 EString Fetch::envelope( Message * m )
1238 {
1239     Header * h = m->header();
1240
1241     // envelope = "(" env-date SP env-subject SP env-from SP
1242     //                env-sender SP env-reply-to SP env-to SP env-cc SP
1243     //                env-bcc SP env-in-reply-to SP env-message-id ")"
1244
1245     EString r;
1246     r.reserve( 300 );
1247     r.append( "(" );
1248
1249     Date * date = h->date();
1250     if ( date )
1251         r.append( imapQuoted( date->rfc822(), NString ) );
1252     else
1253         r.append( "NIL" );
1254     r.append( " " );
1255
1256     r.append( imapQuoted( h->subject(), NString ) + " " );
1257     bool unicode = imap()->clientSupports( IMAP::Unicode );
1258     r.append( hf( h, HeaderField::From, unicode ) );
1259     r.append( hf( h, HeaderField::Sender, unicode ) );
1260     r.append( hf( h, HeaderField::ReplyTo, unicode ) );
1261     r.append( hf( h, HeaderField::To, unicode ) );
1262     r.append( hf( h, HeaderField::Cc, unicode ) );
1263     r.append( hf( h, HeaderField::Bcc, unicode ) );
1264     r.append( imapQuoted( h->inReplyTo(), NString ) + " " );
1265     r.append( imapQuoted( h->messageId(), NString ) );
1266
1267     r.append( ")" );
1268     return r;
1269 }
1270
1271
1272 static EString parameterEString( MimeField *mf )
1273 {
1274     EStringList *p = 0;
1275
1276     if ( mf )
1277         p = mf->parameters();
1278     if ( !mf || !p || p->isEmpty() )
1279         return "NIL";
1280
1281     EStringList l;
1282     EStringList::Iterator it( p );
1283     while ( it ) {
1284         l.append( Command::imapQuoted( *it ) );
1285         l.append( Command::imapQuoted( mf->parameter( *it ) ) );
1286         ++it;
1287     }
1288
1289     EString r = l.join( " " );
1290     r.prepend( "(" );
1291     r.append( ")" );
1292     return r;
1293 }
1294
1295
1296 static EString dispositionEString( ContentDisposition *cd )
1297 {
1298     if ( !cd )
1299         return "NIL";
1300
1301     EString s;
1302     switch ( cd->disposition() ) {
1303     case ContentDisposition::Inline:
1304         s = "inline";
1305         break;
1306     case ContentDisposition::Attachment:
1307         s = "attachment";
1308         break;
1309     }
1310
1311     return "(\"" + s + "\" " + parameterEString( cd ) + ")";
1312 }
1313
1314
1315 static EString languageEString( ContentLanguage *cl )
1316 {
1317     if ( !cl )
1318         return "NIL";
1319
1320     EStringList m;
1321     const EStringList *l = cl->languages();
1322     EStringList::Iterator it( l );
1323     while ( it ) {
1324         m.append( Command::imapQuoted( *it ) );
1325         ++it;
1326     }
1327
1328     if ( l->count() == 1 )
1329         return *m.first();
1330     EString r = m.join( " " );
1331     r.prepend( "(" );
1332     r.append( ")" );
1333     return r;
1334 }
1335
1336
1337 /*! Returns either the IMAP BODY or BODYSTRUCTURE production for \a
1338     m. If \a extended is true, BODYSTRUCTURE is returned. If it's
1339     false, BODY.
1340 */
1341
1342 EString Fetch::bodyStructure( Multipart * m, bool extended )
1343 {
1344     EString r;
1345
1346     Header * hdr = m->header();
1347     ContentType * ct = hdr->contentType();
1348
1349     if ( ct && ct->type() == "multipart" ) {
1350         EStringList children;
1351         List< Bodypart >::Iterator it( m->children() );
1352         while ( it ) {
1353             children.append( bodyStructure( it, extended ) );
1354             ++it;
1355         }
1356
1357         r = children.join( "" );
1358         r.prepend( "(" );
1359         r.append( " " );
1360         r.append( imapQuoted( ct->subtype() ));
1361
1362         if ( extended ) {
1363             r.append( " " );
1364             r.append( parameterEString( ct ) );
1365             r.append( " " );
1366             r.append( dispositionEString( hdr->contentDisposition() ) );
1367             r.append( " " );
1368             r.append( languageEString( hdr->contentLanguage() ) );
1369             r.append( " " );
1370             r.append( imapQuoted( hdr->contentLocation(), NString ) );
1371         }
1372
1373         r.append( ")" );
1374     }
1375     else {
1376         r = singlePartStructure( (Bodypart*)m, extended );
1377     }
1378
1379     return r;
1380 }
1381
1382
1383 /*! Returns the structure of the single-part bodypart \a mp.
1384
1385     If \a extended is true, extended BODYSTRUCTURE attributes are
1386     included.
1387 */
1388
1389 EString Fetch::singlePartStructure( Multipart * mp, bool extended )
1390 {
1391     EStringList l;
1392
1393     if ( !mp )
1394         return "";
1395
1396     ContentType * ct = mp->header()->contentType();
1397
1398     if ( ct ) {
1399         l.append( imapQuoted( ct->type() ) );
1400         l.append( imapQuoted( ct->subtype() ) );
1401     }
1402     else {
1403         // XXX: What happens to the default if this is a /digest?
1404         l.append( "\"text\"" );
1405         l.append( "\"plain\"" );
1406     }
1407
1408     l.append( parameterEString( ct ) );
1409     l.append( imapQuoted( mp->header()->messageId( HeaderField::ContentId ),
1410                           NString ) );
1411     l.append( imapQuoted( mp->header()->contentDescription(), NString ) );
1412
1413     if ( mp->header()->contentTransferEncoding() ) {
1414         switch( mp->header()->contentTransferEncoding()->encoding() ) {
1415         case EString::Binary:
1416             l.append( "\"8BIT\"" ); // hm. is this entirely sound?
1417             break;
1418         case EString::Uuencode:
1419             l.append( "\"x-uuencode\"" ); // should never happen
1420             break;
1421         case EString::Base64:
1422             l.append( "\"BASE64\"" );
1423             break;
1424         case EString::QP:
1425             l.append( "\"QUOTED-PRINTABLE\"" );
1426             break;
1427         }
1428     }
1429     else {
1430         l.append( "\"7BIT\"" );
1431     }
1432
1433     Bodypart * bp = 0;
1434     if ( mp->isBodypart() )
1435         bp = (Bodypart*)mp;
1436     else if ( mp->isMessage() )
1437         bp = ((Message*)mp)->children()->first();
1438
1439     if ( bp ) {
1440         l.append( fn( bp->numEncodedBytes() ) );
1441         if ( ct && ct->type() == "message" && ct->subtype() == "rfc822" ) {
1442             // body-type-msg   = media-message SP body-fields SP envelope
1443             //                   SP body SP body-fld-lines
1444             l.append( envelope( bp->message() ) );
1445             l.append( bodyStructure( bp->message(), extended ) );
1446             l.append( fn ( bp->numEncodedLines() ) );
1447         }
1448         else if ( !ct || ct->type() == "text" ) {
1449             // body-type-text  = media-text SP body-fields SP body-fld-lines
1450             l.append( fn( bp->numEncodedLines() ) );
1451         }
1452     }
1453
1454     if ( extended ) {
1455         EString md5;
1456         HeaderField *f = mp->header()->field( HeaderField::ContentMd5 );
1457         if ( f )
1458             md5 = f->rfc822( false );
1459
1460         l.append( imapQuoted( md5, NString ) );
1461         l.append( dispositionEString( mp->header()->contentDisposition() ) );
1462         l.append( languageEString( mp->header()->contentLanguage() ) );
1463         l.append( imapQuoted( mp->header()->contentLocation(), NString ) );
1464     }
1465
1466     EString r = l.join( " " );
1467     r.prepend( "(" );
1468     r.append( ")" );
1469     return r;
1470 }
1471
1472
1473 /*! Returns the IMAP ANNOTATION production for the message with \a
1474     uid, from the point of view of \a u (0 for no user, only public
1475     annotations). \a entrySpecs is a list of the entries to be
1476     matched, each of which can contain the * and % wildcards. \a
1477     attributes is a list of attributes to be returned (each including
1478     the .priv or .shared suffix).
1479 */
1480
1481 EString Fetch::annotation( User * u, uint uid,
1482                           const EStringList & entrySpecs,
1483                           const EStringList & attributes )
1484 {
1485     FetchData::DynamicData * dd = d->dynamics.find( uid );
1486     if ( !dd ) {
1487         setRespTextCode( "SERVERBUG" );
1488         return "()";
1489     }
1490
1491     typedef Dict< EString > AttributeDict;
1492     Dict< AttributeDict > entries;
1493
1494     EStringList entryNames;
1495
1496     uint user = 0;
1497     if ( u )
1498         user = u->id();
1499     List<Annotation>::Iterator i( dd->annotations );
1500     while ( i ) {
1501         Annotation * a = i;
1502         ++i;
1503
1504         EString entry( a->entryName() );
1505         bool entryWanted = false;
1506         EStringList::Iterator e( entrySpecs );
1507         while ( e && !entryWanted ) {
1508             AsciiCodec c;
1509             if ( Mailbox::match( c.toUnicode( *e ), 0,
1510                                  c.toUnicode( entry ), 0 ) == 2 ) {
1511                 if ( !entries.find( entry ) )
1512                     entryNames.append( entry );
1513                 entryWanted = true;
1514             }
1515             ++e;
1516         }
1517
1518         if ( ( a->ownerId() == 0 || a->ownerId() == user ) &&
1519              entryWanted )
1520         {
1521             AttributeDict * atts = entries.find( entry );
1522             if ( !atts ) {
1523                 atts = new AttributeDict;
1524                 entries.insert( entry, atts );
1525             }
1526
1527             const char * suffix = ".shared";
1528             if ( a->ownerId() )
1529                 suffix = ".priv";
1530
1531             EString * v = new EString( a->value() );
1532             EString * s = new EString( fn( v->length() ) );
1533
1534             atts->insert( EString( "value" ) + suffix, v );
1535             atts->insert( EString( "size" ) + suffix, s );
1536         }
1537     }
1538
1539     EString r( "(" );
1540     EStringList::Iterator e( entryNames );
1541     while ( e ) {
1542         EString entry( *e );
1543
1544         EStringList l;
1545         EStringList::Iterator a( attributes );
1546         while ( a ) {
1547             EString attrib( *a );
1548
1549             EString * value = 0;
1550             AttributeDict * atts = entries.find( entry );
1551             if ( atts )
1552                 value = atts->find( attrib );
1553
1554             EString tmp = attrib;
1555             tmp.append( " " );
1556             if ( value )
1557                 tmp.append( imapQuoted( *value ) );
1558             else if ( attrib.startsWith( "size." ) )
1559                 tmp.append( "\"0\"" );
1560             else
1561                 tmp.append( "NIL" );
1562             ++a;
1563             l.append( tmp );
1564         }
1565
1566         r.append( entry );
1567         if ( !l.isEmpty() ) {
1568             r.append( " (" );
1569             r.append( l.join( " " ) );
1570             r.append( ")" );
1571         }
1572
1573         ++e;
1574         if ( e )
1575             r.append( " " );
1576     }
1577     r.append( ")" );
1578     return r;
1579 }
1580
1581
1582 /*! Parses a single RFC 4466 fetch-modifier. At the moment RFC 4551
1583     and RFC 7162 are supported.
1584 */
1585
1586 void Fetch::parseFetchModifier()
1587 {
1588     EString name = atom().lower();
1589     if ( name == "changedsince" ) {
1590         space();
1591         d->changedSince = number();
1592         d->modseq = true;
1593     }
1594     else if ( name == "vanished" ) {
1595         d->vanished = true;
1596     }
1597     else {
1598         error( Bad, "Unknown fetch modifier: " + name );
1599     }
1600 }
1601
1602
1603 /*! Retrieves completed messages and builds ImapFetchResponse objects.
1604 */
1605
1606 void Fetch::pickup()
1607 {
1608     ImapSession * s = (ImapSession *)imap()->session();
1609     if ( !s )
1610         return;
1611
1612     if ( d->seenDeletedFetcher ) {
1613         EString seenl( "\\seen" );
1614         EString * seen = new EString( "\\Seen" );
1615         EString deletedl( "\\deleted" );
1616         EString * deleted = new EString( "\\Deleted" );
1617         while ( d->seenDeletedFetcher->hasResults() ) {
1618             Row * r = d->seenDeletedFetcher->nextRow();
1619             uint uid = r->getInt( "uid" );
1620             FetchData::DynamicData * dd = d->dynamics.find( uid );
1621             if ( !dd ) {
1622                 dd = new FetchData::DynamicData;
1623                 d->dynamics.insert( uid, dd );
1624             }
1625             if ( r->getBoolean( "seen" ) )
1626                 dd->flags.insert( seenl, seen );
1627             if ( r->getBoolean( "deleted" ) )
1628                 dd->flags.insert( deletedl, deleted );
1629         }
1630         while ( d->flagFetcher->hasResults() ) {
1631             Row * r = d->flagFetcher->nextRow();
1632             uint uid = r->getInt( "uid" );
1633             FetchData::DynamicData * dd = d->dynamics.find( uid );
1634             if ( !dd ) {
1635                 dd = new FetchData::DynamicData;
1636                 d->dynamics.insert( uid, dd );
1637             }
1638             EString f = r->getEString( "name" );
1639             if ( !f.isEmpty() )
1640                 dd->flags.insert( f.lower(), new EString( f ) );
1641         }
1642         if ( d->seenDeletedFetcher->done() &&
1643              d->flagFetcher->done() ) {
1644             d->seenDeletedFetcher = 0;
1645             d->flagFetcher = 0;
1646         }
1647     }
1648
1649     if ( d->annotationFetcher ) {
1650         while ( d->annotationFetcher->hasResults() ) {
1651             Row * r = d->annotationFetcher->nextRow();
1652             uint uid = r->getInt( "uid" );
1653             FetchData::DynamicData * dd = d->dynamics.find( uid );
1654             if ( !dd ) {
1655                 dd = new FetchData::DynamicData;
1656                 d->dynamics.insert( uid, dd );
1657             }
1658
1659             EString n = r->getEString( "name" );
1660             EString v( r->getEString( "value" ) );
1661
1662             uint owner = 0;
1663             if ( !r->isNull( "owner" ) )
1664                 owner = r->getInt( "owner" );
1665
1666             dd->annotations.append( new Annotation( n, v, owner ) );
1667         }
1668     }
1669
1670     if ( d->modseqFetcher ) {
1671         while ( d->modseqFetcher->hasResults() ) {
1672             Row * r = d->modseqFetcher->nextRow();
1673             uint uid = r->getInt( "uid" );
1674             FetchData::DynamicData * dd = d->dynamics.find( uid );
1675             if ( !dd ) {
1676                 dd = new FetchData::DynamicData;
1677                 d->dynamics.insert( uid, dd );
1678             }
1679             dd->modseq = r->getBigint( "modseq" );
1680         }
1681     }
1682
1683     if ( d->seenDeletedFetcher && !d->seenDeletedFetcher->done() )
1684         return;
1685
1686     if ( d->flagFetcher && !d->flagFetcher->done() )
1687         return;
1688
1689     if ( d->annotationFetcher && !d->annotationFetcher->done() )
1690         return;
1691
1692     if ( d->modseqFetcher && !d->modseqFetcher->done() )
1693         return;
1694
1695     bool ok = true;
1696     uint done = 0;
1697     while ( ok && !d->remaining.isEmpty() ) {
1698         uint uid = d->remaining.smallest();
1699         Message * m = d->messages.find( uid );
1700         if ( d->needsAddresses && !m->hasAddresses() )
1701             ok = false;
1702         if ( d->needsHeader && !m->hasHeaders() )
1703             ok = false;
1704         if ( d->needsPartNumbers && !m->hasBytesAndLines() )
1705             ok = false;
1706         if ( d->needsBody && !m->hasBodies() )
1707             ok = false;
1708         if ( ( d->rfc822size || d->internaldate ||
1709                d->databaseId || d->threadId ) && !m->hasTrivia() )
1710             ok = false;
1711         if ( ok ) {
1712             d->processed = uid;
1713             d->remaining.remove( uid );
1714             done++;
1715             waitFor( new ImapFetchResponse( s, this, uid ) );
1716         }
1717     }
1718
1719     if ( !done )
1720         return;
1721     log( "Processed " + fn( done ) + " messages", Log::Debug );
1722     imap()->emitResponses();
1723 }
1724
1725
1726 /*! \class ImapFetchResponse fetch.h
1727
1728     The ImapFetchResponse class models a single FETCH response. Its
1729     primary responsibity is to pick the right MSN at send time.
1730 */
1731
1732
1733 /*! Constructs a FETCH response for the message with \a uid with the
1734     data \a fetch fetched, if and only if \a s is active when it's
1735     time to send.
1736 */
1737
1738 ImapFetchResponse::ImapFetchResponse( ImapSession * s,
1739                                       Fetch * fetch, uint uid )
1740     : ImapResponse( s ), f( fetch ), u( uid )
1741 {
1742 }
1743
1744
1745 EString ImapFetchResponse::text() const
1746 {
1747     uint msn = session()->msn( u );
1748     if ( u && msn )
1749         return f->makeFetchResponse( f->message( u ), u, msn );
1750     return "";
1751 }
1752
1753
1754 /*! This reimplementation of setSent() frees up memory... that
1755     shouldn't be necessary when using garbage collection, but in this
1756     case it's important to remove messages from the data structures
1757     when they've been sent, so the collector sees that the memory can
1758     be reused. If we don't, then all of the messages occupy RAM until
1759     the last one has been sent.
1760 */
1761
1762 void ImapFetchResponse::setSent()
1763 {
1764     f->forget( u );
1765     ImapResponse::setSent();
1766 }
1767
1768
1769 /*! This dangerous function makes the Fetch handler forget (part of)
1770     what it knows about \a uid. If Fetch has processed \a uid to
1771     completion, then forget() frees up memory for other use. To be
1772     used only by ImapFetchResponse::setSent().
1773 */
1774
1775 void Fetch::forget( uint uid )
1776 {
1777     d->messages.remove( uid );
1778 }
1779
1780
1781 /*! Returns a pointer to the message with \a uid that this command has
1782     fetched or will fetch.
1783 */
1784
1785 Message * Fetch::message( uint uid ) const
1786 {
1787     return d->messages.find( uid );
1788 }
1789
1790
1791 /*! Sends a query to retrieve all flags. */
1792
1793 void Fetch::sendFlagQuery()
1794 {
1795     d->seenDeletedFetcher = new Query(
1796         "select uid, seen, deleted from mailbox_messages "
1797         "where mailbox=$1 and uid=any($2)",
1798         this );
1799     d->seenDeletedFetcher->bind( 1, session()->mailbox()->id() );
1800     d->seenDeletedFetcher->bind( 2, d->set );
1801     enqueue( d->seenDeletedFetcher );
1802
1803     d->flagFetcher = new Query(
1804         "select f.uid, fn.name from flags f "
1805         "join flag_names fn on (f.flag=fn.id) "
1806         "where f.mailbox=$1 and f.uid=any($2)",
1807         this );
1808     d->flagFetcher->bind( 1, session()->mailbox()->id() );
1809     d->flagFetcher->bind( 2, d->set );
1810     enqueue( d->flagFetcher );
1811 }
1812
1813
1814 /*! Sends a query to retrieve all annotations. */
1815
1816 void Fetch::sendAnnotationsQuery()
1817 {
1818     d->annotationFetcher = new Query(
1819         "select a.uid, "
1820         "a.owner, a.value, an.name "
1821         "from annotations a "
1822         "join annotation_names an on (a.name=an.id) "
1823         "where a.mailbox=$1 and a.uid=any($2) "
1824         "order by an.name",
1825         this );
1826     d->annotationFetcher->bind( 1, session()->mailbox()->id() );
1827     d->annotationFetcher->bind( 2, d->set );
1828     enqueue( d->annotationFetcher );
1829 }
1830
1831
1832 /*! Sends a query to retrieve the modseq. */
1833
1834 void Fetch::sendModSeqQuery()
1835 {
1836     d->modseqFetcher = new Query(
1837         "select uid, modseq "
1838         "from mailbox_messages "
1839         "where mailbox=$1 and uid=any($2)",
1840         this );
1841     d->modseqFetcher->bind( 1, session()->mailbox()->id() );
1842     d->modseqFetcher->bind( 2, d->set );
1843     enqueue( d->modseqFetcher );
1844 }
1845
1846
1847 /*! This helper enqueues \a q for execution, either directly of via a
1848     transaction.
1849 */
1850
1851 void Fetch::enqueue( Query * q )
1852 {
1853     if ( transaction() )
1854         transaction()->enqueue( q );
1855     else
1856         q->execute();
1857 }