IntegerSet IntegerSet::set_union(const IntegerSet& o){ IntegerSet out; for(int i = 0; i < num_elements; i++){ out.add(elements[i]); } for(int i = 0; i < o.num_elements; i++){ out.add(o.elements[i]); } return out; }
IntegerSet Command::set( bool parseMsns = false ) { IntegerSet result; ImapSession *s = 0; if ( imap() ) s = imap()->session(); uint n1 = 0, n2 = 0; bool done = false; while ( ok() && !done ) { char c = nextChar(); if ( c == '*' ) { step(); n1 = 0; if ( s ) n1 = s->largestUid(); else error( Bad, "Need a mailbox session to use * as an UID/MSN" ); } else if ( c >= '1' && c <= '9' ) { if ( parseMsns ) n1 = msn(); else n1 = nzNumber(); } else { error( Bad, "number or '*' expected, saw: " + following() ); } c = nextChar(); if ( c == ':' ) { if ( n2 ) error( Bad, "saw colon after range (" + fn( n1 ) + ":" + fn( n2 ) + "), saw:" + following() ); n2 = n1; n1 = 0; step(); } else if ( ok() ) { if ( n2 ) result.add( n1, n2 ); else result.add( n1 ); n1 = 0; n2 = 0; if ( c == ',' ) step(); else done = true; } }; return result; }
void ImapSession::emitUpdates( Transaction * t ) { if ( d->emitting ) return; d->emitting = true; bool work = false; Scope x( d->l ); IntegerSet e; e.add( expunged() ); e.remove( d->expungesReported ); if ( !e.isEmpty() ) { d->expungesReported.add( e ); while ( !e.isEmpty() ) { (void)new ImapExpungeResponse( e.smallest(), this ); work = true; e.remove( e.smallest() ); } } emitFlagUpdates( t ); if ( d->uidnext < uidnext() ) { if ( !d->existsResponse ) { d->existsResponse = new ImapSessionData::ExistsResponse( this, d ); work = true; } if ( !d->recentResponse ) { d->recentResponse = new ImapSessionData::RecentResponse( this, d ); work = true; } if ( !d->uidnextResponse ) { d->uidnextResponse = new ImapSessionData::UidnextResponse( this, d ); work = true; } } if ( d->nms < nextModSeq() ) d->nms = nextModSeq(); if ( d->changed.isEmpty() ) d->cms = d->nms; if ( work ) d->i->unblockCommands(); d->i->emitResponses(); d->emitting = false; }
IntegerSet IntegerSet::set_difference(const IntegerSet& o){ IntegerSet out; // for(int i = 0; i < o.num_elements; i++){ // if(!contains(o.elements[i])){ // out.add(o.elements[i]); // } // } for(int i = 0; i < num_elements; i++){ if(!o.contains(elements[i])){ out.add(elements[i]); } } return out; }
void MaildirMailbox::readSubDir( const EString & sub ) { DIR * dir = opendir( ( d->path + "/" + sub ).cstr() ); if ( !dir ) return; Map<EStringList> files; IntegerSet times; struct dirent * de = readdir( dir ); while ( de ) { if ( de->d_name[0] >= '0' && de->d_name[0] <= '9' ) { EString n( de->d_name ); int i = n.find( '.' ); bool ok = false; if ( i > 0 ) i = n.mid( 0, i ).number( &ok ); if ( ok ) { EString * f = new EString ( sub ); f->append( "/" ); f->append( n ); EStringList * l = 0; l = files.find( i ); if ( !l ) { l = new EStringList; files.insert( i, l ); times.add( i ); // XXX: how very evil } l->append( f ); } else { // no dot in the name... what is it? } } de = readdir( dir ); } closedir( dir ); while ( !times.isEmpty() ) { uint n = times.smallest(); times.remove( n ); EStringList * l = files.find( n ); EStringList::Iterator i( *l ); while ( i ) { d->messages.append( *i ); ++i; } } }
void SessionInitialiser::recordExpunges() { if ( !d->expunges ) return; Row * r = 0; IntegerSet uids; while ( (r=d->expunges->nextRow()) != 0 ) uids.add( r->getInt( "uid" ) ); if ( uids.isEmpty() ) return; List<Session>::Iterator i( d->sessions ); while ( i ) { Session * s = i; ++i; s->expunge( uids ); } }
void Select::execute() { if ( state() != Executing ) return; if ( Flag::id( "\\Deleted" ) == 0 ) { // should only happen when we flush the entire database during // testing, so we don't bother being accurate or fast, but // simply try again in a second. (void)new Timer( this, 1 ); return; } if ( !d->permissions ) { if ( d->condstore ) imap()->setClientSupports( IMAP::Condstore ); if ( d->annotate ) imap()->setClientSupports( IMAP::Annotate ); if ( d->mailbox->deleted() ) error( No, d->mailbox->name().ascii() + " is deleted" ); if ( !ok() ) { finish(); return; } d->permissions = new Permissions( d->mailbox, imap()->user(), this ); } if ( d->permissions && !d->session ) { if ( !d->permissions->ready() ) return; if ( !d->permissions->allowed( Permissions::Read ) ) { error( No, d->mailbox->name().ascii() + " is not accessible" ); finish(); return; } if ( !d->readOnly && !d->permissions->allowed( Permissions::KeepSeen ) ) d->readOnly = true; } if ( !::firstUnseenCache ) ::firstUnseenCache = new SelectData::FirstUnseenCache; if ( mailboxGroup() && !d->sessionPreloader ) { d->sessionPreloader = new SessionPreloader( mailboxGroup()->contents(), this ); d->sessionPreloader->execute(); IntegerSet s; List<Mailbox>::Iterator i( mailboxGroup()->contents() ); while ( i ) { if ( !::firstUnseenCache->find( i, i->nextModSeq() ) ) s.add( i->id() ); ++i; } if ( s.count() > 2 ) { d->cacheFirstUnseen = new Query( "select min(uid) as uid, mailbox, " "max(modseq) as modseq " "from mailbox_messages mm " "where mailbox=any($1) and not seen " "group by mailbox", this ); d->cacheFirstUnseen->bind( 1, s ); d->cacheFirstUnseen->execute(); } } if ( d->sessionPreloader ) { while ( d->cacheFirstUnseen && d->cacheFirstUnseen->hasResults() ) { Row * r = d->cacheFirstUnseen->nextRow(); Mailbox * m = Mailbox::find( r->getInt( "mailbox" ) ); ::firstUnseenCache->insert( m, 1 + r->getBigint( "modseq" ), r->getInt( "uid" ) ); } if ( d->cacheFirstUnseen && !d->cacheFirstUnseen->done() ) return; if ( !d->sessionPreloader->done() ) return; } if ( !d->session ) { d->session = new ImapSession( imap(), d->mailbox, d->readOnly ); d->session->setPermissions( d->permissions ); imap()->beginSession( d->session ); } if ( !d->session->initialised() ) return; if ( d->session->isEmpty() ) d->needFirstUnseen = false; else if ( ::firstUnseenCache && ::firstUnseenCache->find( d->mailbox, d->session->nextModSeq() ) ) d->needFirstUnseen = false; else d->needFirstUnseen = true; if ( d->needFirstUnseen && !d->firstUnseen ) { d->firstUnseen = new Query( "select uid from mailbox_messages mm " "where mailbox=$1 and not seen " "order by uid limit 1", this ); d->firstUnseen->bind( 1, d->mailbox->id() ); d->firstUnseen->execute(); } if ( d->firstUnseen && !d->firstUnseen->done() ) return; d->session->emitUpdates( 0 ); // emitUpdates often calls Imap::runCommands, which calls this // function, which will then change its state to Finished. so // check that and don't repeat the last few responses. if ( state() != Executing ) return; respond( "OK [UIDVALIDITY " + fn( d->session->uidvalidity() ) + "]" " uid validity" ); if ( d->firstUnseen ) { if ( !::firstUnseenCache ) ::firstUnseenCache = new SelectData::FirstUnseenCache; Row * r = d->firstUnseen->nextRow(); if ( r ) ::firstUnseenCache->insert( d->mailbox, d->session->nextModSeq(), r->getInt( "uid" ) ); } if ( ::firstUnseenCache ) { uint unseen = ::firstUnseenCache->find( d->mailbox, d->session->nextModSeq() ); if ( unseen ) respond( "OK [UNSEEN " + fn( d->session->msn( unseen ) ) + "] first unseen" ); } if ( imap()->clientSupports( IMAP::Condstore ) && !d->session->isEmpty() ) { uint nms = d->session->nextModSeq(); if ( nms < 2 ) nms = 2; respond( "OK [HIGHESTMODSEQ " + fn( nms-1 ) + "] highest modseq" ); } if ( imap()->clientSupports( IMAP::Annotate ) ) { Permissions * p = d->session->permissions(); if ( p && p->allowed( Permissions::WriteSharedAnnotation ) ) respond( "OK [ANNOTATIONS 262144] Arbitrary limit" ); else respond( "OK [ANNOTATIONS READ-ONLY] Missing 'n' right" ); } if ( d->session->readOnly() ) setRespTextCode( "READ-ONLY" ); else setRespTextCode( "READ-WRITE" ); finish(); }
void Status::execute() { if ( state() != Executing ) return; // first part. set up. if ( !permitted() ) return; Session * session = imap()->session(); Mailbox * current = 0; if ( session ) current = session->mailbox(); // second part. see if anything has happened, and feed the cache if // so. make sure we feed the cache at once. if ( d->unseenCount || d->recentCount || d->messageCount ) { if ( d->unseenCount && !d->unseenCount->done() ) return; if ( d->messageCount && !d->messageCount->done() ) return; if ( d->recentCount && !d->recentCount->done() ) return; } if ( !::cache ) ::cache = new StatusData::StatusCache; if ( d->unseenCount ) { while ( d->unseenCount->hasResults() ) { Row * r = d->unseenCount->nextRow(); StatusData::CacheItem * ci = ::cache->find( r->getInt( "mailbox" ) ); if ( ci ) { ci->hasUnseen = true; ci->unseen = r->getInt( "unseen" ); } } } if ( d->recentCount ) { while ( d->recentCount->hasResults() ) { Row * r = d->recentCount->nextRow(); StatusData::CacheItem * ci = ::cache->find( r->getInt( "mailbox" ) ); if ( ci ) { ci->hasRecent = true; ci->recent = r->getInt( "recent" ); } } } if ( d->messageCount ) { while ( d->messageCount->hasResults() ) { Row * r = d->messageCount->nextRow(); StatusData::CacheItem * ci = ::cache->find( r->getInt( "mailbox" ) ); if ( ci ) { ci->hasMessages = true; ci->messages = r->getInt( "messages" ); } } } // third part. are we processing the first command in a STATUS // loop? if so, see if we ought to preload the cache. if ( mailboxGroup() && d->cacheState < 3 ) { IntegerSet mailboxes; if ( d->cacheState < 1 ) { // cache state 0: decide which messages List<Mailbox>::Iterator i( mailboxGroup()->contents() ); while ( i ) { StatusData::CacheItem * ci = ::cache->provide( i ); bool need = false; if ( d->unseen || d->recent || d->messages ) need = true; if ( ci->hasUnseen || ci->hasRecent || ci->hasMessages ) need = false; if ( need ) mailboxes.add( i->id() ); ++i; } if ( mailboxes.count() < 3 ) d->cacheState = 3; } if ( d->cacheState == 1 ) { // state 1: send queries if ( d->unseen ) { d->unseenCount = new Query( "select mailbox, count(uid)::int as unseen " "from mailbox_messages " "where mailbox=any($1) and not seen " "group by mailbox", this ); d->unseenCount->bind( 1, mailboxes ); d->unseenCount->execute(); } if ( d->recent ) { d->recentCount = new Query( "select id as mailbox, " "uidnext-first_recent as recent " "from mailboxes where id=any($1)", this ); d->recentCount->bind( 1, mailboxes ); d->recentCount->execute(); } if ( d->messages ) { d->messageCount = new Query( "select count(*)::int as messages, mailbox " "from mailbox_messages where mailbox=any($1) " "group by mailbox", this ); d->messageCount->bind( 1, mailboxes ); d->messageCount->execute(); } d->cacheState = 2; } if ( d->cacheState == 2 ) { // state 2: mark the cache as complete. IntegerSet mailboxes; List<Mailbox>::Iterator i( mailboxGroup()->contents() ); while ( i ) { StatusData::CacheItem * ci = ::cache->find( i->id() ); if ( ci && d->unseenCount ) ci->hasUnseen = true; if ( ci && d->recentCount ) ci->hasRecent = true; if ( ci && d->messageCount ) ci->hasMessages = true; ++i; } // and drop the queries d->cacheState = 3; d->unseenCount = 0; d->recentCount = 0; d->messageCount = 0; } } // the cache item we'll actually read from StatusData::CacheItem * i = ::cache->provide( d->mailbox ); // fourth part: send individual queries if there's anything we need if ( d->unseen && !d->unseenCount && !i->hasUnseen ) { d->unseenCount = new Query( "select $1::int as mailbox, " "count(uid)::int as unseen " "from mailbox_messages " "where mailbox=$1 and not seen", this ); d->unseenCount->bind( 1, d->mailbox->id() ); d->unseenCount->execute(); } if ( !d->recent ) { // nothing doing } else if ( d->mailbox == current ) { // we'll pick it up from the session } else if ( i->hasRecent ) { // the cache has it } else if ( !d->recentCount ) { d->recentCount = new Query( "select id as mailbox, " "uidnext-first_recent as recent " "from mailboxes where id=$1", this ); d->recentCount->bind( 1, d->mailbox->id() ); d->recentCount->execute(); } if ( !d->messages ) { // we don't need to collect } else if ( d->mailbox == current ) { // we'll pick it up } else if ( i->hasMessages ) { // the cache has it } else if ( d->messages && !d->messageCount ) { d->messageCount = new Query( "select count(*)::int as messages, " "$1::int as mailbox " "from mailbox_messages where mailbox=$1", this ); d->messageCount->bind( 1, d->mailbox->id() ); d->messageCount->execute(); } if ( d->unseenCount || d->recentCount || d->messageCount ) { if ( d->unseenCount && !d->unseenCount->done() ) return; if ( d->messageCount && !d->messageCount->done() ) return; if ( d->recentCount && !d->recentCount->done() ) return; } // fifth part: return the payload. EStringList status; if ( d->messages && i->hasMessages ) status.append( "MESSAGES " + fn( i->messages ) ); else if ( d->messages && d->mailbox == current ) status.append( "MESSAGES " + fn( session->messages().count() ) ); if ( d->recent && i->hasRecent ) status.append( "RECENT " + fn( i->recent ) ); else if ( d->recent && d->mailbox == current ) status.append( "RECENT " + fn( session->recent().count() ) ); if ( d->uidnext ) status.append( "UIDNEXT " + fn( d->mailbox->uidnext() ) ); if ( d->uidvalidity ) status.append( "UIDVALIDITY " + fn( d->mailbox->uidvalidity() ) ); if ( d->unseen && i->hasUnseen ) status.append( "UNSEEN " + fn( i->unseen ) ); if ( d->modseq ) { int64 hms = d->mailbox->nextModSeq(); // don't like this. an empty mailbox will have a STATUS HMS of // 1 before it receives its first message, and also 1 after. if ( hms > 1 ) hms--; status.append( "HIGHESTMODSEQ " + fn( hms ) ); } respond( "STATUS " + imapQuoted( d->mailbox ) + " (" + status.join( " " ) + ")" ); finish(); }
void SpoolManager::execute() { // Fetch a list of spooled messages, and the next time we can try // to deliver each of them. uint delay = UINT_MAX; if ( !d->q ) { IntegerSet have; List<DeliveryAgent>::Iterator a( d->agents ); while ( a ) { if ( a->working() ) { have.add( a->messageId() ); delay = 900; ++a; } else { d->agents.take( a ); } } log( "Starting queue run" ); d->again = false; reset(); EString s( "select d.message, " "extract(epoch from" " min(coalesce(dr.last_attempt+interval '900 s'," " d.deliver_after," " current_timestamp)))::bigint" "-extract(epoch from current_timestamp)::bigint as delay " "from deliveries d " "join delivery_recipients dr on (d.id=dr.delivery) " "where (dr.action=$1 or dr.action=$2) " ); if ( !have.isEmpty() ) s.append( "and not d.message=any($3) " ); s.append( "group by d.message " "order by delay" ); d->q = new Query( s, this ); d->q->bind( 1, Recipient::Unknown ); d->q->bind( 2, Recipient::Delayed ); if ( !have.isEmpty() ) d->q->bind( 3, have ); d->q->execute(); } if ( d->q && !d->q->done() ) return; // Is there anything we might do? if ( d->q && !d->q->rows() ) { // No. Just finish. reset(); log( "Ending queue run" ); return; } // Yes. What? if ( d->q ) { while ( d->q->hasResults() ) { Row * r = d->q->nextRow(); int64 deliverableAt = r->getBigint( "delay" ); if ( deliverableAt <= 0 ) { DeliveryAgent * a = new DeliveryAgent( r->getInt( "message" ) ); (void)new Timer( a, d->agents.count() * 5 ); d->agents.append( a ); } else if ( delay > deliverableAt ) delay = deliverableAt; } if ( delay < UINT_MAX ) { log( "Will process the queue again in " + fn( delay ) + " seconds" ); d->t = new Timer( this, delay ); } d->q = 0; } reset(); }
bool PopCommand::session() { if ( !d->mailbox ) { d->mailbox = d->pop->user()->inbox(); log( "Attempting to start a session on " + d->mailbox->name().ascii() ); d->permissions = new Permissions( d->mailbox, d->pop->user(), this ); } if ( !d->permissions->ready() ) return false; if ( !d->session ) { if ( !d->permissions->allowed( Permissions::Read ) ) { d->pop->err( "Insufficient privileges" ); return true; } else { bool ro = true; if ( d->permissions->allowed( Permissions::KeepSeen ) && d->permissions->allowed( Permissions::DeleteMessages ) && d->permissions->allowed( Permissions::Expunge ) ) ro = false; d->session = new PopCommandData::PopSession( d->mailbox, ro, this ); d->session->setPermissions( d->permissions ); d->pop->setSession( d->session ); } } if ( !d->session->initialised() ) return false; if ( !d->map ) { d->session->clearUnannounced(); IntegerSet s( d->session->messages() ); IntegerSet r; d->map = new Map<Message>; while ( !s.isEmpty() ) { uint uid = s.smallest(); s.remove( uid ); Message * m = MessageCache::provide( d->mailbox, uid ); if ( !m->databaseId() ) r.add( uid ); d->map->insert( uid, m ); } if ( !r.isEmpty() ) { d->findIds = new Query( "select message, uid " "from mailbox_messages " "where mailbox=$1 and uid=any($2)", this ); d->findIds->bind( 1, d->mailbox->id() ); d->findIds->bind( 2, r ); d->findIds->execute(); } } if ( d->findIds && !d->findIds->done() ) return false; while ( d->findIds && d->findIds->hasResults() ) { Row * r = d->findIds->nextRow(); Message * m = d->map->find( r->getInt( "uid" ) ); if ( m ) m->setDatabaseId( r->getInt( "message" ) ); } d->session->clearUnannounced(); d->pop->setMessageMap( d->map ); d->pop->setState( POP::Transaction ); d->pop->ok( "Done" ); return true; }