class SieveCommand * SieveParser::command() { whitespace(); SieveCommand * sc = new SieveCommand; sc->setParser( this ); sc->setStart( pos() ); sc->setIdentifier( identifier() ); sc->setArguments( arguments() ); whitespace(); if ( nextChar() == '{' ) { sc->setBlock( block() ); } else if ( present( ";" ) ) { // fine } else { setError( "Garbage after command: " + following() ); // if the line ends with ';', skip ahead to it uint x = pos(); while ( x < input().length() && input()[x] != '\n' && input()[x] != '\r' ) x++; if ( x > pos() && input()[x-1] == ';' ) step( x - pos() ); } sc->setError( error() ); sc->setEnd( pos() ); return sc; }
void SieveScript::parse( const EString & script ) { d->source = script; SieveParser p( script ); d->script = p.commands(); // if we're not yet at the end, treat whatever follows as another // command, which will have a nice big error message. p.whitespace(); if ( !p.atEnd() ) { SieveCommand * sc = p.command(); sc->setError( "Junk at end of script" ); d->script->append( sc ); } // require is only permitted at the start List<SieveCommand>::Iterator s( d->script ); while ( s && s->identifier() == "require" ) { s->setRequirePermitted( true ); ++s; } // do the semantic bits of parsing s = d->script->first(); EString prev; while ( s ) { s->setParent( this ); s->parse( prev ); prev = s->identifier(); ++s; } // check that require lists the right extensions EStringList * extensions = p.extensionsNeeded(); EStringList declared; s = d->script->first(); while ( s && s->identifier() == "require" ) { if ( s->error().isEmpty() ) { UStringList * r = 0; if ( s->arguments() && s->arguments()->arguments() && s->arguments()->arguments()->first() ) r = s->arguments()->arguments()->first()->stringList(); UStringList::Iterator i( r ); while ( i ) { if ( i->isAscii() ) declared.append( i->ascii() ); ++i; } } ++s; } declared.append( "comparator-i;octet" ); declared.append( "comparator-i;ascii-casemap" ); declared.append( "fileinto" ); declared.append( "reject" ); EStringList::Iterator i( extensions ); EStringList undeclared; while ( i ) { if ( !declared.contains( *i ) ) undeclared.append( i->quoted() ); ++i; } if ( !undeclared.isEmpty() ) { SieveCommand * f = d->script->first(); if ( f->identifier() == "require" ) f->setError( "Extensions used but not declared: " + undeclared.join( ", " ) ); else f->setError( "Missing require: require [ " + undeclared.join( ", " ) + " ];" ); } // and find all the errors d->errors = p.bad( this ); }
bool ManageSieveCommand::putScript() { if ( !d->t ) { d->name = string(); whitespace(); d->script = string(); end(); if ( d->script.isEmpty() ) { no( "Script cannot be empty" ); return true; } SieveScript script; script.parse( d->script.crlf() ); EString e = script.parseErrors(); if ( !e.isEmpty() ) { no( e ); return true; } if ( d->name.isEmpty() ) { log( "Syntax checking only" ); // Our very own syntax-checking hack. return true; } // look for fileinto calls. if any refer to nonexistent // mailboxes in the user's namespace, create those. if any // refer to mailboxes not owned by the user, deny the command. List<SieveCommand> stack; stack.append( script.topLevelCommands() ); while ( !stack.isEmpty() ) { SieveCommand * c = stack.shift(); if ( c->block() ) stack.append( c->block()->commands() ); if ( c->error().isEmpty() && c->identifier() == "fileinto" ) { SieveArgumentList * l = c->arguments(); List<SieveArgument>::Iterator a( l->arguments() ); while ( a ) { UString n = *a->stringList()->first(); Mailbox * home = d->sieve->user()->home(); Mailbox * m = 0; if ( n.startsWith( "/" ) ) m = Mailbox::obtain( n, true ); else m = Mailbox::obtain( home->name() + "/" + n, true ); Mailbox * p = m; while ( p && p != home ) p = p->parent(); if ( !m->deleted() ) { // no action needed } else if ( p == home ) { log( "Creating mailbox " + m->name().ascii() + " (used in fileinto and did not exist)" ); d->create.insert( m->name().utf8(), m ); } else { no( "Script refers to mailbox " + m->name().ascii().quoted() + ", which does not exist and is outside your" " home directory (" + home->name().ascii().quoted() + ")" ); return true; } ++a; } } } // at this point, nothing can prevent us from completing. d->t = new Transaction( this ); d->query = new Query( "select * from scripts " "where name=$1 and owner=$2 " "for update", this ); d->query->bind( 1, d->name ); d->query->bind( 2, d->sieve->user()->id() ); d->t->enqueue( d->query ); d->t->execute(); Dict<Mailbox>::Iterator i( d->create ); while ( i ) { (void)i->create( d->t, d->sieve->user() ); ++i; } if ( !d->create.isEmpty() ) Mailbox::refreshMailboxes( d->t ); } if ( !d->query->done() ) return false; if ( d->step == 0 ) { if ( d->query->nextRow() ) { d->query = new Query( "update scripts set script=$3 where " "owner=$1 and name=$2", 0 ); log( "Updating script: " + d->name ); } else { d->query = new Query( "insert into scripts " "(owner,name,script,active) " "values($1,$2,$3,false)", 0 ); log( "Storing new script: " + d->name ); } d->query->bind( 1, d->sieve->user()->id() ); d->query->bind( 2, d->name ); d->query->bind( 3, d->script ); d->t->enqueue( d->query ); d->step = 1; d->t->commit(); return false; } if ( !d->t->done() ) return false; Dict<Mailbox>::Iterator i( d->create ); while ( i ) { d->ok.append( "Created mailbox " + i->name().utf8().quoted() + "." ); ++i; if ( i ) d->ok.append( "\r\n" ); } return true; }