implementation of mbox folder with unique messages numbers
[claws.git] / src / mailmbox_parse.c
1 /*
2  * libEtPan! -- a mail stuff library
3  *
4  * Copyright (C) 2001, 2002 - DINH Viet Hoa
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the libEtPan! project nor the names of its
16  *    contributors may be used to endorse or promote products derived
17  *    from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31
32 /*
33  * $Id$
34  */
35
36 #include "mailmbox_parse.h"
37
38 #include "mailmbox.h"
39
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <string.h>
43 #include <stdlib.h>
44
45 #define UID_HEADER "X-LibEtPan-UID:"
46
47 #ifndef TRUE
48 #define TRUE 1
49 #endif
50
51 #ifndef FALSE
52 #define FALSE 0
53 #endif
54
55 enum {
56   UNSTRUCTURED_START,
57   UNSTRUCTURED_CR,
58   UNSTRUCTURED_LF,
59   UNSTRUCTURED_WSP,
60   UNSTRUCTURED_OUT
61 };
62
63 /* begin extracted from imf/mailimf.c */
64
65 static int mailimf_char_parse(char * message, size_t length,
66                        size_t * index, char token)
67 {
68   size_t cur_token;
69
70   cur_token = * index;
71
72   if (cur_token >= length)
73     return MAILIMF_ERROR_PARSE;
74
75   if (message[cur_token] == token) {
76     cur_token ++;
77     * index = cur_token;
78     return MAILIMF_NO_ERROR;
79   }
80   else
81     return MAILIMF_ERROR_PARSE;
82 }
83
84 int mailimf_crlf_parse(char * message, size_t length, size_t * index)
85 {
86   size_t cur_token;
87   int r;
88
89   cur_token = * index;
90
91   r = mailimf_char_parse(message, length, &cur_token, '\r');
92   if ((r != MAILIMF_NO_ERROR) && (r != MAILIMF_ERROR_PARSE))
93     return r;
94
95   r = mailimf_char_parse(message, length, &cur_token, '\n');
96   if (r != MAILIMF_NO_ERROR)
97     return r;
98
99   * index = cur_token;
100   return MAILIMF_NO_ERROR;
101 }
102
103
104 int mailimf_ignore_field_parse(char * message, size_t length,
105                                size_t * index)
106 {
107   int has_field;
108   size_t cur_token;
109   int state;
110   size_t terminal;
111
112   has_field = FALSE;
113   cur_token = * index;
114
115   terminal = cur_token;
116   state = UNSTRUCTURED_START;
117
118   /* check if this is not a beginning CRLF */
119
120   if (cur_token >= length)
121     return MAILIMF_ERROR_PARSE;
122
123   switch (message[cur_token]) {
124   case '\r':
125     return MAILIMF_ERROR_PARSE;
126   case '\n':
127     return MAILIMF_ERROR_PARSE;
128   }
129
130   while (state != UNSTRUCTURED_OUT) {
131
132     switch(state) {
133     case UNSTRUCTURED_START:
134       if (cur_token >= length)
135         return MAILIMF_ERROR_PARSE;
136
137       switch(message[cur_token]) {
138       case '\r':
139         state = UNSTRUCTURED_CR;
140         break;
141       case '\n':
142         state = UNSTRUCTURED_LF;
143         break;
144       case ':':
145         has_field = TRUE;
146         state = UNSTRUCTURED_START;
147         break;
148       default:
149         state = UNSTRUCTURED_START;
150         break;
151       }
152       break;
153     case UNSTRUCTURED_CR:
154       if (cur_token >= length)
155         return MAILIMF_ERROR_PARSE;
156
157       switch(message[cur_token]) {
158       case '\n':
159         state = UNSTRUCTURED_LF;
160         break;
161       case ':':
162         has_field = TRUE;
163         state = UNSTRUCTURED_START;
164         break;
165       default:
166         state = UNSTRUCTURED_START;
167         break;
168       }
169       break;
170     case UNSTRUCTURED_LF:
171       if (cur_token >= length) {
172         terminal = cur_token;
173         state = UNSTRUCTURED_OUT;
174         break;
175       }
176
177       switch(message[cur_token]) {
178       case '\t':
179       case ' ':
180         state = UNSTRUCTURED_WSP;
181         break;
182       default:
183         terminal = cur_token;
184         state = UNSTRUCTURED_OUT;
185         break;
186       }
187       break;
188     case UNSTRUCTURED_WSP:
189       if (cur_token >= length)
190         return MAILIMF_ERROR_PARSE;
191
192       switch(message[cur_token]) {
193       case '\r':
194         state = UNSTRUCTURED_CR;
195         break;
196       case '\n':
197         state = UNSTRUCTURED_LF;
198         break;
199       case ':':
200         has_field = TRUE;
201         state = UNSTRUCTURED_START;
202         break;
203       default:
204         state = UNSTRUCTURED_START;
205         break;
206       }
207       break;
208     }
209
210     cur_token ++;
211   }
212
213   if (!has_field)
214     return MAILIMF_ERROR_PARSE;
215
216   * index = terminal;
217
218   return MAILIMF_NO_ERROR;
219 }
220
221 /* end - extracted from imf/mailimf.c */
222
223 static inline int
224 mailmbox_fields_parse(char * str, size_t length,
225                       size_t * index,
226                       uint32_t * puid,
227                       size_t * phlen)
228 {
229   size_t cur_token;
230   int r;
231   size_t hlen;
232   size_t uid;
233   int end;
234
235   cur_token = * index;
236
237   end = FALSE;
238   uid = 0;
239   while (!end) {
240     size_t begin;
241
242     begin = cur_token;
243
244     r = mailimf_ignore_field_parse(str, length, &cur_token);
245     switch (r) {
246     case MAILIMF_NO_ERROR:
247       if (str[begin] == 'X') {
248
249         if (strncasecmp(str + begin, UID_HEADER, strlen(UID_HEADER)) == 0) {
250           begin += strlen(UID_HEADER);
251
252           while (str[begin] == ' ')
253             begin ++;
254           
255           uid = strtoul(str + begin, NULL, 10);
256         }
257       }
258       
259       break;
260     case MAILIMF_ERROR_PARSE:
261     default:
262       end = TRUE;
263       break;
264     }
265   }
266
267   hlen = cur_token - * index;
268
269   * phlen = hlen;
270   * puid = uid;
271   * index = cur_token;
272
273   return MAILMBOX_NO_ERROR;
274 }
275
276 enum {
277   IN_MAIL,
278   FIRST_CR,
279   FIRST_LF,
280   SECOND_CR,
281   SECOND_LF,
282   PARSING_F,
283   PARSING_R,
284   PARSING_O,
285   PARSING_M,
286   OUT_MAIL
287 };
288
289
290
291
292 static inline int
293 mailmbox_single_parse(char * str, size_t length,
294                       size_t * index,
295                       size_t * pstart,
296                       size_t * pstart_len,
297                       size_t * pheaders,
298                       size_t * pheaders_len,
299                       size_t * pbody,
300                       size_t * pbody_len,
301                       size_t * psize,
302                       size_t * ppadding,
303                       uint32_t * puid)
304 {
305   size_t cur_token;
306   size_t start;
307   size_t start_len;
308   size_t headers;
309   size_t headers_len;
310   size_t body;
311   size_t end;
312   size_t next;
313   size_t message_length;
314   uint32_t uid;
315   int r;
316 #if 0
317   int in_mail_data;
318 #endif
319 #if 0
320   size_t begin;
321 #endif
322
323   int state;
324
325   cur_token = * index;
326
327   if (cur_token >= length)
328     return MAILMBOX_ERROR_PARSE;
329
330   start = cur_token;
331   start_len = 0;
332   headers = cur_token;
333
334   if (cur_token + 5 < length) {
335     if (strncmp(str + cur_token, "From ", 5) == 0) {
336       cur_token += 5;
337       while (str[cur_token] != '\n') {
338         cur_token ++;
339         if (cur_token >= length)
340           break;
341       }
342       if (cur_token < length) {
343         cur_token ++;
344         headers = cur_token;
345         start_len = headers - start;
346       }
347     }
348   }
349
350   next = length;
351
352   r = mailmbox_fields_parse(str, length, &cur_token,
353                             &uid, &headers_len);
354   if (r != MAILMBOX_NO_ERROR)
355     return r;
356
357   /* save position */
358 #if 0
359   begin = cur_token;
360 #endif
361   
362   mailimf_crlf_parse(str, length, &cur_token);
363
364 #if 0
365   if (str[cur_token] == 'F') {
366     printf("start !\n");
367     printf("%50.50s\n", str + cur_token);
368     getchar();
369   }
370 #endif
371   
372   body = cur_token;
373
374   /* restore position */
375   /*  cur_token = begin; */
376
377   state = FIRST_LF;
378
379   end = length;
380
381 #if 0
382   in_mail_data = 0;
383 #endif
384   while (state != OUT_MAIL) {
385
386     if (cur_token >= length) {
387       if (state == IN_MAIL)
388         end = length;
389       next = length;
390       break;
391     }
392
393     switch(state) {
394     case IN_MAIL:
395       switch(str[cur_token]) {
396       case '\r':
397         state = FIRST_CR;
398         break;
399       case '\n':
400         state = FIRST_LF;
401         break;
402       case 'F':
403         if (cur_token == body) {
404           end = cur_token;
405           next = cur_token;
406           state = PARSING_F;
407         }
408         break;
409 #if 0
410       default:
411         in_mail_data = 1;
412         break;
413 #endif
414       }
415       break;
416       
417     case FIRST_CR:
418       end = cur_token;
419       switch(str[cur_token]) {
420       case '\r':
421         state = SECOND_CR;
422         break;
423       case '\n':
424         state = FIRST_LF;
425         break;
426       default:
427         state = IN_MAIL;
428 #if 0
429         in_mail_data = 1;
430 #endif
431         break;
432       }
433       break;
434       
435     case FIRST_LF:
436       end = cur_token;
437       switch(str[cur_token]) {
438       case '\r':
439         state = SECOND_CR;
440         break;
441       case '\n':
442         state = SECOND_LF;
443         break;
444       default:
445         state = IN_MAIL;
446 #if 0
447         in_mail_data = 1;
448 #endif
449         break;
450       }
451       break;
452       
453     case SECOND_CR:
454       switch(str[cur_token]) {
455         case '\r':
456           end = cur_token;
457           break;
458         case '\n':
459           state = SECOND_LF;
460           break;
461         case 'F':
462           next = cur_token;
463           state = PARSING_F;
464           break;
465         default:
466           state = IN_MAIL;
467 #if 0
468           in_mail_data = 1;
469 #endif
470           break;
471       }
472       break;
473
474     case SECOND_LF:
475       switch(str[cur_token]) {
476         case '\r':
477           state = SECOND_CR;
478           break;
479         case '\n':
480           end = cur_token;
481           break;
482         case 'F':
483           next = cur_token;
484           state = PARSING_F;
485           break;
486         default:
487           state = IN_MAIL;
488 #if 0
489           in_mail_data = 1;
490 #endif
491           break;
492       }
493       break;
494       
495     case PARSING_F:
496       switch(str[cur_token]) {
497         case 'r':
498           state = PARSING_R;
499           break;
500         default:
501           state = IN_MAIL;
502 #if 0
503           in_mail_data = 1;
504 #endif
505           break;
506       }
507       break;
508       
509     case PARSING_R:
510       switch(str[cur_token]) {
511         case 'o':
512           state = PARSING_O;
513           break;
514         default:
515           state = IN_MAIL;
516 #if 0
517           in_mail_data = 1;
518 #endif
519           break;
520       }
521       break;
522       
523     case PARSING_O:
524       switch(str[cur_token]) {
525         case 'm':
526           state = PARSING_M;
527           break;
528         default:
529           state = IN_MAIL;
530 #if 0
531           in_mail_data = 1;
532 #endif
533           break;
534       }
535       break;
536
537     case PARSING_M:
538       switch(str[cur_token]) {
539         case ' ':
540           state = OUT_MAIL;
541           break;
542       default:
543           state = IN_MAIL;
544           break;
545       }
546       break;
547     }
548     
549     cur_token ++;
550   }
551
552   message_length = end - start;
553
554   * pstart = start;
555   * pstart_len = start_len;
556   * pheaders = headers;
557   * pheaders_len = headers_len;
558   * pbody = body;
559   * pbody_len = end - body;
560   * psize = message_length;
561   * ppadding = next - end;
562   * puid = uid;
563
564   * index = next;
565
566   return MAILMBOX_NO_ERROR;
567 }
568
569
570 int
571 mailmbox_parse_additionnal(struct mailmbox_folder * folder,
572                            size_t * index)
573 {
574   size_t cur_token;
575
576   size_t start;
577   size_t start_len;
578   size_t headers;
579   size_t headers_len;
580   size_t body;
581   size_t body_len;
582   size_t size;
583   size_t padding;
584   uint32_t uid;
585   int r;
586   int res;
587
588   uint32_t max_uid;
589   uint32_t first_index;
590   uint32_t i;
591   uint32_t j;
592
593   cur_token = * index;
594
595   /* remove temporary UID that we will parse */
596
597   first_index = folder->tab->len;
598
599   for(i = 0 ; i < folder->tab->len ; i++) {
600     struct mailmbox_msg_info * info;
601     
602     info = carray_get(folder->tab, i);
603
604     if (info->start < cur_token) {
605       continue;
606     }
607
608     if (!info->written_uid) {
609       chashdatum key;
610       
611       key.data = (char *) &info->uid;
612       key.len = sizeof(info->uid);
613       
614       chash_delete(folder->hash, &key, NULL);
615       carray_delete_fast(folder->tab, i);
616       mailmbox_msg_info_free(info);
617       if (i < first_index)
618         first_index = i;
619     }
620   }
621
622   /* make a sequence in the table */
623
624   max_uid = folder->written_uid;
625
626   i = 0;
627   j = 0;
628   while (i < folder->tab->len) {
629     struct mailmbox_msg_info * info;
630     
631     info = carray_get(folder->tab, i);
632     if (info != NULL) {
633       carray_set(folder->tab, j, info);
634
635       if (info->uid > max_uid)
636         max_uid = info->uid;
637
638       info->index = j;
639       j ++;
640     }
641     i ++;
642   }
643   carray_set_size(folder->tab, j);
644
645   /* parse content */
646
647   first_index = j;
648
649   while (1) {
650     struct mailmbox_msg_info * info;
651     chashdatum key;
652     chashdatum data;
653     
654     r = mailmbox_single_parse(folder->mapping, folder->mapping_size,
655                               &cur_token,
656                               &start, &start_len,
657                               &headers, &headers_len,
658                               &body, &body_len,
659                               &size, &padding, &uid);
660     if (r == MAILMBOX_NO_ERROR) {
661       /* do nothing */
662     }
663     else if (r == MAILMBOX_ERROR_PARSE)
664       break;
665     else {
666       res = r;
667       goto err;
668     }
669     
670     key.data = (char *) &uid;
671     key.len = sizeof(uid);
672     
673     r = chash_get(folder->hash, &key, &data);
674     if (r == 0) {
675       info = (struct mailmbox_msg_info *) data.data;
676       
677       if (!info->written_uid) {
678         /* some new mail has been written and override an
679            existing temporary UID */
680         
681         chash_delete(folder->hash, &key, NULL);
682         info->uid = 0;
683
684         if (info->index < first_index)
685           first_index = info->index;
686       }
687       else
688         uid = 0;
689     }
690
691     if (uid > max_uid)
692       max_uid = uid;
693
694     r = mailmbox_msg_info_update(folder,
695                                  start, start_len, headers, headers_len,
696                                  body, body_len, size, padding, uid);
697     if (r != MAILMBOX_NO_ERROR) {
698       res = r;
699       goto err;
700     }
701   }
702
703   * index = cur_token;
704
705   folder->written_uid = max_uid;
706
707   /* attribute uid */
708
709   for(i = first_index ; i < folder->tab->len ; i ++) {
710     struct mailmbox_msg_info * info;
711     chashdatum key;
712     chashdatum data;
713
714     info = carray_get(folder->tab, i);
715
716     if (info->uid != 0) {
717       continue;
718     }
719
720     max_uid ++;
721     info->uid = max_uid;
722     
723     key.data = (char *) &info->uid;
724     key.len = sizeof(info->uid);
725     data.data = (char *) info;
726     data.len = 0;
727     
728     r = chash_set(folder->hash, &key, &data, NULL);
729     if (r < 0) {
730       res = MAILMBOX_ERROR_MEMORY;
731       goto err;
732     }
733   }
734
735   folder->max_uid = max_uid;
736
737   return MAILMBOX_NO_ERROR;
738
739  err:
740   return res;
741 }
742
743 static void flush_uid(struct mailmbox_folder * folder)
744 {
745   uint32_t i;
746   
747   for(i = 0 ; i < folder->tab->len ; i++) {
748     struct mailmbox_msg_info * info;
749     
750     info = carray_get(folder->tab, i);
751     if (info != NULL)
752       mailmbox_msg_info_free(info);
753   }
754   
755   chash_clear(folder->hash);
756   carray_set_size(folder->tab, 0);
757 }
758
759 int mailmbox_parse(struct mailmbox_folder * folder)
760 {
761   int r;
762   int res;
763   size_t cur_token;
764
765   flush_uid(folder);
766   
767   cur_token = 0;
768
769   r = mailmbox_parse_additionnal(folder, &cur_token);
770
771   if (r != MAILMBOX_NO_ERROR) {
772     res = r;
773     goto err;
774   }
775
776   return MAILMBOX_NO_ERROR;
777
778  err:
779   return res;
780 }