avoid pgsql warning
[aox:aox.git] / imap / handlers / select.cpp
1 // Copyright Oryx Mail Systems GmbH. All enquiries to info@oryx.com, please.
2
3 #include "select.h"
4
5 #include "imap.h"
6 #include "flag.h"
7 #include "user.h"
8 #include "timer.h"
9 #include "query.h"
10 #include "message.h"
11 #include "mailbox.h"
12 #include "messageset.h"
13 #include "imapsession.h"
14 #include "permissions.h"
15
16
17 class SelectData
18     : public Garbage
19 {
20 public:
21     SelectData()
22         : readOnly( false ), annotate( false ), condstore( false ),
23           firstUnseen( 0 ), allFlags( 0 ),
24           mailbox( 0 ), session( 0 ), permissions( 0 )
25     {}
26
27     bool readOnly;
28     bool annotate;
29     bool condstore;
30     Query * firstUnseen;
31     Query * allFlags;
32     Mailbox * mailbox;
33     ImapSession * session;
34     Permissions * permissions;
35 };
36
37
38 /*! \class Select select.h
39     Opens a mailbox for read-write access (RFC 3501 section 6.3.1)
40
41     This class implements both Select and Examine. The constructor has
42     to tell execute() what to do by setting the readOnly flag.
43 */
44
45 /*! Creates a Select object to handle SELECT if \a ro if false, and to
46     handle EXAMINE if \a ro is true.
47 */
48
49 Select::Select( bool ro )
50     : d( new SelectData )
51 {
52     d->readOnly = ro;
53 }
54
55
56 void Select::parse()
57 {
58     space();
59     d->mailbox = mailbox();
60     if ( present( " (" ) ) {
61         bool more = true;
62         while ( ok() && more ) {
63             // select-param can be a list or an astring. in our case,
64             // only astring is legal, since we advertise no extension
65             // that permits the list.
66             String param = astring().lower();
67             if ( param == "annotate" )
68                 d->annotate = true;
69             else if ( param == "condstore" )
70                 d->condstore = true;
71             else
72                 error( Bad, "Unknown select-param: " + param );
73             more = present( " " );
74         }
75         require( ")" );
76     }
77     end();
78 }
79
80
81 void Select::execute()
82 {
83     if ( state() != Executing )
84         return;
85
86     if ( Flag::id( "\\Deleted" ) == 0 ) {
87         // should only happen when we flush the entire database during
88         // testing, so we don't bother being accurate or fast, but
89         // simply try again in a second.
90         (void)new Timer( this, 1 );
91         return;
92     }
93
94     if ( !d->permissions ) {
95         if ( d->condstore )
96             imap()->setClientSupports( IMAP::Condstore );
97         if ( d->annotate )
98             imap()->setClientSupports( IMAP::Annotate );
99         if ( d->mailbox->synthetic() )
100             error( No,
101                    d->mailbox->name().ascii() + " is not in the database" );
102         else if ( d->mailbox->deleted() )
103             error( No, d->mailbox->name().ascii() + " is deleted" );
104
105         if ( !ok() ) {
106             finish();
107             return;
108         }
109
110         d->permissions = new Permissions( d->mailbox, imap()->user(),
111                                           this );
112     }
113
114     if ( d->permissions && !d->session ) {
115         if ( !d->permissions->ready() )
116             return;
117         if ( !d->permissions->allowed( Permissions::Read ) ) {
118             error( No, d->mailbox->name().ascii() + " is not accessible" );
119             finish();
120             return;
121         }
122         if ( !d->readOnly &&
123              !d->permissions->allowed( Permissions::KeepSeen ) )
124             d->readOnly = true;
125     }
126
127     if ( !d->session ) {
128         if ( imap()->session() )
129             imap()->endSession();
130         d->session = new ImapSession( imap(), d->mailbox, d->readOnly );
131         d->session->setPermissions( d->permissions );
132         imap()->beginSession( d->session );
133     }
134
135     if ( !d->allFlags ) {
136         d->allFlags = new Query( "select name from flag_names "
137                                  "order by lower(name)", this );
138         d->allFlags->execute();
139     }
140
141     if ( !d->session->initialised() )
142         return;
143
144     if ( !d->firstUnseen && !d->session->isEmpty() ) {
145         d->firstUnseen
146             = new Query( "select uid from mailbox_messages mm "
147                          "where mailbox=$1 and uid not in "
148                          "(select uid from flags f"
149                          " join flag_names fn on (f.flag=fn.id)"
150                          " where f.mailbox=$1 and fn.name=$2) "
151                          "order by uid limit 1", this );
152         d->firstUnseen->bind( 1, d->mailbox->id() );
153         d->firstUnseen->bind( 2, "\\seen" );
154         d->firstUnseen->execute();
155     }
156
157     if ( d->firstUnseen && !d->firstUnseen->done() )
158         return;
159
160     if ( d->allFlags && !d->allFlags->done() )
161         return;
162
163     d->session->emitUpdates( 0 );
164     // emitUpdates often calls Imap::runCommands, which calls this
165     // function, which will then change its state to Finished. so
166     // check that and don't repeat the last few responses.
167     if ( state() != Executing )
168         return;
169
170     respond( "OK [UIDVALIDITY " + fn( d->session->uidvalidity() ) + "]"
171              " uid validity" );
172
173     if ( d->firstUnseen ) {
174         Row * r = d->firstUnseen->nextRow();
175         uint unseen = 0;
176         if ( r )
177             unseen = r->getInt( "uid" );
178         if ( unseen )
179             respond( "OK [UNSEEN " + fn( d->session->msn( unseen ) ) +
180                      "] first unseen" );
181     }
182
183     if ( imap()->clientSupports( IMAP::Condstore ) &&
184          !d->session->isEmpty() ) {
185         uint nms = d->session->nextModSeq();
186         if ( nms < 2 )
187             nms = 2;
188         respond( "OK [HIGHESTMODSEQ " + fn( nms-1 ) + "] highest modseq" );
189     }
190
191     if ( d->allFlags ) {
192         StringList l;
193         while ( d->allFlags->hasResults() )
194             l.append( d->allFlags->nextRow()->getString( "name" ) );
195         String f = l.join( " " );
196         respond( "FLAGS (" + f + ")" );
197         respond( "OK [PERMANENTFLAGS (" + f + " \\*)] permanent flags" );
198     }
199
200     if ( imap()->clientSupports( IMAP::Annotate ) ) {
201         Permissions * p  = d->session->permissions();
202         if ( p && p->allowed( Permissions::WriteSharedAnnotation ) )
203             respond( "OK [ANNOTATIONS 262144] Arbitrary limit" );
204         else
205             respond( "OK [ANNOTATIONS READ-ONLY] Missing 'n' right" );
206     }
207
208     if ( d->session->readOnly() )
209         setRespTextCode( "READ-ONLY" );
210     else
211         setRespTextCode( "READ-WRITE" );
212
213     finish();
214 }
215
216
217 /*! \class Examine select.h
218     Opens a mailbox for read-only access (RFC 3501 section 6.3.1)
219
220     This class merely inherits from Select and sets the readOnly flag.
221     It has no code of its own.
222 */
223
224 /*! Constructs an Examine handler, which is the same as a Select
225     handler, except that it always is read-only.
226 */
227
228 Examine::Examine()
229     : Select( true )
230 {
231 }