]> git.llucax.com Git - software/mutt-debian.git/blob - hcache.c
Move Mutt with NNTP support to mutt-nntp package
[software/mutt-debian.git] / hcache.c
1 /*
2  * Copyright (C) 2004 Thomas Glanzmann <sithglan@stud.uni-erlangen.de>
3  * Copyright (C) 2004 Tobias Werth <sitowert@stud.uni-erlangen.de>
4  * Copyright (C) 2004 Brian Fundakowski Feldman <green@FreeBSD.org>
5  *
6  *     This program is free software; you can redistribute it and/or modify
7  *     it under the terms of the GNU General Public License as published by
8  *     the Free Software Foundation; either version 2 of the License, or
9  *     (at your option) any later version.
10  *
11  *     This program is distributed in the hope that it will be useful,
12  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *     GNU General Public License for more details.
15  *
16  *     You should have received a copy of the GNU General Public License
17  *     along with this program; if not, write to the Free Software
18  *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20
21 #if HAVE_CONFIG_H
22 #include "config.h"
23 #endif                          /* HAVE_CONFIG_H */
24
25 #if HAVE_QDBM
26 #include <depot.h>
27 #include <cabin.h>
28 #include <villa.h>
29 #elif HAVE_TC
30 #include <tcbdb.h>
31 #elif HAVE_GDBM
32 #include <gdbm.h>
33 #elif HAVE_DB4
34 #include <db.h>
35 #endif
36
37 #include <errno.h>
38 #include <fcntl.h>
39 #if HAVE_SYS_TIME_H
40 #include <sys/time.h>
41 #endif
42 #include "mutt.h"
43 #include "hcache.h"
44 #include "hcversion.h"
45 #include "mx.h"
46 #include "lib.h"
47 #include "md5.h"
48 #include "rfc822.h"
49
50 #if HAVE_QDBM
51 static struct header_cache
52 {
53   VILLA *db;
54   char *folder;
55   unsigned int crc;
56 } HEADER_CACHE;
57 #elif HAVE_TC
58 static struct header_cache
59 {
60   TCBDB *db;
61   char *folder;
62   unsigned int crc;
63 } HEADER_CACHE;
64 #elif HAVE_GDBM
65 static struct header_cache
66 {
67   GDBM_FILE db;
68   char *folder;
69   unsigned int crc;
70 } HEADER_CACHE;
71 #elif HAVE_DB4
72 static struct header_cache
73 {
74   DB_ENV *env;
75   DB *db;
76   char *folder;
77   unsigned int crc;
78   int fd;
79   char lockfile[_POSIX_PATH_MAX];
80 } HEADER_CACHE;
81
82 static void mutt_hcache_dbt_init(DBT * dbt, void *data, size_t len);
83 static void mutt_hcache_dbt_empty_init(DBT * dbt);
84 #endif
85
86 typedef union
87 {
88   struct timeval timeval;
89   unsigned int uidvalidity;
90 } validate;
91
92 static void *
93 lazy_malloc(size_t siz)
94 {
95   if (0 < siz && siz < 4096)
96     siz = 4096;
97
98   return safe_malloc(siz);
99 }
100
101 static void
102 lazy_realloc(void *ptr, size_t siz)
103 {
104   void **p = (void **) ptr;
105
106   if (p != NULL && 0 < siz && siz < 4096)
107     return;
108
109   safe_realloc(ptr, siz);
110 }
111
112 static unsigned char *
113 dump_int(unsigned int i, unsigned char *d, int *off)
114 {
115   lazy_realloc(&d, *off + sizeof (int));
116   memcpy(d + *off, &i, sizeof (int));
117   (*off) += sizeof (int);
118
119   return d;
120 }
121
122 static void
123 restore_int(unsigned int *i, const unsigned char *d, int *off)
124 {
125   memcpy(i, d + *off, sizeof (int));
126   (*off) += sizeof (int);
127 }
128
129 static inline int is_ascii (const char *p, size_t len) {
130   register const char *s = p;
131   while (s && (unsigned) (s - p) < len) {
132     if ((*s & 0x80) != 0)
133       return 0;
134     s++;
135   }
136   return 1;
137 }
138
139 static unsigned char *
140 dump_char_size(char *c, unsigned char *d, int *off, ssize_t size, int convert)
141 {
142   char *p = c;
143
144   if (c == NULL)
145   {
146     size = 0;
147     d = dump_int(size, d, off);
148     return d;
149   }
150
151   if (convert && !is_ascii (c, size)) {
152     p = mutt_substrdup (c, c + size);
153     if (mutt_convert_string (&p, Charset, "utf-8", 0) == 0) {
154       c = p;
155       size = mutt_strlen (c) + 1;
156     }
157   }
158
159   d = dump_int(size, d, off);
160   lazy_realloc(&d, *off + size);
161   memcpy(d + *off, p, size);
162   *off += size;
163
164   if (p != c)
165     FREE(&p);
166
167   return d;
168 }
169
170 static unsigned char *
171 dump_char(char *c, unsigned char *d, int *off, int convert)
172 {
173   return dump_char_size (c, d, off, mutt_strlen (c) + 1, convert);
174 }
175
176 static void
177 restore_char(char **c, const unsigned char *d, int *off, int convert)
178 {
179   unsigned int size;
180   restore_int(&size, d, off);
181
182   if (size == 0)
183   {
184     *c = NULL;
185     return;
186   }
187
188   *c = safe_malloc(size);
189   memcpy(*c, d + *off, size);
190   if (convert && !is_ascii (*c, size)) {
191     char *tmp = safe_strdup (*c);
192     if (mutt_convert_string (&tmp, "utf-8", Charset, 0) == 0) {
193       mutt_str_replace (c, tmp);
194     } else {
195       FREE(&tmp);
196     }
197   }
198   *off += size;
199 }
200
201 static unsigned char *
202 dump_address(ADDRESS * a, unsigned char *d, int *off, int convert)
203 {
204   unsigned int counter = 0;
205   unsigned int start_off = *off;
206
207   d = dump_int(0xdeadbeef, d, off);
208
209   while (a)
210   {
211 #ifdef EXACT_ADDRESS
212     d = dump_char(a->val, d, off, convert);
213 #endif
214     d = dump_char(a->personal, d, off, convert);
215     d = dump_char(a->mailbox, d, off, 0);
216     d = dump_int(a->group, d, off);
217     a = a->next;
218     counter++;
219   }
220
221   memcpy(d + start_off, &counter, sizeof (int));
222
223   return d;
224 }
225
226 static void
227 restore_address(ADDRESS ** a, const unsigned char *d, int *off, int convert)
228 {
229   unsigned int counter;
230
231   restore_int(&counter, d, off);
232
233   while (counter)
234   {
235     *a = rfc822_new_address();
236 #ifdef EXACT_ADDRESS
237     restore_char(&(*a)->val, d, off, convert);
238 #endif
239     restore_char(&(*a)->personal, d, off, convert);
240     restore_char(&(*a)->mailbox, d, off, 0);
241     restore_int((unsigned int *) &(*a)->group, d, off);
242     a = &(*a)->next;
243     counter--;
244   }
245
246   *a = NULL;
247 }
248
249 static unsigned char *
250 dump_list(LIST * l, unsigned char *d, int *off, int convert)
251 {
252   unsigned int counter = 0;
253   unsigned int start_off = *off;
254
255   d = dump_int(0xdeadbeef, d, off);
256
257   while (l)
258   {
259     d = dump_char(l->data, d, off, convert);
260     l = l->next;
261     counter++;
262   }
263
264   memcpy(d + start_off, &counter, sizeof (int));
265
266   return d;
267 }
268
269 static void
270 restore_list(LIST ** l, const unsigned char *d, int *off, int convert)
271 {
272   unsigned int counter;
273
274   restore_int(&counter, d, off);
275
276   while (counter)
277   {
278     *l = safe_malloc(sizeof (LIST));
279     restore_char(&(*l)->data, d, off, convert);
280     l = &(*l)->next;
281     counter--;
282   }
283
284   *l = NULL;
285 }
286
287 static unsigned char *
288 dump_buffer(BUFFER * b, unsigned char *d, int *off, int convert)
289 {
290   if (!b)
291   {
292     d = dump_int(0, d, off);
293     return d;
294   }
295   else
296     d = dump_int(1, d, off);
297
298   d = dump_char_size(b->data, d, off, b->dsize + 1, convert);
299   d = dump_int(b->dptr - b->data, d, off);
300   d = dump_int(b->dsize, d, off);
301   d = dump_int(b->destroy, d, off);
302
303   return d;
304 }
305
306 static void
307 restore_buffer(BUFFER ** b, const unsigned char *d, int *off, int convert)
308 {
309   unsigned int used;
310   unsigned int offset;
311   restore_int(&used, d, off);
312   if (!used)
313   {
314     return;
315   }
316
317   *b = safe_malloc(sizeof (BUFFER));
318
319   restore_char(&(*b)->data, d, off, convert);
320   restore_int(&offset, d, off);
321   (*b)->dptr = (*b)->data + offset;
322   restore_int (&used, d, off);
323   (*b)->dsize = used;
324   restore_int (&used, d, off);
325   (*b)->destroy = used;
326 }
327
328 static unsigned char *
329 dump_parameter(PARAMETER * p, unsigned char *d, int *off, int convert)
330 {
331   unsigned int counter = 0;
332   unsigned int start_off = *off;
333
334   d = dump_int(0xdeadbeef, d, off);
335
336   while (p)
337   {
338     d = dump_char(p->attribute, d, off, 0);
339     d = dump_char(p->value, d, off, convert);
340     p = p->next;
341     counter++;
342   }
343
344   memcpy(d + start_off, &counter, sizeof (int));
345
346   return d;
347 }
348
349 static void
350 restore_parameter(PARAMETER ** p, const unsigned char *d, int *off, int convert)
351 {
352   unsigned int counter;
353
354   restore_int(&counter, d, off);
355
356   while (counter)
357   {
358     *p = safe_malloc(sizeof (PARAMETER));
359     restore_char(&(*p)->attribute, d, off, 0);
360     restore_char(&(*p)->value, d, off, convert);
361     p = &(*p)->next;
362     counter--;
363   }
364
365   *p = NULL;
366 }
367
368 static unsigned char *
369 dump_body(BODY * c, unsigned char *d, int *off, int convert)
370 {
371   BODY nb;
372
373   memcpy (&nb, c, sizeof (BODY));
374
375   /* some fields are not safe to cache */
376   nb.content = NULL;
377   nb.charset = NULL;
378   nb.next = NULL;
379   nb.parts = NULL;
380   nb.hdr = NULL;
381   nb.aptr = NULL;
382
383   lazy_realloc(&d, *off + sizeof (BODY));
384   memcpy(d + *off, &nb, sizeof (BODY));
385   *off += sizeof (BODY);
386
387   d = dump_char(nb.xtype, d, off, 0);
388   d = dump_char(nb.subtype, d, off, 0);
389
390   d = dump_parameter(nb.parameter, d, off, convert);
391
392   d = dump_char(nb.description, d, off, convert);
393   d = dump_char(nb.form_name, d, off, convert);
394   d = dump_char(nb.filename, d, off, convert);
395   d = dump_char(nb.d_filename, d, off, convert);
396
397   return d;
398 }
399
400 static void
401 restore_body(BODY * c, const unsigned char *d, int *off, int convert)
402 {
403   memcpy(c, d + *off, sizeof (BODY));
404   *off += sizeof (BODY);
405
406   restore_char(&c->xtype, d, off, 0);
407   restore_char(&c->subtype, d, off, 0);
408
409   restore_parameter(&c->parameter, d, off, convert);
410
411   restore_char(&c->description, d, off, convert);
412   restore_char(&c->form_name, d, off, convert);
413   restore_char(&c->filename, d, off, convert);
414   restore_char(&c->d_filename, d, off, convert);
415 }
416
417 static unsigned char *
418 dump_envelope(ENVELOPE * e, unsigned char *d, int *off, int convert)
419 {
420   d = dump_address(e->return_path, d, off, convert);
421   d = dump_address(e->from, d, off, convert);
422   d = dump_address(e->to, d, off, convert);
423   d = dump_address(e->cc, d, off, convert);
424   d = dump_address(e->bcc, d, off, convert);
425   d = dump_address(e->sender, d, off, convert);
426   d = dump_address(e->reply_to, d, off, convert);
427   d = dump_address(e->mail_followup_to, d, off, convert);
428
429   d = dump_char(e->list_post, d, off, convert);
430   d = dump_char(e->subject, d, off, convert);
431
432   if (e->real_subj)
433     d = dump_int(e->real_subj - e->subject, d, off);
434   else
435     d = dump_int(-1, d, off);
436
437   d = dump_char(e->message_id, d, off, 0);
438   d = dump_char(e->supersedes, d, off, 0);
439   d = dump_char(e->date, d, off, 0);
440   d = dump_char(e->x_label, d, off, convert);
441
442   d = dump_buffer(e->spam, d, off, convert);
443
444   d = dump_list(e->references, d, off, 0);
445   d = dump_list(e->in_reply_to, d, off, 0);
446   d = dump_list(e->userhdrs, d, off, convert);
447
448   return d;
449 }
450
451 static void
452 restore_envelope(ENVELOPE * e, const unsigned char *d, int *off, int convert)
453 {
454   int real_subj_off;
455
456   restore_address(&e->return_path, d, off, convert);
457   restore_address(&e->from, d, off, convert);
458   restore_address(&e->to, d, off, convert);
459   restore_address(&e->cc, d, off, convert);
460   restore_address(&e->bcc, d, off, convert);
461   restore_address(&e->sender, d, off, convert);
462   restore_address(&e->reply_to, d, off, convert);
463   restore_address(&e->mail_followup_to, d, off, convert);
464
465   restore_char(&e->list_post, d, off, convert);
466   restore_char(&e->subject, d, off, convert);
467   restore_int((unsigned int *) (&real_subj_off), d, off);
468
469   if (0 <= real_subj_off)
470     e->real_subj = e->subject + real_subj_off;
471   else
472     e->real_subj = NULL;
473
474   restore_char(&e->message_id, d, off, 0);
475   restore_char(&e->supersedes, d, off, 0);
476   restore_char(&e->date, d, off, 0);
477   restore_char(&e->x_label, d, off, convert);
478
479   restore_buffer(&e->spam, d, off, convert);
480
481   restore_list(&e->references, d, off, 0);
482   restore_list(&e->in_reply_to, d, off, 0);
483   restore_list(&e->userhdrs, d, off, convert);
484 }
485
486 static int
487 crc_matches(const char *d, unsigned int crc)
488 {
489   int off = sizeof (validate);
490   unsigned int mycrc = 0;
491
492   if (!d)
493     return 0;
494
495   restore_int(&mycrc, (unsigned char *) d, &off);
496
497   return (crc == mycrc);
498 }
499
500 /* Append md5sumed folder to path if path is a directory. */
501 static const char *
502 mutt_hcache_per_folder(const char *path, const char *folder,
503                        hcache_namer_t namer)
504 {
505   static char hcpath[_POSIX_PATH_MAX];
506   struct stat sb;
507   unsigned char md5sum[16];
508   char* s;
509   int ret, plen;
510 #ifndef HAVE_ICONV
511   const char *chs = Charset && *Charset ? Charset : 
512                     mutt_get_default_charset ();
513 #endif
514
515   plen = mutt_strlen (path);
516
517   ret = stat(path, &sb);
518   if (ret < 0 && path[plen-1] != '/')
519   {
520 #ifdef HAVE_ICONV
521     return path;
522 #else
523     snprintf (hcpath, _POSIX_PATH_MAX, "%s-%s", path, chs);
524     return hcpath;
525 #endif
526   }
527
528   if (ret >= 0 && !S_ISDIR(sb.st_mode))
529   {
530 #ifdef HAVE_ICONV
531     return path;
532 #else
533     snprintf (hcpath, _POSIX_PATH_MAX, "%s-%s", path, chs);
534     return hcpath;
535 #endif
536   }
537
538   if (namer)
539   {
540     snprintf (hcpath, sizeof (hcpath), "%s%s", path,
541               path[plen-1] == '/' ? "" : "/");
542     if (path[plen-1] != '/')
543       plen++;
544
545     ret = namer (folder, hcpath + plen, sizeof (hcpath) - plen);
546   }
547   else
548   {
549     md5_buffer (folder, strlen (folder), &md5sum);
550
551     ret = snprintf(hcpath, _POSIX_PATH_MAX,
552                    "%s/%02x%02x%02x%02x%02x%02x%02x%02x"
553                    "%02x%02x%02x%02x%02x%02x%02x%02x"
554 #ifndef HAVE_ICONV
555                    "-%s"
556 #endif
557                    ,
558                    path, md5sum[0], md5sum[1], md5sum[2], md5sum[3],
559                    md5sum[4], md5sum[5], md5sum[6], md5sum[7], md5sum[8],
560                    md5sum[9], md5sum[10], md5sum[11], md5sum[12],
561                    md5sum[13], md5sum[14], md5sum[15]
562 #ifndef HAVE_ICONV
563                    ,chs
564 #endif
565                    );
566   }
567   
568   if (ret <= 0)
569     return path;
570
571   if (stat (hcpath, &sb) >= 0)
572     return hcpath;
573
574   s = strchr (hcpath + 1, '/');
575   while (s)
576   {
577     /* create missing path components */
578     *s = '\0';
579     if (stat (hcpath, &sb) < 0 && (errno != ENOENT || mkdir (hcpath, 0777) < 0))
580       return path;
581     *s = '/';
582     s = strchr (s + 1, '/');
583   }
584
585   return hcpath;
586 }
587
588 /* This function transforms a header into a char so that it is useable by
589  * db_store */
590 static void *
591 mutt_hcache_dump(header_cache_t *h, HEADER * header, int *off,
592                  unsigned int uidvalidity)
593 {
594   unsigned char *d = NULL;
595   HEADER nh;
596   int convert = !Charset_is_utf8;
597
598   *off = 0;
599   d = lazy_malloc(sizeof (validate));
600
601   if (uidvalidity)
602     memcpy(d, &uidvalidity, sizeof (uidvalidity));
603   else
604   {
605     struct timeval now;
606     gettimeofday(&now, NULL);
607     memcpy(d, &now, sizeof (struct timeval));
608   }
609   *off += sizeof (validate);
610
611   d = dump_int(h->crc, d, off);
612
613   lazy_realloc(&d, *off + sizeof (HEADER));
614   memcpy(&nh, header, sizeof (HEADER));
615
616   /* some fields are not safe to cache */
617   nh.tagged = 0;
618   nh.changed = 0;
619   nh.threaded = 0;
620   nh.recip_valid = 0;
621   nh.searched = 0;
622   nh.matched = 0;
623   nh.collapsed = 0;
624   nh.limited = 0;
625   nh.num_hidden = 0;
626   nh.recipient = 0;
627   nh.pair = 0;
628   nh.attach_valid = 0;
629   nh.path = NULL;
630   nh.tree = NULL;
631   nh.thread = NULL;
632 #ifdef MIXMASTER
633   nh.chain = NULL;
634 #endif
635 #if defined USE_POP || defined USE_IMAP
636   nh.data = NULL;
637 #endif
638
639   memcpy(d + *off, &nh, sizeof (HEADER));
640   *off += sizeof (HEADER);
641
642   d = dump_envelope(nh.env, d, off, convert);
643   d = dump_body(nh.content, d, off, convert);
644   d = dump_char(nh.maildir_flags, d, off, convert);
645
646   return d;
647 }
648
649 HEADER *
650 mutt_hcache_restore(const unsigned char *d, HEADER ** oh)
651 {
652   int off = 0;
653   HEADER *h = mutt_new_header();
654   int convert = !Charset_is_utf8;
655
656   /* skip validate */
657   off += sizeof (validate);
658
659   /* skip crc */
660   off += sizeof (unsigned int);
661
662   memcpy(h, d + off, sizeof (HEADER));
663   off += sizeof (HEADER);
664
665   h->env = mutt_new_envelope();
666   restore_envelope(h->env, d, &off, convert);
667
668   h->content = mutt_new_body();
669   restore_body(h->content, d, &off, convert);
670
671   restore_char(&h->maildir_flags, d, &off, convert);
672
673   /* this is needed for maildir style mailboxes */
674   if (oh)
675   {
676     h->old = (*oh)->old;
677     h->path = safe_strdup((*oh)->path);
678     mutt_free_header(oh);
679   }
680
681   return h;
682 }
683
684 void *
685 mutt_hcache_fetch(header_cache_t *h, const char *filename,
686                   size_t(*keylen) (const char *fn))
687 {
688   void* data;
689
690   data = mutt_hcache_fetch_raw (h, filename, keylen);
691
692   if (!data || !crc_matches(data, h->crc))
693   {
694     FREE(&data);
695     return NULL;
696   }
697   
698   return data;
699 }
700
701 void *
702 mutt_hcache_fetch_raw (header_cache_t *h, const char *filename,
703                        size_t(*keylen) (const char *fn))
704 {
705 #ifndef HAVE_DB4
706   char path[_POSIX_PATH_MAX];
707   int ksize;
708 #endif
709 #ifdef HAVE_QDBM
710   char *data = NULL;
711 #elif HAVE_TC
712   void *data;
713   int sp;
714 #elif HAVE_GDBM
715   datum key;
716   datum data;
717 #elif HAVE_DB4
718   DBT key;
719   DBT data;
720 #endif
721   
722   if (!h)
723     return NULL;
724   
725 #ifdef HAVE_DB4
726   if (filename[0] == '/')
727     filename++;
728
729   mutt_hcache_dbt_init(&key, (void *) filename, keylen(filename));
730   mutt_hcache_dbt_empty_init(&data);
731   data.flags = DB_DBT_MALLOC;
732   
733   h->db->get(h->db, NULL, &key, &data, 0);
734   
735   return data.data;
736 #else
737   strncpy(path, h->folder, sizeof (path));
738   safe_strcat(path, sizeof (path), filename);
739
740   ksize = strlen (h->folder) + keylen (path + strlen (h->folder));  
741 #endif
742 #ifdef HAVE_QDBM
743   data = vlget(h->db, path, ksize, NULL);
744   
745   return data;
746 #elif HAVE_TC
747   data = tcbdbget(h->db, path, ksize, &sp);
748
749   return data;
750 #elif HAVE_GDBM
751   key.dptr = path;
752   key.dsize = ksize;
753   
754   data = gdbm_fetch(h->db, key);
755   
756   return data.dptr;
757 #endif
758 }
759
760 int
761 mutt_hcache_store(header_cache_t *h, const char *filename, HEADER * header,
762                   unsigned int uidvalidity,
763                   size_t(*keylen) (const char *fn))
764 {
765   char* data;
766   int dlen;
767   int ret;
768   
769   if (!h)
770     return -1;
771   
772   data = mutt_hcache_dump(h, header, &dlen, uidvalidity);
773   ret = mutt_hcache_store_raw (h, filename, data, dlen, keylen);
774   
775   FREE(&data);
776   
777   return ret;
778 }
779
780 int
781 mutt_hcache_store_raw (header_cache_t* h, const char* filename, void* data,
782                        size_t dlen, size_t(*keylen) (const char* fn))
783 {
784 #ifndef HAVE_DB4
785   char path[_POSIX_PATH_MAX];
786   int ksize;
787 #endif
788 #if HAVE_GDBM
789   datum key;
790   datum databuf;
791 #elif HAVE_DB4
792   DBT key;
793   DBT databuf;
794 #endif
795   
796   if (!h)
797     return -1;
798
799 #if HAVE_DB4
800   if (filename[0] == '/')
801     filename++;
802   
803   mutt_hcache_dbt_init(&key, (void *) filename, keylen(filename));
804   
805   mutt_hcache_dbt_empty_init(&databuf);
806   databuf.flags = DB_DBT_USERMEM;
807   databuf.data = data;
808   databuf.size = dlen;
809   databuf.ulen = dlen;
810   
811   return h->db->put(h->db, NULL, &key, &databuf, 0);
812 #else
813   strncpy(path, h->folder, sizeof (path));
814   safe_strcat(path, sizeof (path), filename);
815
816   ksize = strlen(h->folder) + keylen(path + strlen(h->folder));
817 #endif
818 #if HAVE_QDBM
819   return vlput(h->db, path, ksize, data, dlen, VL_DOVER);
820 #elif HAVE_TC
821   return tcbdbput(h->db, path, ksize, data, dlen);
822 #elif HAVE_GDBM
823   key.dptr = path;
824   key.dsize = ksize;
825   
826   databuf.dsize = dlen;
827   databuf.dptr = data;
828   
829   return gdbm_store(h->db, key, databuf, GDBM_REPLACE);
830 #endif
831 }
832
833 static char* get_foldername(const char *folder)
834 {
835   char *p = NULL;
836   char path[_POSIX_PATH_MAX];
837   struct stat st;
838
839   mutt_encode_path (path, sizeof (path), folder);
840
841   /* if the folder is local, canonify the path to avoid
842    * to ensure equivalent paths share the hcache */
843   if (stat (path, &st) == 0)
844   {
845     p = safe_malloc (PATH_MAX+1);
846     if (!realpath (path, p))
847       mutt_str_replace (&p, path);
848   } else
849     p = safe_strdup (path);
850
851   return p;
852 }
853
854 #if HAVE_QDBM
855 static int
856 hcache_open_qdbm (struct header_cache* h, const char* path)
857 {
858   int    flags = VL_OWRITER | VL_OCREAT;
859
860   if (option(OPTHCACHECOMPRESS))
861     flags |= VL_OZCOMP;
862
863   h->db = vlopen (path, flags, VL_CMPLEX);
864   if (h->db)
865     return 0;
866   else
867     return -1;
868 }
869
870 void
871 mutt_hcache_close(header_cache_t *h)
872 {
873   if (!h)
874     return;
875
876   vlclose(h->db);
877   FREE(&h->folder);
878   FREE(&h);
879 }
880
881 int
882 mutt_hcache_delete(header_cache_t *h, const char *filename,
883                    size_t(*keylen) (const char *fn))
884 {
885   char path[_POSIX_PATH_MAX];
886   int ksize;
887
888   if (!h)
889     return -1;
890
891   strncpy(path, h->folder, sizeof (path));
892   safe_strcat(path, sizeof (path), filename);
893
894   ksize = strlen(h->folder) + keylen(path + strlen(h->folder));
895
896   return vlout(h->db, path, ksize);
897 }
898
899 #elif HAVE_TC
900 static int
901 hcache_open_tc (struct header_cache* h, const char* path)
902 {
903   h->db = tcbdbnew();
904   if (option(OPTHCACHECOMPRESS))
905     tcbdbtune(h->db, 0, 0, 0, -1, -1, BDBTDEFLATE);
906   if (tcbdbopen(h->db, path, BDBOWRITER | BDBOCREAT))
907     return 0;
908   else
909   {
910     tcbdbdel(h->db);
911     return -1;
912   }
913 }
914
915 void
916 mutt_hcache_close(header_cache_t *h)
917 {
918   if (!h)
919     return;
920
921   tcbdbclose(h->db);
922   tcbdbdel(h->db);
923   FREE(&h->folder);
924   FREE(&h);
925 }
926
927 int
928 mutt_hcache_delete(header_cache_t *h, const char *filename,
929                    size_t(*keylen) (const char *fn))
930 {
931   char path[_POSIX_PATH_MAX];
932   int ksize;
933
934   if (!h)
935     return -1;
936
937   strncpy(path, h->folder, sizeof (path));
938   safe_strcat(path, sizeof (path), filename);
939
940   ksize = strlen(h->folder) + keylen(path + strlen(h->folder));
941
942   return tcbdbout(h->db, path, ksize);
943 }
944
945 #elif HAVE_GDBM
946 static int
947 hcache_open_gdbm (struct header_cache* h, const char* path)
948 {
949   int pagesize;
950
951   if (mutt_atoi (HeaderCachePageSize, &pagesize) < 0 || pagesize <= 0)
952     pagesize = 16384;
953
954   h->db = gdbm_open((char *) path, pagesize, GDBM_WRCREAT, 00600, NULL);
955   if (h->db)
956     return 0;
957
958   /* if rw failed try ro */
959   h->db = gdbm_open((char *) path, pagesize, GDBM_READER, 00600, NULL);
960   if (h->db)
961     return 0;
962
963   return -1;
964 }
965
966 void
967 mutt_hcache_close(header_cache_t *h)
968 {
969   if (!h)
970     return;
971
972   gdbm_close(h->db);
973   FREE(&h->folder);
974   FREE(&h);
975 }
976
977 int
978 mutt_hcache_delete(header_cache_t *h, const char *filename,
979                    size_t(*keylen) (const char *fn))
980 {
981   datum key;
982   char path[_POSIX_PATH_MAX];
983
984   if (!h)
985     return -1;
986
987   strncpy(path, h->folder, sizeof (path));
988   safe_strcat(path, sizeof (path), filename);
989
990   key.dptr = path;
991   key.dsize = strlen(h->folder) + keylen(path + strlen(h->folder));
992
993   return gdbm_delete(h->db, key);
994 }
995 #elif HAVE_DB4
996
997 static void
998 mutt_hcache_dbt_init(DBT * dbt, void *data, size_t len)
999 {
1000   dbt->data = data;
1001   dbt->size = dbt->ulen = len;
1002   dbt->dlen = dbt->doff = 0;
1003   dbt->flags = DB_DBT_USERMEM;
1004 }
1005
1006 static void
1007 mutt_hcache_dbt_empty_init(DBT * dbt)
1008 {
1009   dbt->data = NULL;
1010   dbt->size = dbt->ulen = dbt->dlen = dbt->doff = 0;
1011   dbt->flags = 0;
1012 }
1013
1014 static int
1015 hcache_open_db4 (struct header_cache* h, const char* path)
1016 {
1017   struct stat sb;
1018   int ret;
1019   u_int32_t createflags = DB_CREATE;
1020   int pagesize;
1021
1022   if (mutt_atoi (HeaderCachePageSize, &pagesize) < 0 || pagesize <= 0)
1023     pagesize = 16384;
1024
1025   snprintf (h->lockfile, _POSIX_PATH_MAX, "%s-lock-hack", path);
1026
1027   h->fd = open (h->lockfile, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
1028   if (h->fd < 0)
1029     return -1;
1030
1031   if (mx_lock_file (h->lockfile, h->fd, 1, 0, 5))
1032     goto fail_close;
1033
1034   ret = db_env_create (&h->env, 0);
1035   if (ret)
1036     goto fail_unlock;
1037
1038   ret = (*h->env->open)(h->env, NULL, DB_INIT_MPOOL | DB_CREATE | DB_PRIVATE,
1039         0600);
1040   if (ret)
1041     goto fail_env;
1042
1043   ret = db_create (&h->db, h->env, 0);
1044   if (ret)
1045     goto fail_env;
1046
1047   if (stat(path, &sb) != 0 && errno == ENOENT)
1048   {
1049     createflags |= DB_EXCL;
1050     h->db->set_pagesize(h->db, pagesize);
1051   }
1052
1053   ret = (*h->db->open)(h->db, NULL, path, h->folder, DB_BTREE, createflags,
1054                        0600);
1055   if (ret)
1056     goto fail_db;
1057
1058   return 0;
1059
1060   fail_db:
1061   h->db->close (h->db, 0);
1062   fail_env:
1063   h->env->close (h->env, 0);
1064   fail_unlock:
1065   mx_unlock_file (h->lockfile, h->fd, 0);
1066   fail_close:
1067   close (h->fd);
1068   unlink (h->lockfile);
1069
1070   return -1;
1071 }
1072
1073 void
1074 mutt_hcache_close(header_cache_t *h)
1075 {
1076   if (!h)
1077     return;
1078
1079   h->db->close (h->db, 0);
1080   h->env->close (h->env, 0);
1081   mx_unlock_file (h->lockfile, h->fd, 0);
1082   close (h->fd);
1083   unlink (h->lockfile);
1084   FREE (&h->folder);
1085   FREE (&h);
1086 }
1087
1088 int
1089 mutt_hcache_delete(header_cache_t *h, const char *filename,
1090                    size_t(*keylen) (const char *fn))
1091 {
1092   DBT key;
1093
1094   if (!h)
1095     return -1;
1096
1097   if (filename[0] == '/')
1098     filename++;
1099
1100   mutt_hcache_dbt_init(&key, (void *) filename, keylen(filename));
1101   return h->db->del(h->db, NULL, &key, 0);
1102 }
1103 #endif
1104
1105 header_cache_t *
1106 mutt_hcache_open(const char *path, const char *folder, hcache_namer_t namer)
1107 {
1108   struct header_cache *h = safe_calloc(1, sizeof (HEADER_CACHE));
1109   int (*hcache_open) (struct header_cache* h, const char* path);
1110   struct stat sb;
1111
1112 #if HAVE_QDBM
1113   hcache_open = hcache_open_qdbm;
1114 #elif HAVE_TC
1115   hcache_open= hcache_open_tc;
1116 #elif HAVE_GDBM
1117   hcache_open = hcache_open_gdbm;
1118 #elif HAVE_DB4
1119   hcache_open = hcache_open_db4;
1120 #endif
1121
1122   h->db = NULL;
1123   h->folder = get_foldername(folder);
1124   h->crc = HCACHEVER;
1125
1126   if (!path || path[0] == '\0')
1127   {
1128     FREE(&h->folder);
1129     FREE(&h);
1130     return NULL;
1131   }
1132
1133   path = mutt_hcache_per_folder(path, h->folder, namer);
1134
1135   if (!hcache_open (h, path))
1136     return h;
1137   else
1138   {
1139     /* remove a possibly incompatible version */
1140     if (!stat (path, &sb) && !unlink (path))
1141     {
1142       if (!hcache_open (h, path))
1143         return h;
1144     }
1145     FREE(&h->folder);
1146     FREE(&h);
1147
1148     return NULL;
1149   }
1150 }
1151
1152 #if HAVE_DB4
1153 const char *mutt_hcache_backend (void)
1154 {
1155   return DB_VERSION_STRING;
1156 }
1157 #elif HAVE_GDBM
1158 const char *mutt_hcache_backend (void)
1159 {
1160   return gdbm_version;
1161 }
1162 #elif HAVE_QDBM
1163 const char *mutt_hcache_backend (void)
1164 {
1165   return "qdbm " _QDBM_VERSION;
1166 }
1167 #elif HAVE_TC
1168 const char *mutt_hcache_backend (void)
1169 {
1170   return "tokyocabinet " _TC_VERSION;
1171 }
1172 #endif