BDB suck hard. in a GNU env, we will at least have gdbm.
[apps/madmutt.git] / lib-mx / hcache.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 2004 Thomas Glanzmann <sithglan@stud.uni-erlangen.de>
4  * Copyright (C) 2004 Tobias Werth <sitowert@stud.uni-erlangen.de>
5  * Copyright (C) 2004 Brian Fundakowski Feldman <green@FreeBSD.org>
6  *
7  * This file is part of mutt-ng, see http://www.muttng.org/.
8  * It's licensed under the GNU General Public License,
9  * please see the file GPL in the top level source directory.
10  */
11
12 #include <lib-lib/lib-lib.h>
13
14 #ifdef USE_HCACHE
15
16 #define MUTTNG_HCACHE_ID        "0x004"
17
18 #if defined(HAVE_QDBM)
19 #include <depot.h>
20 #include <cabin.h>
21 #include <villa.h>
22 #elif defined(HAVE_GDBM)
23 #include <gdbm.h>
24 #endif
25
26 #include <imap/message.h>
27
28 #include "md5.h"
29 #include "charset.h"
30 #include "mutt.h"
31 #include "hcache.h"
32
33 struct header_cache {
34 #if defined(HAVE_QDBM)
35   VILLA *db;
36   char *folder;
37   unsigned int crc;
38 #elif defined(HAVE_GDBM)
39   GDBM_FILE db;
40   char *folder;
41   unsigned int crc;
42 #endif
43 };
44
45 typedef union {
46   struct timeval timeval;
47   unsigned long uid_validity;
48 } validate;
49
50 #define UPPER4K(i)  ((i & ~(4096 - 1)) + 4096)
51
52 static unsigned char *lazy_malloc(ssize_t siz)
53 {
54     return p_new(unsigned char, UPPER4K(siz));
55 }
56
57 static void lazy_realloc(unsigned char **p, ssize_t siz)
58 {
59     p_realloc(p, UPPER4K(siz));
60 }
61
62 static unsigned char *dump_int (unsigned int i, unsigned char *d, int *off)
63 {
64   lazy_realloc (&d, *off + sizeof (int));
65   memcpy (d + *off, &i, sizeof (int));
66   (*off) += sizeof (int);
67
68   return d;
69 }
70
71 static void restore_int (unsigned int *i, const unsigned char *d, int *off)
72 {
73   memcpy (i, d + *off, sizeof (int));
74   (*off) += sizeof (int);
75 }
76
77 static unsigned char *dump_char (char *c, unsigned char *d, int *off)
78 {
79   unsigned int size;
80
81   if (c == NULL) {
82     size = 0;
83     d = dump_int (size, d, off);
84     return d;
85   }
86
87   size = m_strlen(c) + 1;
88   d = dump_int (size, d, off);
89   lazy_realloc (&d, *off + size);
90   memcpy (d + *off, c, size);
91   *off += size;
92
93   return d;
94 }
95
96 static void restore_char (char **c, const unsigned char *d, int *off)
97 {
98   unsigned int size;
99
100   restore_int (&size, d, off);
101
102   if (size == 0) {
103     *c = NULL;
104     return;
105   }
106
107   *c = p_dup(d + *off, size);
108   *off += size;
109 }
110
111 static unsigned char *dump_address (address_t * a, unsigned char *d, int *off)
112 {
113   unsigned int counter = 0;
114   unsigned int start_off = *off;
115
116   d = dump_int (0xdeadbeef, d, off);
117
118   while (a) {
119     d = dump_char (a->personal, d, off);
120     d = dump_char (a->mailbox, d, off);
121     d = dump_int (a->group, d, off);
122     a = a->next;
123     counter++;
124   }
125
126   memcpy (d + start_off, &counter, sizeof (int));
127
128   return d;
129 }
130
131 static void restore_address (address_t ** a, const unsigned char *d, int *off)
132 {
133   unsigned int counter;
134
135   restore_int (&counter, d, off);
136
137   while (counter) {
138     *a = p_new(address_t, 1);
139     restore_char (&(*a)->personal, d, off);
140     restore_char (&(*a)->mailbox, d, off);
141     restore_int ((unsigned int *) &(*a)->group, d, off);
142     a = &(*a)->next;
143     counter--;
144   }
145
146   *a = NULL;
147 }
148
149 static unsigned char *dump_list (string_list_t * l, unsigned char *d, int *off)
150 {
151   unsigned int counter = 0;
152   unsigned int start_off = *off;
153
154   d = dump_int (0xdeadbeef, d, off);
155
156   while (l) {
157     d = dump_char (l->data, d, off);
158     l = l->next;
159     counter++;
160   }
161
162   memcpy (d + start_off, &counter, sizeof (int));
163
164   return d;
165 }
166
167 static void restore_list (string_list_t ** l, const unsigned char *d, int *off)
168 {
169   unsigned int counter;
170
171   restore_int (&counter, d, off);
172
173   while (counter) {
174     *l = p_new(string_list_t, 1);
175     restore_char (&(*l)->data, d, off);
176     l = &(*l)->next;
177     counter--;
178   }
179
180   *l = NULL;
181 }
182
183 static unsigned char *dump_parameter (parameter_t * p, unsigned char *d,
184                                       int *off)
185 {
186   unsigned int counter = 0;
187   unsigned int start_off = *off;
188
189   d = dump_int (0xdeadbeef, d, off);
190
191   while (p) {
192     d = dump_char (p->attribute, d, off);
193     d = dump_char (p->value, d, off);
194     p = p->next;
195     counter++;
196   }
197
198   memcpy (d + start_off, &counter, sizeof (int));
199
200   return d;
201 }
202
203 static void
204 restore_parameter (parameter_t ** p, const unsigned char *d, int *off)
205 {
206   unsigned int counter;
207
208   restore_int (&counter, d, off);
209
210   while (counter) {
211     *p = parameter_new();
212     restore_char (&(*p)->attribute, d, off);
213     restore_char (&(*p)->value, d, off);
214     p = &(*p)->next;
215     counter--;
216   }
217 }
218
219 static unsigned char *dump_body (BODY * c, unsigned char *d, int *off)
220 {
221   lazy_realloc (&d, *off + sizeof (BODY));
222   memcpy (d + *off, c, sizeof (BODY));
223   *off += sizeof (BODY);
224
225   d = dump_char (c->xtype, d, off);
226   d = dump_char (c->subtype, d, off);
227
228   d = dump_parameter (c->parameter, d, off);
229
230   d = dump_char (c->description, d, off);
231   d = dump_char (c->form_name, d, off);
232   d = dump_char (c->filename, d, off);
233   d = dump_char (c->d_filename, d, off);
234
235   return d;
236 }
237
238 static void restore_body (BODY * c, const unsigned char *d, int *off)
239 {
240   memcpy (c, d + *off, sizeof (BODY));
241   *off += sizeof (BODY);
242
243   restore_char (&c->xtype, d, off);
244   restore_char (&c->subtype, d, off);
245
246   restore_parameter (&c->parameter, d, off);
247
248   restore_char (&c->description, d, off);
249   restore_char (&c->form_name, d, off);
250   restore_char (&c->filename, d, off);
251   restore_char (&c->d_filename, d, off);
252 }
253
254 static unsigned char *dump_envelope (ENVELOPE * e, unsigned char *d, int *off)
255 {
256   d = dump_address (e->return_path, d, off);
257   d = dump_address (e->from, d, off);
258   d = dump_address (e->to, d, off);
259   d = dump_address (e->cc, d, off);
260   d = dump_address (e->bcc, d, off);
261   d = dump_address (e->sender, d, off);
262   d = dump_address (e->reply_to, d, off);
263   d = dump_address (e->mail_followup_to, d, off);
264
265   d = dump_char (e->subject, d, off);
266   if (e->real_subj) {
267     d = dump_int (e->real_subj - e->subject, d, off);
268   }
269   else {
270     d = dump_int (-1, d, off);
271   }
272   d = dump_char (e->message_id, d, off);
273   d = dump_char (e->supersedes, d, off);
274   d = dump_char (e->date, d, off);
275   d = dump_char (e->x_label, d, off);
276   d = dump_char (e->list_post, d, off);
277
278 #ifdef USE_NNTP
279   d = dump_char (e->newsgroups, d, off);
280   d = dump_char (e->xref, d, off);
281   d = dump_char (e->followup_to, d, off);
282   d = dump_char (e->x_comment_to, d, off);
283 #endif
284
285   d = dump_list (e->references, d, off);
286   d = dump_list (e->in_reply_to, d, off);
287   d = dump_list (e->userhdrs, d, off);
288
289   return d;
290 }
291
292 static void restore_envelope (ENVELOPE * e, const unsigned char *d, int *off)
293 {
294   int real_subj_off;
295
296   restore_address (&e->return_path, d, off);
297   restore_address (&e->from, d, off);
298   restore_address (&e->to, d, off);
299   restore_address (&e->cc, d, off);
300   restore_address (&e->bcc, d, off);
301   restore_address (&e->sender, d, off);
302   restore_address (&e->reply_to, d, off);
303   restore_address (&e->mail_followup_to, d, off);
304
305   restore_char (&e->subject, d, off);
306   restore_int ((unsigned int *) (&real_subj_off), d, off);
307   if (0 <= real_subj_off) {
308     e->real_subj = e->subject + real_subj_off;
309   }
310   else {
311     e->real_subj = NULL;
312   }
313   restore_char (&e->message_id, d, off);
314   restore_char (&e->supersedes, d, off);
315   restore_char (&e->date, d, off);
316   restore_char (&e->x_label, d, off);
317   restore_char (&e->list_post, d, off);
318
319 #ifdef USE_NNTP
320   restore_char (&e->newsgroups, d, off);
321   restore_char (&e->xref, d, off);
322   restore_char (&e->followup_to, d, off);
323   restore_char (&e->x_comment_to, d, off);
324 #endif
325
326   restore_list (&e->references, d, off);
327   restore_list (&e->in_reply_to, d, off);
328   restore_list (&e->userhdrs, d, off);
329 }
330
331 static
332 unsigned int crc32 (unsigned int crc, unsigned char const *p, ssize_t len)
333 {
334   int i;
335
336   while (len--) {
337     crc ^= *p++;
338     for (i = 0; i < 8; i++)
339       crc = (crc >> 1) ^ ((crc & 1) ? 0xedb88320 : 0);
340   }
341   return crc;
342 }
343
344 static int generate_crc32 ()
345 {
346   int crc = 0;
347
348   crc = crc32 (crc, (unsigned char const *)
349                MUTTNG_HCACHE_ID "sithglan@stud.uni-erlangen.de[sithglan]|hcache.c|20041108231548|29613",
350                m_strlen
351                (MUTTNG_HCACHE_ID "sithglan@stud.uni-erlangen.de[sithglan]|hcache.c|20041108231548|29613"));
352
353 #ifdef HAVE_LANGINFO_CODESET
354   crc = crc32(crc, (unsigned char const *) MCharset.charset, m_strlen(MCharset.charset));
355   crc = crc32(crc, (unsigned char const *) "HAVE_LANGINFO_CODESET",
356               m_strlen("HAVE_LANGINFO_CODESET"));
357 #endif
358
359   crc = crc32(crc, (unsigned char const *) "USE_POP", m_strlen("USE_POP"));
360
361   crc = crc32(crc, (unsigned char const *) "MIXMASTER",
362               m_strlen("MIXMASTER"));
363
364   crc = crc32(crc, (unsigned char const *) "USE_IMAP", m_strlen("USE_IMAP"));
365
366 #ifdef USE_NNTP
367   crc = crc32(crc, (unsigned char const *) "USE_NNTP", m_strlen("USE_NNTP"));
368 #endif
369   return crc;
370 }
371
372 static int crc32_matches (const char *d, unsigned int crc)
373 {
374   int off = sizeof (validate);
375   unsigned int mycrc = 0;
376
377   if (!d) {
378     return 0;
379   }
380
381   restore_int (&mycrc, (unsigned char *) d, &off);
382
383   return (crc == mycrc);
384 }
385
386 /* Append md5sumed folder to path if path is a directory. */
387 static const char *mutt_hcache_per_folder (const char *path,
388                                            const char *folder)
389 {
390   static char mutt_hcache_per_folder_path[_POSIX_PATH_MAX];
391   struct stat path_stat;
392   MD5_CTX md5;
393   unsigned char md5sum[16];
394   int ret;
395
396   ret = stat (path, &path_stat);
397   if (ret < 0) {
398     return path;
399   }
400
401   if (!S_ISDIR (path_stat.st_mode)) {
402     return path;
403   }
404
405   MD5Init (&md5);
406   MD5Update (&md5, (unsigned char *) folder, m_strlen(folder));
407   MD5Final (md5sum, &md5);
408
409   ret = snprintf (mutt_hcache_per_folder_path, _POSIX_PATH_MAX,
410                   "%s/%02x%02x%02x%02x%02x%02x%02x%02x"
411                   "%02x%02x%02x%02x%02x%02x%02x%02x",
412                   path, md5sum[0], md5sum[1], md5sum[2], md5sum[3],
413                   md5sum[4], md5sum[5], md5sum[6], md5sum[7], md5sum[8],
414                   md5sum[9], md5sum[10], md5sum[11], md5sum[12],
415                   md5sum[13], md5sum[14], md5sum[15]);
416
417   if (ret <= 0) {
418     return path;
419   }
420
421   return mutt_hcache_per_folder_path;
422 }
423
424 /* This function transforms a header into a char so that it is useable by
425  * db_store */
426 static void *mutt_hcache_dump (void *_db, HEADER * h, int *off,
427                                unsigned long uid_validity)
428 {
429   struct header_cache *db = _db;
430   unsigned char *d = NULL;
431
432   *off = 0;
433
434   d = lazy_malloc (sizeof (validate));
435
436   if (uid_validity) {
437     memcpy (d, &uid_validity, sizeof (unsigned long));
438   }
439   else {
440     struct timeval now;
441
442     gettimeofday (&now, NULL);
443     memcpy (d, &now, sizeof (struct timeval));
444   }
445   *off += sizeof (validate);
446
447   d = dump_int (db->crc, d, off);
448
449   lazy_realloc (&d, *off + sizeof (HEADER));
450   memcpy (d + *off, h, sizeof (HEADER));
451   *off += sizeof (HEADER);
452
453   d = dump_envelope (h->env, d, off);
454   d = dump_body (h->content, d, off);
455   d = dump_char (h->maildir_flags, d, off);
456
457   return d;
458 }
459
460 HEADER *mutt_hcache_restore (const unsigned char *d, HEADER ** oh)
461 {
462   int off = 0;
463   HEADER *h = header_new();
464
465   /* skip validate */
466   off += sizeof (validate);
467
468   /* skip crc */
469   off += sizeof (unsigned int);
470
471   memcpy (h, d + off, sizeof (HEADER));
472   off += sizeof (HEADER);
473
474   h->env = envelope_new();
475   restore_envelope (h->env, d, &off);
476
477   h->content = body_new();
478   restore_body (h->content, d, &off);
479
480   restore_char (&h->maildir_flags, d, &off);
481
482   /* this is needed for maildir style mailboxes */
483   if (oh) {
484     h->old = (*oh)->old;
485     h->path = m_strdup((*oh)->path);
486     header_delete(oh);
487   }
488
489   return h;
490 }
491
492 #if defined(HAVE_QDBM)
493 void *
494 mutt_hcache_open(const char *path, const char *folder)
495 {
496   struct header_cache *h = p_new(struct header_cache, 1);
497   int    flags = VL_OWRITER | VL_OCREAT;
498   h->db = NULL;
499   h->folder = m_strdup(folder);
500   h->crc = generate_crc32();
501
502   if (!path || path[0] == '\0')
503   {
504     p_delete(&h->folder);
505     p_delete(&h);
506     return NULL;
507   }
508
509   path = mutt_hcache_per_folder(path, folder);
510
511   if (option(OPTHCACHECOMPRESS))
512     flags |= VL_OZCOMP;
513
514   h->db = vlopen(path, flags, VL_CMPLEX);
515   if (h->db)
516     return h;
517   else
518   {
519     p_delete(&h->folder);
520     p_delete(&h);
521
522     return NULL;
523   }
524 }
525
526 void
527 mutt_hcache_close(void *db)
528 {
529   struct header_cache *h = db;
530
531   if (!h)
532     return;
533
534   vlclose(h->db);
535   p_delete(&h->folder);
536   p_delete(&h);
537 }
538
539 void *
540 mutt_hcache_fetch(void *db, const char *filename,
541                   ssize_t(*keylen) (const char *fn))
542 {
543   struct header_cache *h = db;
544   char path[_POSIX_PATH_MAX];
545   int ksize;
546   char *data = NULL;
547
548   if (!h)
549     return NULL;
550
551   m_strcpy(path, sizeof(path), h->folder);
552   m_strcat(path, sizeof(path), filename);
553
554   ksize = strlen(h->folder) + keylen(path + strlen(h->folder));
555
556   data = vlget(h->db, path, ksize, NULL);
557
558   if (!crc32_matches(data, h->crc))
559   {
560     p_delete(&data);
561     return NULL;
562   }
563
564   return data;
565 }
566
567 int
568 mutt_hcache_store(void *db, const char *filename, HEADER * header,
569                   unsigned long uid_validity,
570                   ssize_t(*keylen) (const char *fn))
571 {
572   struct header_cache *h = db;
573   char path[_POSIX_PATH_MAX];
574   int ret;
575   int ksize, dsize;
576   char *data = NULL;
577
578   if (!h)
579     return -1;
580
581   m_strcpy(path, sizeof(path), h->folder);
582   m_strcat(path, sizeof(path), filename);
583
584   ksize = strlen(h->folder) + keylen(path + strlen(h->folder));
585
586   data  = mutt_hcache_dump(db, header, &dsize, uid_validity);
587
588   ret = vlput(h->db, path, ksize, data, dsize, VL_DOVER);
589
590   p_delete(&data);
591
592   return ret;
593 }
594
595 int
596 mutt_hcache_delete(void *db, const char *filename,
597                    ssize_t(*keylen) (const char *fn))
598 {
599   struct header_cache *h = db;
600   char path[_POSIX_PATH_MAX];
601   int ksize;
602
603   if (!h)
604     return -1;
605
606   m_strcpy(path, sizeof(path), h->folder);
607   m_strcat(path, sizeof(path), filename);
608
609   ksize = strlen(h->folder) + keylen(path + strlen(h->folder));
610
611   return vlout(h->db, path, ksize);
612 }
613
614 #elif defined(HAVE_GDBM)
615
616 void *mutt_hcache_open (const char *path, const char *folder)
617 {
618   struct header_cache *h = p_new(struct header_cache, 1);
619   int pagesize =
620     atoi (HeaderCachePageSize) ? atoi (HeaderCachePageSize) : 16384;
621   h->db = NULL;
622   h->folder = m_strdup(folder);
623   h->crc = generate_crc32 ();
624
625   if (!path || path[0] == '\0') {
626     p_delete(&h->folder);
627     p_delete(&h);
628     return NULL;
629   }
630
631   path = mutt_hcache_per_folder (path, folder);
632
633   h->db = gdbm_open ((char *) path, pagesize, GDBM_WRCREAT, 00600, NULL);
634   if (h->db) {
635     return h;
636   }
637
638   /* if rw failed try ro */
639   h->db = gdbm_open ((char *) path, pagesize, GDBM_READER, 00600, NULL);
640   if (h->db) {
641     return h;
642   }
643   else {
644     p_delete(&h->folder);
645     p_delete(&h);
646
647     return NULL;
648   }
649 }
650
651 void mutt_hcache_close (void *db)
652 {
653   struct header_cache *h = db;
654
655   if (!h) {
656     return;
657   }
658
659   gdbm_close (h->db);
660   p_delete(&h->folder);
661   p_delete(&h);
662 }
663
664 void *mutt_hcache_fetch (void *db, const char *filename,
665                          ssize_t (*keylen) (const char *fn))
666 {
667   struct header_cache *h = db;
668   datum key;
669   datum data;
670   char path[_POSIX_PATH_MAX];
671
672   if (!h) {
673     return NULL;
674   }
675
676   m_strcpy(path, sizeof(path), h->folder);
677   strncat (path, filename, sizeof (path) - m_strlen(path));
678
679   key.dptr = path;
680   key.dsize = keylen (path);
681
682   data = gdbm_fetch (h->db, key);
683
684   if (!crc32_matches (data.dptr, h->crc)) {
685     p_delete(&data.dptr);
686     return NULL;
687   }
688
689   return data.dptr;
690 }
691
692 int
693 mutt_hcache_store (void *db, const char *filename, HEADER * header,
694                    unsigned long uid_validity, ssize_t (*keylen) (const char *fn))
695 {
696   struct header_cache *h = db;
697   datum key;
698   datum data;
699   char path[_POSIX_PATH_MAX];
700   int ret;
701
702   if (!h) {
703     return -1;
704   }
705
706   m_strcpy(path, sizeof(path), h->folder);
707   strncat (path, filename, sizeof (path) - m_strlen(path));
708
709   key.dptr = path;
710   key.dsize = keylen (path);
711
712   data.dptr = mutt_hcache_dump (db, header, &data.dsize, uid_validity);
713
714   ret = gdbm_store (h->db, key, data, GDBM_REPLACE);
715
716   p_delete(&data.dptr);
717
718   return ret;
719 }
720
721 int
722 mutt_hcache_delete (void *db, const char *filename,
723                     ssize_t (*keylen) (const char *fn))
724 {
725   datum key;
726   struct header_cache *h = db;
727   char path[_POSIX_PATH_MAX];
728
729   if (!h) {
730     return -1;
731   }
732
733   m_strcpy(path, sizeof(path), h->folder);
734   strncat (path, filename, sizeof (path) - m_strlen(path));
735
736   key.dptr = path;
737   key.dsize = keylen (path);
738
739   return gdbm_delete (h->db, key);
740 }
741 #endif
742
743 #endif /* USE_HCACHE */