070e56fee75c9caae8123d94d04745099cc4f3b7
[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 #if defined(HAVE_QDBM)
17 #include <depot.h>
18 #include <cabin.h>
19 #include <villa.h>
20 #elif defined(HAVE_GDBM)
21 #include <gdbm.h>
22 #else
23 #error neither HAVE_QDBM nor HAVE_GDBM are set ?!
24 #endif
25
26 #include "charset.h"
27 #include "mutt.h"
28 #include "hcache.h"
29
30 struct hcache_t {
31 #if defined(HAVE_QDBM)
32     VILLA *db;
33 #elif defined(HAVE_GDBM)
34     GDBM_FILE db;
35 #endif
36     char *folder;
37     unsigned int crc;
38 };
39
40 static unsigned int crc32(unsigned int crc, const void *src, ssize_t len)
41 {
42     int i;
43     const unsigned char *p = src;
44
45     while (len--) {
46         crc ^= *p++;
47         for (i = 0; i < 8; i++)
48             crc = (crc >> 1) ^ ((crc & 1) ? 0xedb88320 : 0);
49     }
50     return crc;
51 }
52
53 static int generate_crc32(void)
54 {
55     static int crc = 0;
56
57     crc = crc32(crc, "madmutt.2007.05.13", m_strlen("madmutt.2007.05.13"));
58 #ifdef HAVE_LANGINFO_CODESET
59     crc = crc32(crc, MCharset.charset, m_strlen(MCharset.charset));
60 #endif
61     crc = crc32(crc, "USE_POP",   m_strlen("USE_POP"));
62     crc = crc32(crc, "MIXMASTER", m_strlen("MIXMASTER"));
63     crc = crc32(crc, "USE_IMAP",  m_strlen("USE_IMAP"));
64 #ifdef USE_NNTP
65     crc = crc32(crc, "USE_NNTP",  m_strlen("USE_NNTP"));
66 #endif
67     return crc;
68 }
69
70 static const char *
71 mutt_hcache_per_folder(const char *path, const char *folder)
72 {
73     static char buf[_POSIX_PATH_MAX];
74     struct stat st;
75     int pos;
76
77     if (stat(path, &st) < 0 || !S_ISDIR(st.st_mode)) {
78         return path;
79     }
80
81     pos  = m_strcpy(buf, sizeof(buf), path);
82     pos += m_strputc(buf + pos, sizeof(buf) - pos, '/');
83
84     while (*folder) {
85         if (isalnum((unsigned char)*folder) || *folder == '.') {
86             pos += m_strputc(buf + pos, sizeof(buf) - pos, *folder);
87         } else {
88             pos += m_strputc(buf + pos, sizeof(buf) - pos, '_');
89         }
90         folder++;
91     }
92     pos += m_strcpy(buf + pos, sizeof(buf) - pos, ".hdb");
93     return buf;
94 }
95
96 /* store and restore things {{{ */
97
98 static const void *restore_int(const char *d, int *i)
99 {
100     memcpy(i, d, sizeof(*i));
101     return d + sizeof(*i);
102 }
103
104 static void dump_cstr(buffer_t *buf, const char *s)
105 {
106     int size = 0;
107
108     if (m_strisempty(s)) {
109         buffer_add(buf, &size, sizeof(size));
110         return;
111     }
112
113     size = strlen(s) + 1;
114     buffer_add(buf, &size, sizeof(size));
115     buffer_add(buf, s, size);
116 }
117
118 static const void *restore_cstr(const char *d, char **c)
119 {
120     int size;
121
122     d  = restore_int(d, &size);
123     *c = p_dup(d, size);
124     return d + size;
125 }
126
127 static void dump_address(buffer_t *buf, address_t *a)
128 {
129     int counter = 0, pos = buf->len;
130
131     buffer_add(buf, &counter, sizeof(counter));
132
133     for (; a; a = a->next, counter++) {
134         dump_cstr(buf, a->personal);
135         dump_cstr(buf, a->mailbox);
136         buffer_add(buf, &a->group, sizeof(a->group));
137     }
138
139     memcpy(buf->data + pos, &counter, sizeof(counter));
140 }
141
142 static const void *restore_address(const char *d, address_t **a)
143 {
144     int counter;
145
146     d = restore_int(d, &counter);
147
148     for (; counter > 0; counter--) {
149         *a = address_new();
150         d = restore_cstr(d, &(*a)->personal);
151         d = restore_cstr(d, &(*a)->mailbox);
152         d = restore_int(d, &(*a)->group);
153         a = &(*a)->next;
154     }
155
156     *a = NULL;
157     return d;
158 }
159
160 static void dump_list(buffer_t *buf, string_list_t *l)
161 {
162     int pos = buf->len;
163     int counter = 0;
164
165     buffer_add(buf, &counter, sizeof(counter));
166
167     for (; l; l = l->next, counter++) {
168         dump_cstr(buf, l->data);
169     }
170
171     memcpy(buf->data + pos, &counter, sizeof(counter));
172 }
173
174 static const void *restore_list(const char *d, string_list_t **l)
175 {
176     int counter;
177
178     d = restore_int(d, &counter);
179
180     for (; counter > 0; counter--) {
181         *l = string_item_new();
182         d = restore_cstr(d, &(*l)->data);
183         l = &(*l)->next;
184     }
185
186     *l = NULL;
187     return d;
188 }
189
190 static void dump_parameter(buffer_t *buf, parameter_t *p)
191 {
192     int pos = buf->len, counter = 0;
193
194     buffer_add(buf, &counter, sizeof(counter));
195
196     for (; p; p = p->next, counter++) {
197         dump_cstr(buf, p->attribute);
198         dump_cstr(buf, p->value);
199     }
200
201     memcpy(buf->data + pos, &counter, sizeof(counter));
202 }
203
204 static const void *restore_parameter(const char *d, parameter_t ** p)
205 {
206     int counter;
207
208     d = restore_int(d, &counter);
209
210     for (; counter > 0; counter--) {
211         *p = parameter_new();
212         d  = restore_cstr(d, &(*p)->attribute);
213         d  = restore_cstr(d, &(*p)->value);
214         p  = &(*p)->next;
215     }
216
217     *p = NULL;
218     return d;
219 }
220
221 static void dump_body(buffer_t *buf, BODY *b)
222 {
223     buffer_add(buf, b, sizeof(*b));
224     dump_cstr(buf, b->xtype);
225     dump_cstr(buf, b->subtype);
226
227     dump_parameter(buf, b->parameter);
228
229     dump_cstr(buf, b->description);
230     dump_cstr(buf, b->form_name);
231     dump_cstr(buf, b->filename);
232     dump_cstr(buf, b->d_filename);
233 }
234
235 static const void *restore_body(const char *d, BODY *c)
236 {
237     memcpy(c, d, sizeof(*c));
238     d += sizeof(*c);
239
240     d = restore_cstr(d, &c->xtype);
241     d = restore_cstr(d, &c->subtype);
242
243     d = restore_parameter(d, &c->parameter);
244
245     d = restore_cstr(d, &c->description);
246     d = restore_cstr(d, &c->form_name);
247     d = restore_cstr(d, &c->filename);
248     d = restore_cstr(d, &c->d_filename);
249     return d;
250 }
251
252 static void dump_envelope(buffer_t *buf, ENVELOPE * e)
253 {
254     int n;
255
256     dump_address(buf, e->return_path);
257     dump_address(buf, e->from);
258     dump_address(buf, e->to);
259     dump_address(buf, e->cc);
260     dump_address(buf, e->bcc);
261     dump_address(buf, e->sender);
262     dump_address(buf, e->reply_to);
263     dump_address(buf, e->mail_followup_to);
264
265     dump_cstr(buf, e->subject);
266     n = e->real_subj ? e->real_subj - e->subject : -1;
267     buffer_add(buf, &n, sizeof(n));
268
269     dump_cstr(buf, e->message_id);
270     dump_cstr(buf, e->supersedes);
271     dump_cstr(buf, e->date);
272     dump_cstr(buf, e->x_label);
273     dump_cstr(buf, e->list_post);
274
275 #ifdef USE_NNTP
276     dump_cstr(buf, e->newsgroups);
277     dump_cstr(buf, e->xref);
278     dump_cstr(buf, e->followup_to);
279     dump_cstr(buf, e->x_comment_to);
280 #endif
281
282     dump_list(buf, e->references);
283     dump_list(buf, e->in_reply_to);
284     dump_list(buf, e->userhdrs);
285 }
286
287 static const void *restore_envelope(const char *d, ENVELOPE *e)
288 {
289     int real_subj_off;
290
291     d = restore_address(d, &e->return_path);
292     d = restore_address(d, &e->from);
293     d = restore_address(d, &e->to);
294     d = restore_address(d, &e->cc);
295     d = restore_address(d, &e->bcc);
296     d = restore_address(d, &e->sender);
297     d = restore_address(d, &e->reply_to);
298     d = restore_address(d, &e->mail_followup_to);
299
300     d = restore_cstr(d, &e->subject);
301     d = restore_int(d, &real_subj_off);
302     if (real_subj_off >= 0) {
303         e->real_subj = e->subject + real_subj_off;
304     } else {
305         e->real_subj = NULL;
306     }
307     d = restore_cstr(d, &e->message_id);
308     d = restore_cstr(d, &e->supersedes);
309     d = restore_cstr(d, &e->date);
310     d = restore_cstr(d, &e->x_label);
311     d = restore_cstr(d, &e->list_post);
312
313 #ifdef USE_NNTP
314     d = restore_cstr(d, &e->newsgroups);
315     d = restore_cstr(d, &e->xref);
316     d = restore_cstr(d, &e->followup_to);
317     d = restore_cstr(d, &e->x_comment_to);
318 #endif
319
320     d = restore_list(d, &e->references);
321     d = restore_list(d, &e->in_reply_to);
322     d = restore_list(d, &e->userhdrs);
323
324     return d;
325 }
326
327 static buffer_t *mutt_hcache_dump(hcache_t *db, HEADER *h, long uid_validity)
328 {
329     buffer_t *res = buffer_new();
330
331     if (!uid_validity) {
332         uid_validity = time(NULL);
333     }
334     buffer_add(res, &uid_validity, sizeof(uid_validity));
335     buffer_add(res, &db->crc, sizeof(db->crc));
336     buffer_add(res, h, sizeof(*h));
337
338     dump_envelope(res, h->env);
339     dump_body(res, h->content);
340     dump_cstr(res, h->maildir_flags);
341     return res;
342 }
343
344 HEADER *mutt_hcache_restore(const void *_d, HEADER **oh)
345 {
346     const char *d = _d;
347     HEADER *h = header_new();
348
349     /* skip uid_validity + crc */
350     d += sizeof(long) + sizeof(int);
351
352     memcpy(h, d, sizeof(*h));
353     d += sizeof(*h);
354
355     h->env = envelope_new();
356     d = restore_envelope(d, h->env);
357
358     h->content = body_new();
359     d = restore_body(d, h->content);
360
361     d = restore_cstr(d, &h->maildir_flags);
362
363     /* this is needed for maildir style mailboxes */
364     if (oh) {
365         h->old = (*oh)->old;
366         h->path = m_strdup((*oh)->path);
367         header_delete(oh);
368     }
369
370     return h;
371 }
372
373 /* }}} */
374
375 hcache_t *mutt_hcache_open(const char *path, const char *folder)
376 {
377     hcache_t *h = p_new(hcache_t, 1);
378
379     h->folder = m_strdup(folder);
380     h->crc = generate_crc32();
381
382     if (m_strisempty(path)) {
383         p_delete(&h->folder);
384         p_delete(&h);
385         return NULL;
386     }
387
388     path = mutt_hcache_per_folder(path, folder);
389
390     {
391 #if defined(HAVE_QDBM)
392         int flags = VL_OWRITER | VL_OCREAT;
393         if (option(OPTHCACHECOMPRESS))
394             flags |= VL_OZCOMP;
395
396         h->db = vlopen(path, flags, VL_CMPLEX);
397 #elif defined(HAVE_GDBM)
398         int pagesize = atoi(HeaderCachePageSize) ?: 16384;
399
400         h->db = gdbm_open((char *) path, pagesize, GDBM_WRCREAT, 00600, NULL);
401 #endif
402     }
403
404     if (!h->db) {
405         p_delete(&h->folder);
406         p_delete(&h);
407     }
408     return h;
409 }
410
411 void mutt_hcache_close(hcache_t **db)
412 {
413     if (!*db)
414         return;
415
416 #if defined(HAVE_QDBM)
417     vlclose((*db)->db);
418 #elif defined(HAVE_GDBM)
419     gdbm_close((*db)->db);
420 #endif
421
422     p_delete(&(*db)->folder);
423     p_delete(db);
424 }
425
426 void *mutt_hcache_fetch(hcache_t *db, const char *filename,
427                         ssize_t (*keylen)(const char *fn))
428 {
429     char path[_POSIX_PATH_MAX];
430     char *data = NULL;
431
432     if (!db)
433         return NULL;
434
435     snprintf(path, sizeof(path), "%s%s", db->folder, filename);
436
437     {
438 #if defined(HAVE_QDBM)
439         int ksize = strlen(db->folder) + keylen(path + strlen(db->folder));
440         data  = vlget(db->db, path, ksize, NULL);
441 #elif defined(HAVE_GDBM)
442         datum k = { .dptr = path, .dsize = keylen(path) };
443
444         data = gdbm_fetch(db->db, k).dtpr;
445 #endif
446     }
447
448     if (data) {
449         unsigned crc = 0;
450
451         restore_int(data + sizeof(long), (int *)&crc);
452         if (crc != db->crc)
453             p_delete(&data);
454     }
455
456     return data;
457 }
458
459 int mutt_hcache_store(hcache_t *db, const char *filename, HEADER *header,
460                       long uid_validity, ssize_t (*keylen)(const char *fn))
461 {
462     char path[_POSIX_PATH_MAX];
463     buffer_t *data;
464     int ret;
465
466     if (!db)
467         return -1;
468
469     snprintf(path, sizeof(path), "%s%s", db->folder, filename);
470     data = mutt_hcache_dump(db, header, uid_validity);
471
472     {
473 #if defined(HAVE_QDBM)
474         int ksize = strlen(db->folder) + keylen(path + strlen(db->folder));
475
476         ret = vlput(db->db, path, ksize, data->data, data->len, VL_DOVER);
477 #elif defined(HAVE_GDBM)
478         datum k = { .dptr = path, .dsize = keylen(path) };
479         datum v = { .dptr = data->data, .dsize = data->len };
480
481         ret = gdbm_store(db->db, k, v, GDBM_REPLACE);
482 #endif
483     }
484
485     buffer_delete(&data);
486     return ret;
487 }
488
489 int mutt_hcache_delete(hcache_t *db, const char *filename,
490                        ssize_t (*keylen)(const char *fn))
491 {
492     char path[_POSIX_PATH_MAX];
493
494     if (!db)
495         return -1;
496
497     snprintf(path, sizeof(path), "%s%s", db->folder, filename);
498
499     {
500 #if defined(HAVE_QDBM)
501         int ksize = strlen(db->folder) + keylen(path + strlen(db->folder));
502         return vlout(db->db, path, ksize);
503 #elif defined(HAVE_GDBM)
504         datum k = { .dptr = path, .dsize = keylen(path) };
505         return gdbm_delete(db->db, k);
506 #endif
507     }
508 }
509
510 #endif /* USE_HCACHE */