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