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