Handle unicode append properly
[aox:aox.git] / imap / handlers / append.cpp
1 // Copyright 2009 The Archiveopteryx Developers <info@aox.org>
2
3 #include "append.h"
4
5 #include "user.h"
6 #include "date.h"
7 #include "query.h"
8 #include "fetcher.h"
9 #include "mailbox.h"
10 #include "injector.h"
11 #include "annotation.h"
12 #include "imapsession.h"
13 #include "imapurlfetcher.h"
14 #include "imapparser.h"
15 #include "recipient.h"
16 #include "imapurl.h"
17 #include "section.h"
18 #include "message.h"
19 #include "estring.h"
20 #include "fetch.h"
21 #include "imap.h"
22 #include "list.h"
23 #include "flag.h"
24
25
26 struct Textpart
27     : public Garbage
28 {
29     Textpart()
30         : type( Text ), url( 0 )
31     {}
32
33     enum Type { Text, Url };
34
35     Type type;
36     EString s;
37     ImapUrl * url;
38 };
39
40
41 struct Appendage
42     : public Garbage
43 {
44     Appendage()
45         : Garbage(),
46           message( 0 ),
47           textparts( 0 ), urlFetcher( 0 ),
48           annotations( 0 )
49     {}
50     Injectee * message;
51     List<Textpart> * textparts;
52     ImapUrlFetcher * urlFetcher;
53     EString text;
54     EStringList flags;
55     List<Annotation> * annotations;
56     Date date;
57 };
58
59
60 class AppendData
61     : public Garbage
62 {
63 public:
64     AppendData()
65         : mailbox( 0 ), injector( 0 )
66     {}
67
68     Mailbox * mailbox;
69     List<Appendage> messages;
70     Injector * injector;
71 };
72
73
74 /*! \class Append append.h
75     Adds a message to a mailbox (RFC 3501 section 6.3.11)
76
77     Parsing mostly relies on the Message class, execution on the
78     Injector. There is no way to insert anything but conformant
79     messages, unlike some other IMAP servers. How could we do that?
80     Not at all, I think.
81
82     RFC 3501 gives a basic syntax for Append. RFC 3502 modifies
83     it. RFC 4466 modifies it too. RFC 5257 extends the modification
84     given by RFC 4466.
85
86     We now use the syntax given by RFC 4466.
87 */
88
89 Append::Append()
90     : Command(), d( new AppendData )
91 {
92     // nothing more needed
93 }
94
95
96 void Append::parse()
97 {
98     // the grammar used is:
99
100     // append          = "APPEND" SP mailbox 1*append-message
101     // append-message  = append-opts SP append-data
102     // append-ext      = append-ext-name SP append-ext-value
103     // append-ext-name = tagged-ext-label
104     // append-ext-value= tagged-ext-val
105     // append-data     = literal / literal8 / append-data-ext /
106     //                   "CATENATE" SP "(" cat-part *(SP cat-part) ")" /
107     //                   "UTF8" SP "(" literal8 ")"
108     // append-data-ext = tagged-ext / att-annotate
109     // append-opts     = [SP flag-list] [SP date-time] *(SP append-ext)
110
111     // att-annotate    = "ANNOTATION" SP
112     //                   "(" entry-att *(SP entry-att) ")"
113
114     space();
115     d->mailbox = mailbox();
116
117     while ( ok() && !parser()->atEnd() ) {
118         space();
119
120         Appendage * h = new Appendage;
121         d->messages.append( h );
122
123         if ( present( "(" ) ) {
124             if ( nextChar() != ')' ) {
125                 h->flags.append( flag() );
126                 while( nextChar() == ' ' ) {
127                     space();
128                     h->flags.append( flag() );
129                 }
130             }
131             require( ")" );
132             space();
133         }
134
135         if ( present( "\"" ) ) {
136             uint day;
137             if ( nextChar() == ' ' ) {
138                 space();
139                 day = number( 1 );
140             }
141             else {
142                 day = number( 2 );
143             }
144             require( "-" );
145             EString month = letters( 3, 3 );
146             require( "-" );
147             uint year = number( 4 );
148             space();
149             uint hour = number( 2 );
150             require( ":" );
151             uint minute = number( 2 );
152             require( ":" );
153             uint second = number( 2 );
154             space();
155             int zone = 1;
156             if ( nextChar() == '-' )
157                 zone = -1;
158             else if ( nextChar() != '+' )
159                 error( Bad, "Time zone must start with + or -" );
160             step();
161             zone = zone * ( ( 60 * number( 2 ) ) + number( 2 ) );
162             require( "\"" );
163             space();
164             h->date.setDate( year, month, day, hour, minute, second, zone );
165             if ( !h->date.valid() )
166                 error( Bad, "Date supplied is not valid" );
167         }
168
169         if ( present( "ANNOTATION " ) ) {
170             h->annotations = new List<Annotation>;
171             require( "(" );
172
173             bool entriesDone = false;
174
175             do {
176                 EString entry( astring() );
177                 if ( entry.startsWith( "/flags/" ) || entry.contains( "//" ) ||
178                      entry.contains( "*" ) || entry.contains( "%" ) ||
179                      entry.endsWith( "/" ) )
180                 {
181                     error( Bad, "Invalid annotation entry name: " + entry );
182                     return;
183                 }
184
185                 space();
186                 require( "(" );
187                 bool attribsDone = false;
188                 do {
189                     int oid;
190
191                     EString attrib( astring() );
192                     if ( attrib.lower() == "value.priv" ) {
193                         oid = imap()->user()->id();
194                     }
195                     else if ( attrib.lower() == "value.shared" ) {
196                         oid = 0;
197                     }
198                     else {
199                         error( Bad,
200                                "Invalid annotation attribute: " + attrib );
201                         return;
202                     }
203
204                     space();
205
206                     if ( present( "nil" ) ) {
207                         // We don't need to store this at all.
208                     }
209                     else {
210                         Annotation * a = new Annotation;
211                         a->setEntryName( entry );
212                         a->setOwnerId( oid );
213                         a->setValue( string() );
214                         h->annotations->append( a );
215                     }
216
217                     if ( nextChar() == ' ' )
218                         space();
219                     else
220                         attribsDone = true;
221                 }
222                 while ( !attribsDone );
223                 require( ")" );
224                 if ( nextChar() == ' ' )
225                     space();
226                 else
227                     entriesDone = true;
228             }
229             while ( !entriesDone );
230
231             require( ")" );
232             space();
233         }
234
235         if ( present( "CATENATE " ) ) {
236             h->textparts = new List<Textpart>;
237             require( "(" );
238
239             bool done = false;
240
241             do {
242                 Textpart * tp = new Textpart;
243                 if ( present( "URL " ) ) {
244                     tp->type = Textpart::Url;
245                     tp->s = astring();
246                 }
247                 else if ( present( "TEXT " ) ) {
248                     tp->type = Textpart::Text;
249                     tp->s = literal();
250                 }
251                 else {
252                     error( Bad, "Expected cat-part, got: " + following() );
253                 }
254                 h->textparts->append( tp );
255
256                 if ( nextChar() == ' ' )
257                     space();
258                 else
259                     done = true;
260             }
261             while ( !done );
262
263             require( ")" );
264         }
265         else if ( present( "UTF8 " ) ) {
266             h->text = literal();
267         }
268         else {
269             h->text = literal();
270         }
271     }
272
273     end();
274
275     if ( !ok() )
276         return;
277
278     requireRight( d->mailbox, Permissions::Insert );
279     requireRight( d->mailbox, Permissions::Write );
280 }
281
282
283 /*! This new version of number() demands \a n digits and returns the
284     number.
285 */
286
287 uint Append::number( uint n )
288 {
289     EString tmp = digits( n, n );
290     return tmp.number( 0 );
291 }
292
293
294 void Append::execute()
295 {
296     if ( !permitted() || !ok() || state() != Executing )
297         return;
298
299     List<Appendage>::Iterator h( d->messages );
300     bool allDone = true;
301     while ( h && ok() ) {
302         if ( !h->message )
303             process( h );
304         if ( !h->message )
305             allDone = false;
306         ++h;
307     }
308
309     if ( !allDone )
310         return;
311
312     if ( !d->injector ) {
313         List<Injectee> * m = new List<Injectee>;
314         h = d->messages.first();
315         while ( h ) {
316             m->append( h->message );
317             ++h;
318         }
319
320         d->injector = new Injector( this );
321         d->injector->addInjection( m );
322         d->injector->execute();
323     }
324
325     if ( !d->injector->done() )
326         return;
327
328     if ( d->injector->failed() ) {
329         error( No, "Could not append to " + d->mailbox->name().ascii() );
330         return;
331     }
332
333     IntegerSet uids;
334     h = d->messages.first();
335     while ( h ) {
336         uids.add( h->message->uid( d->mailbox ) );
337         ++h;
338     }
339     setRespTextCode( "APPENDUID"
340                      " " + fn( d->mailbox->uidvalidity() ) +
341                      " " + uids.set() );
342     finish();
343 }
344
345
346 /*! This private execute() helper processes the single message \a h. It
347     can be executed in parallel.
348 */
349
350 void Append::process( class Appendage * h )
351 {
352     if ( h->message )
353         return;
354
355     if ( !h->urlFetcher ) {
356         List<ImapUrl> * urls = new List<ImapUrl>;
357         List<Textpart>::Iterator it( h->textparts );
358         while ( it ) {
359             Textpart * tp = it;
360             if ( tp->type == Textpart::Url ) {
361                 // We require that this be a URL relative to the current
362                 // IMAP session; that's all CATENATE allows for.
363                 tp->url = new ImapUrl( imap(), tp->s );
364                 if ( !tp->url->valid() ) {
365                     setRespTextCode( "BADURL " + tp->s );
366                     error( No, "invalid URL" );
367                     return;
368                 }
369                 urls->append( tp->url );
370             }
371             ++it;
372         }
373
374         h->urlFetcher = new ImapUrlFetcher( urls, this, true );
375         h->urlFetcher->execute();
376     }
377
378     if ( !h->urlFetcher->done() || h->message )
379         return;
380
381     if ( h->urlFetcher->failed() ) {
382         setRespTextCode( "BADURL " + h->urlFetcher->badUrl() );
383         error( No, h->urlFetcher->error() );
384         return;
385     }
386
387     List<Textpart>::Iterator it( h->textparts );
388     while ( it ) {
389         Textpart * tp = it;
390         if ( tp->type == Textpart::Text )
391             h->text.append( tp->s );
392         else
393             h->text.append( tp->url->text() );
394         h->textparts->take( it );
395     }
396
397     h->message = new Injectee;
398     h->message->setInternalDate( h->date.unixTime() );
399     h->message->parse( h->text );
400     h->message->setFlags( d->mailbox, &h->flags );
401     h->message->setAnnotations( d->mailbox, h->annotations );
402     if ( !h->message->valid() ) {
403         setRespTextCode( "PARSE" );
404         error( Bad, h->message->error() );
405         return;
406     }
407 }