#include <config.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <syslog.h>
#include <errno.h>
#include <ctype.h>
#include "acl.h"
#include "annotate.h"
#include "append.h"
#include "assert.h"
#include "charset.h"
#include "exitcodes.h"
#include "hash.h"
#include "imap_err.h"
#include "global.h"
#include "imapd.h"
#include "lsort.h"
#include "mailbox.h"
#include "map.h"
#include "message.h"
#include "parseaddr.h"
#include "search_engines.h"
#include "seen.h"
#include "strhash.h"
#include "stristr.h"
#include "util.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
#include "index.h"
#include "sync_log.h"
extern void printastring (const char *s);
static const char *index_base;
static unsigned long index_len;
static unsigned long index_dirty;
static const char *cache_base;
static unsigned long cache_len;
static unsigned long cache_end;
static unsigned long cache_dirty;
static ino_t index_ino;
static unsigned long start_offset;
static unsigned long record_size;
static unsigned lastnotrecent;
static time_t *flagreport;
static char *seenflag;
static time_t seen_last_change;
static int flagalloced = -1;
struct seen *seendb;
static char *seenuids;
typedef int index_sequenceproc_t(struct mailbox *mailbox, unsigned msgno,
void *rock);
static int index_forsequence(struct mailbox *mailbox, const char *sequence,
int usinguid,
index_sequenceproc_t *proc, void *rock,
int* fetchedsomething);
static int index_insequence(int num, char *sequence, int usinguid);
void index_fetchmsg(const char *msg_base, unsigned long msg_size,
int format, unsigned offset, unsigned size,
unsigned start_octet, unsigned octet_count,
struct protstream *pout);
static int index_fetchsection(const char *resp,
const char *msg_base, unsigned long msg_size,
int format, char *section,
const char *cacheitem, unsigned size,
unsigned start_octet, unsigned octet_count);
static void index_fetchfsection(const char *msg_base,
unsigned long msg_size,
int format, struct fieldlist *fsection,
const char *cacheitem,
unsigned start_octet, unsigned octet_count);
static char *index_readheader(const char *msg_base, unsigned long msg_size,
int format, unsigned offset, unsigned size);
static void index_pruneheader(char *buf, struct strlist *headers,
struct strlist *headers_not);
static void index_fetchheader(const char *msg_base, unsigned long msg_size,
int format, unsigned size,
struct strlist *headers,
struct strlist *headers_not);
static void index_fetchcacheheader(unsigned msgno, struct strlist *headers,
unsigned start_octet, unsigned octet_count);
static void index_listflags(struct mailbox *mailbox);
static void index_fetchflags(struct mailbox *mailbox, unsigned msgno,
bit32 system_flags, bit32 *user_flags,
time_t last_updated);
static index_sequenceproc_t index_fetchreply;
static index_sequenceproc_t index_storeseen;
static index_sequenceproc_t index_storeflag;
static int index_search_evaluate(struct mailbox *mailbox,
struct searchargs *searchargs,
unsigned msgno, struct mapfile *msgfile);
static int index_searchmsg(char *substr, comp_pat *pat,
struct mapfile *msgfile, int format,
int skipheader, const char *cacheitem);
static int index_searchheader(char *name, char *substr, comp_pat *pat,
struct mapfile *msgfile, int format,
int size);
static int index_searchcacheheader(unsigned msgno, char *name, char *substr,
comp_pat *pat);
static index_sequenceproc_t index_copysetup;
static int _index_search(unsigned **msgno_list, struct mailbox *mailbox,
struct searchargs *searchargs,
modseq_t *highestmodseq);
static void parse_cached_envelope(char *env, char *tokens[], int tokens_size);
static char *find_msgid(char *str, char **rem);
static char *get_localpart_addr(const char *header);
static char *index_extract_subject(const char *subj, size_t len, int *is_refwd);
static char *_index_extract_subject(char *s, int *is_refwd);
static void index_get_ids(MsgData *msgdata,
char *envtokens[], const char *headers);
static MsgData *index_msgdata_load(unsigned *msgno_list, int n,
struct sortcrit *sortcrit);
static void *index_sort_getnext(MsgData *node);
static void index_sort_setnext(MsgData *node, MsgData *next);
static int index_sort_compare(MsgData *md1, MsgData *md2,
struct sortcrit *call_data);
static void index_msgdata_free(MsgData *md);
static void *index_thread_getnext(Thread *thread);
static void index_thread_setnext(Thread *thread, Thread *next);
static int index_thread_compare(Thread *t1, Thread *t2,
struct sortcrit *call_data);
static void index_thread_orderedsubj(unsigned *msgno_list, int nmsg,
int usinguid);
static void index_thread_sort(Thread *root, struct sortcrit *sortcrit);
static void index_thread_print(Thread *threads, int usinguid);
static void index_thread_ref(unsigned *msgno_list, int nmsg, int usinguid);
static const struct thread_algorithm thread_algs[] = {
{ "ORDEREDSUBJECT", index_thread_orderedsubj },
{ "REFERENCES", index_thread_ref },
{ NULL, NULL }
};
void index_closemailbox(struct mailbox *mailbox)
{
if (seendb) {
index_checkseen(mailbox, 1, 0, imapd_exists);
seen_close(seendb);
seendb = 0;
}
if (index_len) {
if (index_dirty)
map_free(&index_base, &index_len);
if (cache_dirty)
map_free(&cache_base, &cache_len);
index_dirty = cache_dirty = index_len = cache_end = 0;
}
}
void index_newmailbox(struct mailbox *mailbox, int examine_mode)
{
mailbox->keepingseen = (mailbox->myrights & ACL_SEEN);
mailbox->examining = examine_mode;
index_listflags(mailbox);
imapd_exists = -1;
index_check(mailbox, 0, 1);
}
void index_operatemailbox(struct mailbox *mailbox)
{
index_dirty = cache_dirty = 0;
index_base = mailbox->index_base;
index_len = mailbox->index_len;
cache_base = mailbox->cache_base;
cache_len = mailbox->cache_len;
cache_end = mailbox->cache_size;
index_ino = mailbox->index_ino;
start_offset = mailbox->start_offset;
record_size = mailbox->record_size;
imapd_exists = mailbox->exists;
}
void index_check(struct mailbox *mailbox, int usinguid, int checkseen)
{
struct stat sbuf;
int newexists, oldexists, oldmsgno, msgno, nexpunge, i, r;
struct index_record record;
time_t last_read;
bit32 user_flags[MAX_USER_FLAGS/32];
oldexists = imapd_exists;
if (index_len) {
char fnamebuf[MAX_MAILBOX_PATH+1], *path;
path = (mailbox->mpath &&
(config_metapartition_files &
IMAP_ENUM_METAPARTITION_FILES_INDEX)) ?
mailbox->mpath : mailbox->path;
strlcpy(fnamebuf, path, sizeof(fnamebuf));
strlcat(fnamebuf, FNAME_INDEX, sizeof(fnamebuf));
if (stat(fnamebuf, &sbuf) != 0) {
if (errno == ENOENT) {
for(;imapd_exists > 0; imapd_exists--) {
prot_printf(imapd_out, "* 1 EXPUNGE\r\n");
}
mailbox->exists = 0;
imapd_exists = -1;
if (seendb) {
seen_close(seendb);
seendb = 0;
}
}
}
else if ((sbuf.st_ino != mailbox->index_ino) ||
(index_ino != mailbox->index_ino)) {
unsigned long olduidvalidity = mailbox->uidvalidity;
if (mailbox_open_index(mailbox)) {
fatal("failed to reopen index file", EC_IOERR);
}
if (olduidvalidity != mailbox->uidvalidity) {
oldexists = -1;
}
for (oldmsgno = msgno = 1; oldmsgno <= imapd_exists;
oldmsgno++, msgno++) {
if (msgno <= mailbox->exists) {
mailbox_read_index_record(mailbox, msgno, &record);
}
else {
record.uid = mailbox->last_uid+1;
}
nexpunge = 0;
while (oldmsgno<=imapd_exists && UID(oldmsgno) < record.uid) {
nexpunge++;
oldmsgno++;
}
if (nexpunge) {
memmove(flagreport+msgno, flagreport+msgno+nexpunge,
(oldexists-msgno-nexpunge+1)*sizeof(*flagreport));
memmove(seenflag+msgno, seenflag+msgno+nexpunge,
(oldexists-msgno-nexpunge+1)*sizeof(*seenflag));
oldexists -= nexpunge;
while (nexpunge--) {
prot_printf(imapd_out, "* %u EXPUNGE\r\n", msgno);
}
}
}
map_free(&index_base, &index_len);
map_free(&cache_base, &cache_len);
cache_end = 0;
index_dirty = cache_dirty = 0;
imapd_exists = -1;
}
else if (sbuf.st_mtime != mailbox->index_mtime
|| sbuf.st_size != mailbox->index_size) {
mailbox_read_index_header(mailbox);
}
}
index_ino = mailbox->index_ino;
start_offset = mailbox->start_offset;
record_size = mailbox->record_size;
newexists = mailbox->exists;
map_refresh(mailbox->index_fd, 0, &index_base, &index_len,
start_offset + newexists * record_size,
"index", mailbox->name);
index_dirty = 1;
if (fstat(mailbox->cache_fd, &sbuf) == -1) {
syslog(LOG_ERR, "IOERROR: stating cache file for %s: %m",
mailbox->name);
fatal("failed to stat cache file", EC_IOERR);
}
if (cache_end < sbuf.st_size) {
cache_end = sbuf.st_size;
map_refresh(mailbox->cache_fd, 0, &cache_base, &cache_len,
cache_end, "cache", mailbox->name);
cache_dirty = 1;
}
if (oldexists == -1 && mailbox->keepingseen) {
r = seen_open(mailbox, imapd_userid, SEEN_CREATE, &seendb);
if (!r) {
free(seenuids);
seenuids = NULL;
r = seen_lockread(seendb, &last_read, &mailbox->recentuid,
&seen_last_change, &seenuids);
if (r) seen_close(seendb);
}
if (r) {
seendb = 0;
prot_printf(imapd_out, "* OK (seen state failure) %s: %s\r\n",
error_message(IMAP_NO_CHECKPRESERVE), error_message(r));
syslog(LOG_ERR, "Could not open seen state for %s (%s)",
imapd_userid, error_message(r));
}
else {
*seenuids = '\0';
}
}
if (imapd_exists == -1) {
imapd_exists = newexists;
lastnotrecent = index_finduid(mailbox->recentuid);
imapd_exists = -1;
}
if (newexists != imapd_exists) {
if (newexists > flagalloced) {
flagalloced = newexists * 2;
flagreport = (time_t *)
xrealloc((char *)flagreport, (flagalloced+1) * sizeof(time_t));
seenflag = xrealloc(seenflag, flagalloced+1);
}
for (i = oldexists+1; i <= newexists; i++) {
flagreport[i] = LAST_UPDATED(i);
seenflag[i] = 0;
}
checkseen = 1;
imapd_exists = newexists;
prot_printf(imapd_out, "* %u EXISTS\r\n* %u RECENT\r\n", imapd_exists,
imapd_exists-lastnotrecent);
}
if (checkseen) index_checkseen(mailbox, checkseen >> 1, usinguid, oldexists);
else if (oldexists == -1) seen_unlock(seendb);
for (i = 1; i <= imapd_exists && seenflag[i]; i++);
if (i == imapd_exists + 1) mailbox->allseen = mailbox->last_uid;
if (oldexists == -1) {
if (imapd_exists && i <= imapd_exists) {
prot_printf(imapd_out, "* OK [UNSEEN %u] \r\n", i);
}
prot_printf(imapd_out, "* OK [UIDVALIDITY %lu] \r\n",
mailbox->uidvalidity);
prot_printf(imapd_out, "* OK [UIDNEXT %lu] \r\n",
mailbox->last_uid + 1);
if (mailbox->options & OPT_IMAP_CONDSTORE) {
prot_printf(imapd_out, "* OK [HIGHESTMODSEQ " MODSEQ_FMT "] \r\n",
mailbox->highestmodseq);
} else {
prot_printf(imapd_out, "* OK [NOMODSEQ] Sorry, modsequences have "
"not been enabled on this mailbox\r\n");
}
}
for (msgno = 1; msgno <= oldexists; msgno++) {
if (flagreport[msgno] < LAST_UPDATED(msgno)) {
for (i = 0; i < VECTOR_SIZE(user_flags); i++) {
user_flags[i] = USER_FLAGS(msgno, i);
}
index_fetchflags(mailbox, msgno, SYSTEM_FLAGS(msgno), user_flags,
LAST_UPDATED(msgno));
if ((mailbox->options & OPT_IMAP_CONDSTORE) &&
imapd_condstore_client) {
prot_printf(imapd_out, " MODSEQ (" MODSEQ_FMT ")", MODSEQ(msgno));
}
if (usinguid) prot_printf(imapd_out, " UID %u", UID(msgno));
prot_printf(imapd_out, ")\r\n");
}
}
}
#define SAVEGROW 200
void
index_checkseen(mailbox, quiet, usinguid, oldexists)
struct mailbox *mailbox;
int quiet;
int usinguid;
int oldexists;
{
int r;
time_t last_read;
unsigned last_uid;
char *newseenuids;
char *old, *new;
unsigned oldnext = 0, oldseen = 0;
unsigned newnext = 0, newseen = 0;
int neweof = 0;
unsigned msgno, uid, dirty = 0;
int i;
bit32 user_flags[MAX_USER_FLAGS/32];
char *saveseenuids, *save;
int savealloced;
unsigned start, newallseen, inrange, usecomma;
mailbox_notifyproc_t *updatenotifier;
int dosync = 0;
if (!mailbox->keepingseen || !seendb) return;
if (imapd_exists == 0) {
seen_unlock(seendb);
return;
}
r = seen_lockread(seendb, &last_read, &last_uid, &seen_last_change,
&newseenuids);
if (r) {
prot_printf(imapd_out, "* OK %s: %s\r\n",
error_message(IMAP_NO_CHECKSEEN), error_message(r));
seen_close(seendb);
return;
}
old = seenuids;
new = newseenuids;
while (cyrus_isdigit((int) *old)) oldnext = oldnext * 10 + *old++ - '0';
while (cyrus_isdigit((int) *new)) newnext = newnext * 10 + *new++ - '0';
for (msgno = 1; msgno <= imapd_exists; msgno++) {
uid = UID(msgno);
while (oldnext <= uid) {
if (*old != ':' && !oldseen && oldnext == uid) {
oldseen = 1;
break;
}
else {
oldseen = (*old == ':');
oldnext = 0;
if (!*old) oldnext = mailbox->last_uid+1;
else old++;
while (cyrus_isdigit((int) *old)) {
oldnext = oldnext * 10 + *old++ - '0';
}
oldnext += oldseen;
}
}
while (newnext <= uid) {
if (*new != ':' && !newseen && newnext == uid) {
newseen = 1;
break;
}
else {
newseen = (*new == ':');
newnext = 0;
if (!*new) {
newnext = mailbox->last_uid+1;
neweof++;
}
else new++;
while (cyrus_isdigit((int) *new)) {
newnext = newnext * 10 + *new++ - '0';
}
newnext += newseen;
}
}
if (oldseen != newseen) {
if (seenflag[msgno] != newseen) {
seenflag[msgno] = newseen;
if (!quiet && msgno <= oldexists && oldexists != -1) {
for (i = 0; i < VECTOR_SIZE(user_flags); i++) {
user_flags[i] = USER_FLAGS(msgno, i);
}
index_fetchflags(mailbox, msgno, SYSTEM_FLAGS(msgno),
user_flags, LAST_UPDATED(msgno));
if ((mailbox->options & OPT_IMAP_CONDSTORE) &&
imapd_condstore_client) {
prot_printf(imapd_out, " MODSEQ (" MODSEQ_FMT ")",
MODSEQ(msgno));
}
if (usinguid) {
prot_printf(imapd_out, " UID %u", UID(msgno));
}
prot_printf(imapd_out, ")\r\n");
}
}
}
else if (seenflag[msgno] != newseen) {
dirty++;
}
}
if (dirty) {
seen_last_change = time((time_t *)0);
dosync = 1;
}
if (!mailbox->examining && oldexists != imapd_exists) {
if (oldexists == -1) last_read = time((time_t *)0);
if (last_uid != mailbox->last_uid)
dosync = 1;
last_uid = mailbox->last_uid;
dirty++;
}
if (!dirty) {
seen_unlock(seendb);
free(seenuids);
seenuids = newseenuids;
if (!mailbox->allseen) {
for (msgno = 1; msgno <= imapd_exists; msgno++) {
if (!seenflag[msgno]) break;
}
#if TOIMSP
if (msgno == imapd_exists + 1) {
toimsp(mailbox->name, mailbox->uidvalidity,
"SEENsnn", imapd_userid, mailbox->last_uid,
seen_last_change, 0);
}
#endif
}
return;
}
start = 1;
inrange = 1;
newallseen = mailbox->last_uid;
usecomma = 0;
savealloced = SAVEGROW;
save = saveseenuids = xmalloc(savealloced);
*save = '\0';
for (msgno = 1; msgno <= imapd_exists; msgno++) {
uid = UID(msgno);
if (seenflag[msgno] != inrange) {
newallseen = 0;
if (inrange) {
if (start == uid-1) {
if (usecomma++) *save++ = ',';
sprintf(save, "%u", start);
save += strlen(save);
}
else if (uid > 1) {
if (usecomma++) *save++ = ',';
sprintf(save, "%u:", start);
save += strlen(save);
sprintf(save, "%u", uid-1);
save += strlen(save);
}
inrange = 0;
}
else {
start = uid;
inrange = 1;
}
}
if (save - saveseenuids > savealloced - 30) {
savealloced += SAVEGROW;
saveseenuids = xrealloc(saveseenuids, savealloced);
save = saveseenuids + strlen(saveseenuids);
}
}
uid = mailbox->last_uid;
while (newnext <= uid) {
if (*new != ':' && !newseen && newnext == uid) {
newseen = 1;
break;
}
else {
newseen = (*new == ':');
newnext = 0;
if (!*new) {
newnext = mailbox->last_uid+1;
neweof++;
}
else new++;
while (cyrus_isdigit((int) *new)) newnext = newnext * 10 + *new++ - '0';
newnext += newseen;
}
}
if (inrange) {
if (newseen && newnext > uid+1) {
uid = newnext-1;
}
else if (!neweof && !newseen && newnext == uid+1) {
if (*new == ':') {
new++;
newnext = 0;
while (cyrus_isdigit((int) *new)) newnext = newnext * 10 + *new++ - '0';
}
uid = newnext;
newseen++;
}
if (!start && uid > 1) start = 1;
if (usecomma++) *save++ = ',';
if (start && start != uid) {
sprintf(save, "%u:", start);
save += strlen(save);
}
sprintf(save, "%u", uid);
save += strlen(save);
if (!neweof && !newseen) {
if (usecomma++) *save++ = ',';
sprintf(save, "%u", newnext);
save += strlen(save);
}
}
else if (newseen && newnext > uid+1) {
if (usecomma++) *save++ = ',';
if (newnext > uid+2) {
sprintf(save, "%u:", uid+1);
save += strlen(save);
}
sprintf(save, "%u", newnext-1);
save += strlen(save);
}
else if (*new == ':') {
if (usecomma++) *save++ = ',';
sprintf(save, "%u", uid+1);
save += strlen(save);
}
else if (!neweof && !newseen) {
if (usecomma++) *save++ = ',';
sprintf(save, "%u", newnext);
save += strlen(save);
}
if (*new) {
if (save - saveseenuids + strlen(new) >= savealloced) {
savealloced += strlen(new);
saveseenuids = xrealloc(saveseenuids, savealloced);
save = saveseenuids + strlen(saveseenuids);
}
strcpy(save, usecomma ? new : new+1);
}
r = seen_write(seendb, last_read, last_uid, seen_last_change, saveseenuids);
seen_unlock(seendb);
free(seenuids);
if (r) {
prot_printf(imapd_out, "* OK %s: %s\r\n",
error_message(IMAP_NO_CHECKSEEN), error_message(r));
free(saveseenuids);
seenuids = newseenuids;
return;
}
if (!r && dosync) {
sync_log_seen(imapd_userid, mailbox->name);
}
#if TOIMSP
if (newallseen) {
toimsp(mailbox->name, mailbox->uidvalidity, "SEENsnn", imapd_userid,
mailbox->last_uid, seen_last_change, 0);
}
else if (mailbox->allseen == mailbox->last_uid) {
toimsp(mailbox->name, mailbox->uidvalidity, "SEENsnn", imapd_userid,
0, seen_last_change, 0);
}
#endif
free(newseenuids);
seenuids = saveseenuids;
updatenotifier = mailbox_get_updatenotifier();
if (updatenotifier) updatenotifier(mailbox);
}
int
index_fetch(struct mailbox* mailbox,
const char* sequence,
int usinguid,
struct fetchargs* fetchargs,
int* fetchedsomething)
{
*fetchedsomething = 0;
return index_forsequence(mailbox, sequence, usinguid, index_fetchreply,
(char *)fetchargs, fetchedsomething);
}
int
index_store(mailbox, sequence, usinguid, storeargs, flag, nflags)
struct mailbox *mailbox;
char *sequence;
int usinguid;
struct storeargs *storeargs;
char **flag;
int nflags;
{
int i, r, userflag, emptyflag;
int writeheader = 0;
int newflag[MAX_USER_FLAGS];
long myrights = mailbox->myrights;
if (!(mailbox->options & OPT_IMAP_CONDSTORE) &&
storeargs->operation != STORE_REPLACE &&
!storeargs->system_flags && !nflags) {
if (!storeargs->seen) return 0;
if (!(myrights & ACL_SEEN)) return IMAP_PERMISSION_DENIED;
storeargs->usinguid = usinguid;
index_forsequence(mailbox, sequence, usinguid,
index_storeseen, (char *)storeargs, NULL);
return 0;
}
mailbox_read_acl(mailbox, imapd_authstate);
myrights &= mailbox->myrights;
if ((storeargs->seen && !(myrights & ACL_SEEN)) ||
((storeargs->system_flags & FLAG_DELETED) &&
!(myrights & ACL_DELETEMSG)) ||
(((storeargs->system_flags & ~FLAG_DELETED) || nflags) &&
!(myrights & ACL_WRITE))) {
mailbox->myrights = myrights;
return IMAP_PERMISSION_DENIED;
}
for (userflag=0; userflag < VECTOR_SIZE(newflag); userflag++)
newflag[userflag] = 0;
for (i=0; i < nflags; i++) {
emptyflag = -1;
for (userflag = 0; userflag < VECTOR_SIZE(mailbox->flagname); userflag++) {
if (mailbox->flagname[userflag]) {
if (!strcasecmp(flag[i], mailbox->flagname[userflag]))
break;
}
else if (!newflag[userflag] && emptyflag == -1) {
emptyflag = userflag;
}
}
if (userflag == MAX_USER_FLAGS) {
if (emptyflag == -1) {
return IMAP_USERFLAG_EXHAUSTED;
}
newflag[emptyflag] = 1;
writeheader++;
}
}
if (writeheader) {
r = mailbox_lock_header(mailbox);
if (r) return r;
for (userflag=0; userflag < VECTOR_SIZE(newflag); userflag++)
newflag[userflag] = 0;
for (i=0; i < nflags; i++) {
emptyflag = -1;
for (userflag = 0; userflag < VECTOR_SIZE(newflag); userflag++) {
if (mailbox->flagname[userflag]) {
if (!strcasecmp(flag[i], mailbox->flagname[userflag]))
break;
}
else if (emptyflag == -1) {
emptyflag = userflag;
}
}
if (userflag == MAX_USER_FLAGS) {
if (emptyflag == -1) {
mailbox_unlock_header(mailbox);
mailbox->myrights = myrights;
for (userflag=0; userflag < VECTOR_SIZE(newflag); userflag++) {
if (newflag[userflag] && mailbox->flagname[userflag]) {
free(mailbox->flagname[userflag]);
mailbox->flagname[userflag] = 0;
}
}
index_listflags(mailbox);
return IMAP_USERFLAG_EXHAUSTED;
}
mailbox->flagname[emptyflag] = xstrdup(flag[i]);
}
}
index_listflags(mailbox);
r = mailbox_write_header(mailbox);
mailbox_unlock_header(mailbox);
mailbox->myrights = myrights;
if (r) return r;
}
mailbox->myrights = myrights;
for (i=0; i < nflags; i++) {
for (userflag = 0; userflag < VECTOR_SIZE(mailbox->flagname); userflag++) {
if (mailbox->flagname[userflag]) {
if (!strcasecmp(flag[i], mailbox->flagname[userflag]))
break;
}
}
assert(userflag != MAX_USER_FLAGS);
storeargs->user_flags[userflag/32] |= 1<<(userflag&31);
}
storeargs->update_time = time((time_t *)0);
storeargs->usinguid = usinguid;
r = mailbox_lock_index(mailbox);
if (r) return r;
r = index_forsequence(mailbox, sequence, usinguid,
index_storeflag, (char *)storeargs, NULL);
if (mailbox->dirty) {
if (mailbox->options & OPT_IMAP_CONDSTORE) {
mailbox->highestmodseq++;
}
mailbox_write_index_header(mailbox);
mailbox->dirty = 0;
}
mailbox_unlock_index(mailbox);
map_refresh(mailbox->index_fd, 0, &index_base, &index_len,
start_offset + imapd_exists * record_size,
"index", mailbox->name);
index_dirty = 1;
return r;
}
static int _index_search(unsigned **msgno_list, struct mailbox *mailbox,
struct searchargs *searchargs,
modseq_t *highestmodseq)
{
unsigned msgno;
struct mapfile msgfile;
int n = 0;
int listindex;
int listcount;
if (imapd_exists <= 0) return 0;
*msgno_list = (unsigned *) xmalloc(imapd_exists * sizeof(unsigned));
listcount = search_prefilter_messages(*msgno_list, mailbox, searchargs);
for (listindex = 0; listindex < listcount; listindex++) {
msgno = (*msgno_list)[listindex];
msgfile.base = 0;
msgfile.size = 0;
if (index_search_evaluate(mailbox, searchargs, msgno, &msgfile)) {
(*msgno_list)[n++] = msgno;
if (highestmodseq && (MODSEQ(msgno) > *highestmodseq)) {
*highestmodseq = MODSEQ(msgno);
}
}
if (msgfile.base) {
mailbox_unmap_message(mailbox, UID(msgno),
&msgfile.base, &msgfile.size);
}
}
if (!n && *msgno_list) {
free(*msgno_list);
*msgno_list = NULL;
}
return n;
}
int index_getuid(unsigned msgno) {
return UID(msgno);
}
int index_getuidsequence(struct mailbox *mailbox,
struct searchargs *searchargs,
unsigned **uid_list)
{
unsigned *msgno_list;
int i, n;
n = _index_search(&msgno_list, mailbox, searchargs, NULL);
if (n == 0) {
*uid_list = NULL;
return 0;
}
for (i = 0; i < n; i++) {
msgno_list[i] = UID(msgno_list[i]);
}
*uid_list = msgno_list;
return n;
}
int index_search(struct mailbox *mailbox, struct searchargs *searchargs,
int usinguid)
{
unsigned *msgno_list;
int i, n;
modseq_t highestmodseq = 0;
n = _index_search(&msgno_list, mailbox, searchargs,
searchargs->modseq ? &highestmodseq : NULL);
prot_printf(imapd_out, "* SEARCH");
for (i = 0; i < n; i++)
prot_printf(imapd_out, " %u",
usinguid ? UID(msgno_list[i]) : msgno_list[i]);
if (n) free(msgno_list);
if (highestmodseq) {
prot_printf(imapd_out, " (MODSEQ " MODSEQ_FMT ")", highestmodseq);
}
prot_printf(imapd_out, "\r\n");
return n;
}
int index_sort(struct mailbox *mailbox, struct sortcrit *sortcrit,
struct searchargs *searchargs, int usinguid)
{
unsigned *msgno_list;
MsgData *msgdata = NULL, *freeme = NULL;
int nmsg;
clock_t start;
modseq_t highestmodseq = 0;
int i, modseq = 0;
if(CONFIG_TIMING_VERBOSE)
start = clock();
if (searchargs->modseq) modseq = 1;
else {
for (i = 0; sortcrit[i].key != SORT_SEQUENCE; i++) {
if (sortcrit[i].key == SORT_MODSEQ) {
modseq = 1;
break;
}
}
}
nmsg = _index_search(&msgno_list, mailbox, searchargs,
modseq ? &highestmodseq : NULL);
prot_printf(imapd_out, "* SORT");
if (nmsg) {
freeme = msgdata = index_msgdata_load(msgno_list, nmsg, sortcrit);
free(msgno_list);
msgdata = lsort(msgdata,
(void * (*)(void*)) index_sort_getnext,
(void (*)(void*,void*)) index_sort_setnext,
(int (*)(void*,void*,void*)) index_sort_compare,
sortcrit);
while (msgdata) {
prot_printf(imapd_out, " %u",
usinguid ? UID(msgdata->msgno) : msgdata->msgno);
index_msgdata_free(msgdata);
msgdata = msgdata->next;
}
free(freeme);
}
if (highestmodseq) {
prot_printf(imapd_out, " (MODSEQ " MODSEQ_FMT ")", highestmodseq);
}
prot_printf(imapd_out, "\r\n");
if (CONFIG_TIMING_VERBOSE) {
int len;
char *key_names[] = { "SEQUENCE", "ARRIVAL", "CC", "DATE", "FROM",
"SIZE", "SUBJECT", "TO", "ANNOTATION", "MODSEQ" };
char buf[1024] = "";
while (sortcrit->key && sortcrit->key < VECTOR_SIZE(key_names)) {
if (sortcrit->flags & SORT_REVERSE)
strlcat(buf, "REVERSE ", sizeof(buf));
strlcat(buf, key_names[sortcrit->key], sizeof(buf));
switch (sortcrit->key) {
case SORT_ANNOTATION:
len = strlen(buf);
snprintf(buf + len, sizeof(buf) - len,
" \"%s\" \"%s\"",
sortcrit->args.annot.entry, sortcrit->args.annot.attrib);
break;
}
if ((++sortcrit)->key) strlcat(buf, " ", sizeof(buf));
}
syslog(LOG_DEBUG, "SORT (%s) processing time: %d msg in %f sec",
buf, nmsg, (clock() - start) / (double) CLOCKS_PER_SEC);
}
return nmsg;
}
int index_thread(struct mailbox *mailbox, int algorithm,
struct searchargs *searchargs, int usinguid)
{
unsigned *msgno_list;
int nmsg;
clock_t start;
modseq_t highestmodseq = 0;
if(CONFIG_TIMING_VERBOSE)
start = clock();
nmsg = _index_search(&msgno_list, mailbox, searchargs,
searchargs->modseq ? &highestmodseq : NULL);
if (nmsg) {
(*thread_algs[algorithm].threader)(msgno_list, nmsg, usinguid);
free(msgno_list);
if (highestmodseq) {
prot_printf(imapd_out, " (MODSEQ " MODSEQ_FMT ")", highestmodseq);
}
}
else
index_thread_print(NULL, usinguid);
prot_printf(imapd_out, "\r\n");
if (CONFIG_TIMING_VERBOSE) {
syslog(LOG_DEBUG, "THREAD %s processing time: %d msg in %f sec",
thread_algs[algorithm].alg_name, nmsg,
(clock() - start) / (double) CLOCKS_PER_SEC);
}
return nmsg;
}
int
index_copy(struct mailbox *mailbox,
char *sequence,
int usinguid,
char *name,
char **copyuidp,
int nolink)
{
static struct copyargs copyargs;
int i;
unsigned long totalsize = 0;
int r;
struct appendstate append_mailbox;
char *copyuid;
int copyuid_len, copyuid_size;
int sepchar;
unsigned long uidvalidity;
unsigned long startuid, num;
long docopyuid;
int haveseen = 0;
*copyuidp = NULL;
copyargs.nummsg = 0;
index_forsequence(mailbox, sequence, usinguid, index_copysetup,
(char *)©args, NULL);
if (copyargs.nummsg == 0) return IMAP_NO_NOSUCHMSG;
for (i = 0; i < copyargs.nummsg; i++) {
totalsize += copyargs.copymsg[i].size;
haveseen |= copyargs.copymsg[i].seen;
}
r = append_setup(&append_mailbox, name, MAILBOX_FORMAT_NORMAL,
imapd_userid, imapd_authstate, ACL_INSERT, totalsize);
if (r) return r;
docopyuid = (append_mailbox.m.myrights & ACL_READ);
r = append_copy(mailbox, &append_mailbox, copyargs.nummsg,
copyargs.copymsg, nolink);
if (!r) append_commit(&append_mailbox, totalsize,
&uidvalidity, &startuid, &num);
if (!r) {
sync_log_mailbox_double(mailbox->name, name);
if (haveseen) sync_log_seen(imapd_userid, name);
}
if (!r && docopyuid) {
copyuid_size = 1024;
copyuid = xmalloc(copyuid_size);
snprintf(copyuid, copyuid_size, "%lu", uidvalidity);
copyuid_len = strlen(copyuid);
sepchar = ' ';
for (i = 0; i < copyargs.nummsg; i++) {
if (copyuid_size < copyuid_len + 50) {
copyuid_size += 1024;
copyuid = xrealloc(copyuid, copyuid_size);
}
snprintf(copyuid+copyuid_len, copyuid_size-copyuid_len,
"%c%lu", sepchar, copyargs.copymsg[i].uid);
copyuid_len += strlen(copyuid+copyuid_len);
if (i+1 < copyargs.nummsg &&
copyargs.copymsg[i+1].uid == copyargs.copymsg[i].uid + 1) {
do {
i++;
} while (i+1 < copyargs.nummsg &&
copyargs.copymsg[i+1].uid == copyargs.copymsg[i].uid + 1);
snprintf(copyuid+copyuid_len, copyuid_size-copyuid_len, ":%lu",
copyargs.copymsg[i].uid);
copyuid_len += strlen(copyuid+copyuid_len);
}
sepchar = ',';
}
if (num == 1) {
snprintf(copyuid+copyuid_len, copyuid_size-copyuid_len, " %lu",
startuid);
} else {
snprintf(copyuid+copyuid_len, copyuid_size-copyuid_len, " %lu:%lu",
startuid, startuid + num - 1);
}
*copyuidp = copyuid;
}
return r;
}
static int index_appendremote(struct mailbox *mailbox,
unsigned msgno, void *rock)
{
struct protstream *pout = (struct protstream *) rock;
const char *msg_base = 0;
unsigned long msg_size = 0;
bit32 system_flags;
bit32 user_flags[MAX_USER_FLAGS/32];
unsigned flag;
bit32 flagmask = 0;
char datebuf[30];
char sepchar = '(';
if (mailbox_map_message(mailbox, UID(msgno), &msg_base, &msg_size)) {
return IMAP_NO_MSGGONE;
}
prot_printf(pout, " ");
system_flags = SYSTEM_FLAGS(msgno);
if (system_flags & FLAG_ANSWERED) {
prot_printf(pout, "%c\\Answered", sepchar);
sepchar = ' ';
}
if (system_flags & FLAG_FLAGGED) {
prot_printf(pout, "%c\\Flagged", sepchar);
sepchar = ' ';
}
if (system_flags & FLAG_DRAFT) {
prot_printf(pout, "%c\\Draft", sepchar);
sepchar = ' ';
}
if (system_flags & FLAG_DELETED) {
prot_printf(pout, "%c\\Deleted", sepchar);
sepchar = ' ';
}
if (seenflag[msgno]) {
prot_printf(pout, "%c\\Seen", sepchar);
sepchar = ' ';
}
for (flag = 0; flag < VECTOR_SIZE(user_flags); flag++) {
user_flags[flag] = USER_FLAGS(msgno, flag);
}
for (flag = 0; flag < VECTOR_SIZE(mailbox->flagname); flag++) {
if ((flag & 31) == 0) {
flagmask = user_flags[flag/32];
}
if (mailbox->flagname[flag] && (flagmask & (1<<(flag & 31)))) {
prot_printf(pout, "%c%s", sepchar, mailbox->flagname[flag]);
sepchar = ' ';
}
}
cyrus_ctime(INTERNALDATE(msgno), datebuf);
prot_printf(pout, ") \"%s\" ", datebuf);
index_fetchmsg(msg_base, msg_size, mailbox->format, 0, SIZE(msgno),
0, 0, pout);
if (msg_base) {
mailbox_unmap_message(mailbox, UID(msgno), &msg_base, &msg_size);
}
return 0;
}
int index_copy_remote(struct mailbox *mailbox, char *sequence,
int usinguid, struct protstream *pout)
{
return index_forsequence(mailbox, sequence, usinguid, index_appendremote,
(void *) pout, NULL);
}
int
index_status(mailbox, name, statusitems)
struct mailbox *mailbox;
char *name;
int statusitems;
{
int r;
struct seen *status_seendb;
time_t last_read, last_change = 0;
unsigned last_uid;
char *last_seenuids;
int num_recent = 0;
int num_unseen = 0;
int sepchar;
if (mailbox->exists != 0 &&
(statusitems &
(STATUS_RECENT | STATUS_UNSEEN))) {
r = seen_open(mailbox, imapd_userid, SEEN_CREATE, &status_seendb);
if (r) return r;
r = seen_lockread(status_seendb, &last_read, &last_uid,
&last_change, &last_seenuids);
seen_close(status_seendb);
if (r) return r;
if (statusitems & (STATUS_RECENT | STATUS_UNSEEN)) {
const char *base;
unsigned long len = 0;
int msg;
unsigned uid;
map_refresh(mailbox->index_fd, 0, &base, &len,
mailbox->start_offset +
mailbox->exists * mailbox->record_size,
"index", mailbox->name);
for (msg = 0; msg < mailbox->exists; msg++) {
uid = ntohl(*((bit32 *)(base + mailbox->start_offset +
msg * mailbox->record_size +
OFFSET_UID)));
if (uid > last_uid) num_recent++;
if ((statusitems & STATUS_UNSEEN) &&
!index_insequence(uid, last_seenuids, 0)) num_unseen++;
}
map_free(&base, &len);
free(last_seenuids);
}
}
prot_printf(imapd_out, "* STATUS ");
printastring(name);
prot_printf(imapd_out, " ");
sepchar = '(';
if (statusitems & STATUS_MESSAGES) {
prot_printf(imapd_out, "%cMESSAGES %lu", sepchar, mailbox->exists);
sepchar = ' ';
}
if (statusitems & STATUS_RECENT) {
prot_printf(imapd_out, "%cRECENT %u", sepchar, num_recent);
sepchar = ' ';
}
if (statusitems & STATUS_UIDNEXT) {
prot_printf(imapd_out, "%cUIDNEXT %lu", sepchar, mailbox->last_uid+1);
sepchar = ' ';
}
if (statusitems & STATUS_UIDVALIDITY) {
prot_printf(imapd_out, "%cUIDVALIDITY %lu", sepchar,
mailbox->uidvalidity);
sepchar = ' ';
}
if (statusitems & STATUS_UNSEEN) {
prot_printf(imapd_out, "%cUNSEEN %u", sepchar, num_unseen);
sepchar = ' ';
}
if (statusitems & STATUS_HIGHESTMODSEQ) {
prot_printf(imapd_out, "%cHIGHESTMODSEQ " MODSEQ_FMT, sepchar,
(mailbox->options & OPT_IMAP_CONDSTORE) ?
mailbox->highestmodseq : 0);
sepchar = ' ';
}
prot_printf(imapd_out, ")\r\n");
return 0;
}
int
index_getuids(mailbox, lowuid)
struct mailbox *mailbox __attribute__((unused));
unsigned lowuid;
{
int msgno;
unsigned firstuid = 0, lastuid = 0;
prot_printf(imapd_out, "* GETUIDS");
for (msgno = 1; msgno <= imapd_exists; msgno++) {
if (firstuid == 0) {
if (UID(msgno) >= lowuid) {
prot_printf(imapd_out, " %u %u", msgno, UID(msgno));
firstuid = lastuid = UID(msgno);
}
}
else {
if (UID(msgno) != ++lastuid) {
if (lastuid-1 != firstuid) {
prot_printf(imapd_out, ":%u", lastuid-1);
}
firstuid = lastuid = UID(msgno);
prot_printf(imapd_out, ",%u", firstuid);
}
}
}
if (lastuid != firstuid) {
prot_printf(imapd_out, ":%u", lastuid);
}
prot_printf(imapd_out, "\r\n");
return 0;
}
int
index_getstate(mailbox)
struct mailbox *mailbox;
{
prot_printf(imapd_out, "* XSTATE %lu %lu\r\n", mailbox->index_mtime,
seen_last_change);
return 0;
}
#if 0
int
index_checkstate(mailbox, indexdate, seendate)
struct mailbox *mailbox;
unsigned indexdate;
unsigned seendate;
{
int r;
int msgno;
unsigned int startmsgno = 0;
int sepchar = ' ';
if (imapd_exists < 1) {
prot_printf(imapd_out, "* XCHECKSTATE\r\n");
return 0;
}
if (seendate != seen_last_change) {
if (imapd_exists == 1) {
prot_printf(imapd_out,
"* XCHECKSTATE %u\r\n", UID(1));
}
else {
prot_printf(imapd_out,
"* XCHECKSTATE %u:%u\r\n", UID(1), UID(imapd_exists));
}
return 0;
}
prot_printf(imapd_out, "* XCHECKSTATE");
for (msgno = 1; msgno <= imapd_exists; msgno++) {
if (LAST_UPDATED(msgno) >= indexdate) {
if (startmsgno == 0) {
prot_printf(imapd_out, "%c%u", sepchar, UID(msgno));
sepchar = ',';
startmsgno = msgno;
}
}
else {
if (startmsgno != 0 && startmsgno < msgno - 1) {
prot_printf(imapd_out, ":%u", UID(msgno-1));
}
startmsgno = 0;
}
}
if (startmsgno != 0 && startmsgno < imapd_exists) {
prot_printf(imapd_out, ":%u", UID(imapd_exists));
}
prot_printf(imapd_out, "\r\n");
return 0;
}
#endif
int
index_finduid(uid)
unsigned uid;
{
int low=1, high=imapd_exists, mid;
unsigned miduid;
while (low <= high) {
mid = (high - low)/2 + low;
miduid = UID(mid);
if (miduid == uid) {
return mid;
}
else if (miduid > uid) {
high = mid - 1;
}
else {
low = mid + 1;
}
}
return high;
}
int index_expungeuidlist(struct mailbox *mailbox __attribute__((unused)),
void *rock, char *indexbuf,
int expunge_flags __attribute__((unused)))
{
char *sequence = (char *)rock;
unsigned uid = ntohl(*((bit32 *)(indexbuf+OFFSET_UID)));
if (!(ntohl(*((bit32 *)(indexbuf+OFFSET_SYSTEM_FLAGS))) & FLAG_DELETED))
return 0;
return index_insequence(uid, sequence, 1);
}
static int
index_forsequence(struct mailbox* mailbox,
const char* sequence,
int usinguid,
index_sequenceproc_t proc,
void* rock,
int* fetchedsomething)
{
unsigned i, start = 0, end;
int r, result = 0;
if (! imapd_exists) {
return 0;
}
for (;;) {
if (cyrus_isdigit((int) *sequence)) {
start = start*10 + *sequence - '0';
}
else if (*sequence == '*') {
start = usinguid ? UID(imapd_exists) : imapd_exists;
}
else if (*sequence == ':') {
end = 0;
sequence++;
while (cyrus_isdigit((int) *sequence)) {
end = end*10 + *sequence++ - '0';
}
if (*sequence == '*') {
sequence++;
end = usinguid ? UID(imapd_exists) : imapd_exists;
}
if (start > end) {
i = end;
end = start;
start = i;
}
if (usinguid) {
i = index_finduid(start);
if (!i || start != UID(i)) i++;
start = i;
end = index_finduid(end);
}
if (start < 1) start = 1;
if (end > imapd_exists) end = imapd_exists;
for (i = start; i <= end; i++) {
if (fetchedsomething) *fetchedsomething = 1;
r = (*proc)(mailbox, i, rock);
if (r && !result) result = r;
}
start = 0;
if (!*sequence) return result;
}
else {
if (start && usinguid) {
i = index_finduid(start);
if (!i || start != UID(i)) i = 0;
start = i;
}
if (start > 0 && start <= imapd_exists) {
if (fetchedsomething) *fetchedsomething = 1;
r = (*proc)(mailbox, start, rock);
if (r && !result) result = r;
}
start = 0;
if (!*sequence) return result;
}
sequence++;
}
}
static int
index_insequence(num, sequence, usinguid)
int num;
char *sequence;
int usinguid;
{
unsigned i, start = 0, end;
for (;;) {
if (cyrus_isdigit((int) *sequence)) {
start = start*10 + *sequence - '0';
}
else if (*sequence == '*') {
sequence++;
start = usinguid ? UID(imapd_exists) : imapd_exists;
}
else if (*sequence == ':') {
end = 0;
sequence++;
while (cyrus_isdigit((int) *sequence)) {
end = end*10 + *sequence++ - '0';
}
if (*sequence == '*') {
sequence++;
end = usinguid ? UID(imapd_exists) : imapd_exists;
}
if (start > end) {
i = end;
end = start;
start = i;
}
if (num >= start && num <= end) return 1;
start = 0;
if (!*sequence) return 0;
}
else {
if (num == start) return 1;
start = 0;
if (!*sequence) return 0;
}
sequence++;
}
}
void
index_fetchmsg(msg_base, msg_size, format, offset, size,
start_octet, octet_count, pout)
const char *msg_base;
unsigned long msg_size;
int format __attribute__((unused));
unsigned offset;
unsigned size;
unsigned start_octet;
unsigned octet_count;
struct protstream *pout;
{
int n;
if (!msg_base) {
prot_printf(pout, "NIL");
return;
}
if (octet_count) {
if (size <= start_octet) {
size = 0;
}
else {
size -= start_octet;
}
if (size > octet_count) size = octet_count;
}
if (size == 0) {
prot_printf(pout, "\"\"");
return;
}
offset += start_octet;
n = size;
if (offset + size > msg_size) {
n = msg_size - offset;
}
if (memchr(msg_base + offset, 0, n)) {
prot_printf(pout, "~{%u}\r\n", size);
} else {
prot_printf(pout, "{%u}\r\n", size);
}
prot_write(pout, msg_base + offset, n);
while (n++ < size) {
prot_putc(' ', pout);
}
}
static int index_fetchsection(const char *resp,
const char *msg_base, unsigned long msg_size,
int format, char *section,
const char *cacheitem, unsigned size,
unsigned start_octet, unsigned octet_count)
{
char *p;
int skip = 0;
int fetchmime = 0;
unsigned offset = 0;
char *decbuf = NULL;
cacheitem += CACHE_ITEM_SIZE_SKIP;
p = section;
if (*p == ']') {
if (strstr(resp, "BINARY.SIZE")) {
prot_printf(imapd_out, "%s%u", resp, size);
} else {
prot_printf(imapd_out, "%s", resp);
index_fetchmsg(msg_base, msg_size, format, 0, size,
start_octet, octet_count, imapd_out);
}
return 0;
}
while (*p != ']' && *p != 'M') {
skip = 0;
while (cyrus_isdigit((int) *p)) {
skip = skip * 10 + *p++ - '0';
}
if (*p == '.') p++;
if (skip >= CACHE_ITEM_BIT32(cacheitem)) goto badpart;
if (!skip) {
switch (*p) {
case 'H':
p += 6;
fetchmime++;
break;
case 'T':
p += 4;
break;
default:
fetchmime++;
break;
}
}
if (*p != ']' && *p != 'M') {
cacheitem +=
CACHE_ITEM_BIT32(cacheitem) * 5 * 4 + CACHE_ITEM_SIZE_SKIP;
while (--skip) {
if (CACHE_ITEM_BIT32(cacheitem) > 0) {
skip += CACHE_ITEM_BIT32(cacheitem)-1;
cacheitem += CACHE_ITEM_BIT32(cacheitem) * 5 * 4;
}
cacheitem += CACHE_ITEM_SIZE_SKIP;
}
}
}
if (*p == 'M') fetchmime++;
cacheitem += skip * 5 * 4 + CACHE_ITEM_SIZE_SKIP + (fetchmime ? 0 : 2 * 4);
if (CACHE_ITEM_BIT32(cacheitem + CACHE_ITEM_SIZE_SKIP) == -1) goto badpart;
offset = CACHE_ITEM_BIT32(cacheitem);
size = CACHE_ITEM_BIT32(cacheitem + CACHE_ITEM_SIZE_SKIP);
if (msg_base && (p = strstr(resp, "BINARY"))) {
int encoding = CACHE_ITEM_BIT32(cacheitem + 2 * 4) & 0xff;
msg_base = charset_decode_mimebody(msg_base + offset, size, encoding,
&decbuf, 0, (int *) &size);
if (!msg_base) {
if (decbuf) free(decbuf);
return IMAP_NO_UNKNOWN_CTE;
}
else if (p[6] == '.') {
prot_printf(imapd_out, "%s%u", resp, size);
if (decbuf) free(decbuf);
return 0;
}
else {
msg_size = size;
offset = 0;
}
}
prot_printf(imapd_out, "%s", resp);
index_fetchmsg(msg_base, msg_size, format, offset, size,
start_octet, octet_count, imapd_out);
if (decbuf) free(decbuf);
return 0;
badpart:
if (strstr(resp, "BINARY.SIZE"))
prot_printf(imapd_out, "%s0", resp);
else
prot_printf(imapd_out, "%sNIL", resp);
return 0;
}
static void index_fetchfsection(const char *msg_base,
unsigned long msg_size,
int format,
struct fieldlist *fsection,
const char *cacheitem,
unsigned start_octet, unsigned octet_count)
{
char *p;
int skip = 0;
int fields_not = 0;
unsigned crlf_start = 0;
unsigned crlf_size = 2;
char *buf;
unsigned size;
if (!msg_base) {
prot_printf(imapd_out, "\"\"");
return;
}
cacheitem += CACHE_ITEM_SIZE_SKIP;
p = fsection->section;
while (*p != 'H') {
skip = 0;
while (cyrus_isdigit((int) *p)) {
skip = skip * 10 + *p++ - '0';
}
if (*p == '.') p++;
if (skip >= CACHE_ITEM_BIT32(cacheitem)) goto badpart;
cacheitem += CACHE_ITEM_BIT32(cacheitem) * 5 * 4 + CACHE_ITEM_SIZE_SKIP;
while (--skip) {
if (CACHE_ITEM_BIT32(cacheitem) > 0) {
skip += CACHE_ITEM_BIT32(cacheitem)-1;
cacheitem += CACHE_ITEM_BIT32(cacheitem) * 5 * 4;
}
cacheitem += CACHE_ITEM_SIZE_SKIP;
}
}
if (0 == CACHE_ITEM_BIT32(cacheitem)) goto badpart;
cacheitem += 4;
if (CACHE_ITEM_BIT32(cacheitem+CACHE_ITEM_SIZE_SKIP) == -1) goto badpart;
if (p[13]) fields_not++;
buf = index_readheader(msg_base, msg_size, format,
CACHE_ITEM_BIT32(cacheitem),
CACHE_ITEM_BIT32(cacheitem+CACHE_ITEM_SIZE_SKIP));
if (fields_not) {
index_pruneheader(buf, 0, fsection->fields);
}
else {
index_pruneheader(buf, fsection->fields, 0);
}
size = strlen(buf);
if (octet_count) {
if (size <= start_octet) {
crlf_start = start_octet - size;
size = 0;
start_octet = 0;
if (crlf_size <= crlf_start) {
crlf_size = 0;
}
else {
crlf_size -= crlf_start;
}
}
else {
size -= start_octet;
}
if (size > octet_count) {
size = octet_count;
crlf_size = 0;
}
else if (size + crlf_size > octet_count) {
crlf_size = octet_count - size;
}
}
if (size + crlf_size == 0) {
prot_printf(imapd_out, "\"\"");
return;
}
prot_printf(imapd_out, "{%u}\r\n", size + crlf_size);
prot_write(imapd_out, buf + start_octet, size);
prot_write(imapd_out, "\r\n" + crlf_start, crlf_size);
return;
badpart:
prot_printf(imapd_out, "NIL");
}
static char *
index_readheader(msg_base, msg_size, format, offset, size)
const char *msg_base;
unsigned long msg_size;
int format __attribute__((unused));
unsigned offset;
unsigned size;
{
static char *buf;
static int bufsize;
if (offset + size > msg_size) {
if (offset < msg_size) {
size = msg_size - offset;
}
else {
size = 0;
}
}
if (bufsize < size+2) {
bufsize = size+100;
buf = xrealloc(buf, bufsize);
}
msg_base += offset;
memcpy(buf, msg_base, size);
buf[size] = '\0';
return buf;
}
static void
index_pruneheader(char *buf, struct strlist *headers,
struct strlist *headers_not)
{
char *p, *colon, *nextheader;
int goodheader;
char *endlastgood = buf;
struct strlist *l;
p = buf;
while (*p && *p != '\r') {
colon = strchr(p, ':');
if (colon && headers_not) {
goodheader = 1;
for (l = headers_not; l; l = l->next) {
if (colon - p == strlen(l->s) &&
!strncasecmp(p, l->s, colon - p)) {
goodheader = 0;
break;
}
}
} else {
goodheader = 0;
}
if (colon) {
for (l = headers; l; l = l->next) {
if (colon - p == strlen(l->s) &&
!strncasecmp(p, l->s, colon - p)) {
goodheader = 1;
break;
}
}
}
nextheader = p;
do {
nextheader = strchr(nextheader, '\n');
if (nextheader) nextheader++;
else nextheader = p + strlen(p);
} while (*nextheader == ' ' || *nextheader == '\t');
if (goodheader) {
if (endlastgood != p) {
memmove(endlastgood, p, strlen(p) + 1);
nextheader -= p - endlastgood;
}
endlastgood = nextheader;
}
p = nextheader;
}
*endlastgood = '\0';
}
static void
index_fetchheader(msg_base, msg_size, format, size, headers, headers_not)
const char *msg_base;
unsigned long msg_size;
int format;
unsigned size;
struct strlist *headers;
struct strlist *headers_not;
{
char *buf;
if (!msg_base) {
prot_printf(imapd_out, "\"\"");
return;
}
buf = index_readheader(msg_base, msg_size, format, 0, size);
index_pruneheader(buf, headers, headers_not);
size = strlen(buf);
prot_printf(imapd_out, "{%u}\r\n%s\r\n", size+2, buf);
}
static void
index_fetchcacheheader(unsigned msgno, struct strlist *headers,
unsigned start_octet, unsigned octet_count)
{
static char *buf;
static int bufsize;
const char *cacheitem;
unsigned size;
unsigned crlf_start = 0;
unsigned crlf_size = 2;
cacheitem = cache_base + CACHE_OFFSET(msgno);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
size = CACHE_ITEM_LEN(cacheitem);
if (bufsize < size+2) {
bufsize = size+100;
buf = xrealloc(buf, bufsize);
}
memcpy(buf, cacheitem + CACHE_ITEM_SIZE_SKIP, size);
buf[size] = '\0';
index_pruneheader(buf, headers, 0);
size = strlen(buf);
if (octet_count) {
if (size <= start_octet) {
crlf_start = start_octet - size;
size = 0;
start_octet = 0;
if (crlf_size <= crlf_start) {
crlf_size = 0;
}
else {
crlf_size -= crlf_start;
}
}
else {
size -= start_octet;
}
if (size > octet_count) {
size = octet_count;
crlf_size = 0;
}
else if (size + crlf_size > octet_count) {
crlf_size = octet_count - size;
}
}
if (size + crlf_size == 0) {
prot_printf(imapd_out, "\"\"");
}
else {
prot_printf(imapd_out, "{%u}\r\n", size + crlf_size);
prot_write(imapd_out, buf + start_octet, size);
prot_write(imapd_out, "\r\n" + crlf_start, crlf_size);
}
}
static void index_listflags(struct mailbox *mailbox)
{
int i;
int cancreate = 0;
char sepchar = '(';
prot_printf(imapd_out, "* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen");
for (i = 0; i < VECTOR_SIZE(mailbox->flagname); i++) {
if (mailbox->flagname[i]) {
prot_printf(imapd_out, " %s", mailbox->flagname[i]);
}
else cancreate++;
}
prot_printf(imapd_out, ")\r\n* OK [PERMANENTFLAGS ");
if (!mailbox->examining) {
if (mailbox->myrights & ACL_WRITE) {
prot_printf(imapd_out, "%c\\Answered \\Flagged \\Draft", sepchar);
sepchar = ' ';
}
if (mailbox->myrights & ACL_DELETEMSG) {
prot_printf(imapd_out, "%c\\Deleted", sepchar);
sepchar = ' ';
}
if (mailbox->myrights & ACL_SEEN) {
prot_printf(imapd_out, "%c\\Seen", sepchar);
sepchar = ' ';
}
if (mailbox->myrights & ACL_WRITE) {
for (i = 0; i < VECTOR_SIZE(mailbox->flagname); i++) {
if (mailbox->flagname[i]) {
prot_printf(imapd_out, " %s", mailbox->flagname[i]);
}
}
if (cancreate) {
prot_printf(imapd_out, " \\*");
}
}
}
if (sepchar == '(') prot_printf(imapd_out, "(");
prot_printf(imapd_out, ")] \r\n");
}
static void index_fetchflags(struct mailbox *mailbox,
unsigned msgno,
bit32 system_flags,
bit32 user_flags[MAX_USER_FLAGS/32],
time_t last_updated)
{
int sepchar = '(';
unsigned flag;
bit32 flagmask = 0;
for (flag = 0; flag < VECTOR_SIZE(mailbox->flagname); flag++) {
if ((flag & 31) == 0) {
flagmask = user_flags[flag/32];
}
if (!mailbox->flagname[flag] && (flagmask & (1<<(flag & 31)))) {
mailbox_read_header(mailbox);
index_listflags(mailbox);
break;
}
}
prot_printf(imapd_out, "* %u FETCH (FLAGS ", msgno);
if (msgno > lastnotrecent) {
prot_printf(imapd_out, "%c\\Recent", sepchar);
sepchar = ' ';
}
if (system_flags & FLAG_ANSWERED) {
prot_printf(imapd_out, "%c\\Answered", sepchar);
sepchar = ' ';
}
if (system_flags & FLAG_FLAGGED) {
prot_printf(imapd_out, "%c\\Flagged", sepchar);
sepchar = ' ';
}
if (system_flags & FLAG_DRAFT) {
prot_printf(imapd_out, "%c\\Draft", sepchar);
sepchar = ' ';
}
if (system_flags & FLAG_DELETED) {
prot_printf(imapd_out, "%c\\Deleted", sepchar);
sepchar = ' ';
}
if (seenflag[msgno]) {
prot_printf(imapd_out, "%c\\Seen", sepchar);
sepchar = ' ';
}
for (flag = 0; flag < VECTOR_SIZE(mailbox->flagname); flag++) {
if ((flag & 31) == 0) {
flagmask = user_flags[flag/32];
}
if (mailbox->flagname[flag] && (flagmask & (1<<(flag & 31)))) {
prot_printf(imapd_out, "%c%s", sepchar, mailbox->flagname[flag]);
sepchar = ' ';
}
}
if (sepchar == '(') prot_putc('(', imapd_out);
prot_putc(')', imapd_out);
assert(flagalloced > 0 || msgno < flagalloced);
flagreport[msgno] = last_updated;
}
static int index_fetchreply(struct mailbox *mailbox,
unsigned msgno, void *rock)
{
struct fetchargs *fetchargs = (struct fetchargs *)rock;
int fetchitems = fetchargs->fetchitems;
const char *msg_base = 0;
unsigned long msg_size = 0;
struct octetinfo *oi = NULL;
int sepchar = '(';
int started = 0;
int i;
bit32 user_flags[MAX_USER_FLAGS/32];
const char *cacheitem;
struct strlist *section, *field;
struct fieldlist *fsection;
char respbuf[100];
int r = 0;
if (fetchargs->changedsince &&
MODSEQ(msgno) <= fetchargs->changedsince) {
return 0;
}
if ((fetchitems & (FETCH_HEADER|FETCH_TEXT|FETCH_RFC822)) ||
fetchargs->cache_atleast > CACHE_VERSION(msgno) ||
fetchargs->binsections || fetchargs->sizesections ||
fetchargs->bodysections) {
if (mailbox_map_message(mailbox, UID(msgno), &msg_base, &msg_size)) {
prot_printf(imapd_out, "* OK ");
prot_printf(imapd_out, error_message(IMAP_NO_MSGGONE), msgno);
prot_printf(imapd_out, "\r\n");
}
}
if (fetchitems & FETCH_SETSEEN) {
if (!seenflag[msgno] && (mailbox->myrights & ACL_SEEN)) {
seenflag[msgno] = 1;
fetchitems |= FETCH_FLAGS;
}
}
if (fetchitems & FETCH_FLAGS) {
for (i = 0; i < VECTOR_SIZE(user_flags); i++) {
user_flags[i] = USER_FLAGS(msgno, i);
}
index_fetchflags(mailbox, msgno, SYSTEM_FLAGS(msgno), user_flags,
LAST_UPDATED(msgno));
sepchar = ' ';
}
else if ((fetchitems & ~FETCH_SETSEEN) || fetchargs->fsections ||
fetchargs->headers || fetchargs->headers_not) {
prot_printf(imapd_out, "* %u FETCH ", msgno);
started = 1;
}
if (fetchitems & FETCH_UID) {
prot_printf(imapd_out, "%cUID %u", sepchar, UID(msgno));
sepchar = ' ';
}
if (fetchitems & FETCH_INTERNALDATE) {
time_t msgdate = INTERNALDATE(msgno);
char datebuf[30];
cyrus_ctime(msgdate, datebuf);
prot_printf(imapd_out, "%cINTERNALDATE \"%s\"",
sepchar, datebuf);
sepchar = ' ';
}
if (fetchitems & FETCH_MODSEQ) {
prot_printf(imapd_out, "%cMODSEQ (" MODSEQ_FMT ")",
sepchar, MODSEQ(msgno));
sepchar = ' ';
}
if (fetchitems & FETCH_SIZE) {
prot_printf(imapd_out, "%cRFC822.SIZE %u", sepchar, SIZE(msgno));
sepchar = ' ';
}
if (fetchitems & FETCH_ENVELOPE) {
prot_printf(imapd_out, "%cENVELOPE ", sepchar);
sepchar = ' ';
cacheitem = cache_base + CACHE_OFFSET(msgno);
prot_write(imapd_out, cacheitem + CACHE_ITEM_SIZE_SKIP,
CACHE_ITEM_LEN(cacheitem));
}
if (fetchitems & FETCH_BODYSTRUCTURE) {
prot_printf(imapd_out, "%cBODYSTRUCTURE ", sepchar);
sepchar = ' ';
cacheitem = cache_base + CACHE_OFFSET(msgno);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
prot_write(imapd_out, cacheitem + CACHE_ITEM_SIZE_SKIP,
CACHE_ITEM_LEN(cacheitem));
}
if (fetchitems & FETCH_BODY) {
prot_printf(imapd_out, "%cBODY ", sepchar);
sepchar = ' ';
cacheitem = cache_base + CACHE_OFFSET(msgno);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
prot_write(imapd_out, cacheitem + CACHE_ITEM_SIZE_SKIP,
CACHE_ITEM_LEN(cacheitem));
}
if (fetchitems & FETCH_HEADER) {
prot_printf(imapd_out, "%cRFC822.HEADER ", sepchar);
sepchar = ' ';
index_fetchmsg(msg_base, msg_size, mailbox->format, 0,
HEADER_SIZE(msgno),
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->start_octet : 0,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->octet_count : 0,
imapd_out);
}
else if (fetchargs->headers || fetchargs->headers_not) {
prot_printf(imapd_out, "%cRFC822.HEADER ", sepchar);
sepchar = ' ';
if(fetchargs->cache_atleast > CACHE_VERSION(msgno)) {
index_fetchheader(msg_base, msg_size, mailbox->format,
HEADER_SIZE(msgno),
fetchargs->headers, fetchargs->headers_not);
} else {
index_fetchcacheheader(msgno, fetchargs->headers, 0, 0);
}
}
if (fetchitems & FETCH_TEXT) {
prot_printf(imapd_out, "%cRFC822.TEXT ", sepchar);
sepchar = ' ';
index_fetchmsg(msg_base, msg_size, mailbox->format,
CONTENT_OFFSET(msgno), SIZE(msgno) - HEADER_SIZE(msgno),
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->start_octet : 0,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->octet_count : 0,
imapd_out);
}
if (fetchitems & FETCH_RFC822) {
prot_printf(imapd_out, "%cRFC822 ", sepchar);
sepchar = ' ';
index_fetchmsg(msg_base, msg_size, mailbox->format, 0, SIZE(msgno),
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->start_octet : 0,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->octet_count : 0,
imapd_out);
}
for (fsection = fetchargs->fsections; fsection; fsection = fsection->next) {
prot_printf(imapd_out, "%cBODY[%s ", sepchar, fsection->section);
sepchar = '(';
for (field = fsection->fields; field; field = field->next) {
prot_putc(sepchar, imapd_out);
sepchar = ' ';
printastring(field->s);
}
prot_putc(')', imapd_out);
sepchar = ' ';
oi = (struct octetinfo *)fsection->rock;
prot_printf(imapd_out, "%s ", fsection->trail);
if(fetchargs->cache_atleast > CACHE_VERSION(msgno)) {
cacheitem = cache_base + CACHE_OFFSET(msgno);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
index_fetchfsection(msg_base, msg_size, mailbox->format, fsection,
cacheitem,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->start_octet : oi->start_octet,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->octet_count : oi->octet_count);
}
else {
index_fetchcacheheader(msgno, fsection->fields,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->start_octet : oi->start_octet,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->octet_count : oi->octet_count);
}
}
for (section = fetchargs->bodysections; section; section = section->next) {
respbuf[0] = 0;
if (sepchar == '(' && !started) {
snprintf(respbuf, sizeof(respbuf), "* %u FETCH ", msgno);
}
snprintf(respbuf+strlen(respbuf), sizeof(respbuf)-strlen(respbuf),
"%cBODY[%s ", sepchar, section->s);
cacheitem = cache_base + CACHE_OFFSET(msgno);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
oi = section->rock;
r = index_fetchsection(respbuf, msg_base, msg_size, mailbox->format,
section->s, cacheitem, SIZE(msgno),
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->start_octet : oi->start_octet,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->octet_count : oi->octet_count);
if (!r) sepchar = ' ';
}
for (section = fetchargs->binsections; section; section = section->next) {
respbuf[0] = 0;
if (sepchar == '(' && !started) {
snprintf(respbuf, sizeof(respbuf), "* %u FETCH ", msgno);
}
snprintf(respbuf+strlen(respbuf), sizeof(respbuf)-strlen(respbuf),
"%cBINARY[%s ", sepchar, section->s);
cacheitem = cache_base + CACHE_OFFSET(msgno);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
oi = section->rock;
r = index_fetchsection(respbuf, msg_base, msg_size, mailbox->format,
section->s, cacheitem, SIZE(msgno),
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->start_octet : oi->start_octet,
(fetchitems & FETCH_IS_PARTIAL) ?
fetchargs->octet_count : oi->octet_count);
if (!r) sepchar = ' ';
}
for (section = fetchargs->sizesections; section; section = section->next) {
respbuf[0] = 0;
if (sepchar == '(' && !started) {
snprintf(respbuf, sizeof(respbuf), "* %u FETCH ", msgno);
}
snprintf(respbuf+strlen(respbuf), sizeof(respbuf)-strlen(respbuf),
"%cBINARY.SIZE[%s ", sepchar, section->s);
cacheitem = cache_base + CACHE_OFFSET(msgno);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
r = index_fetchsection(respbuf, msg_base, msg_size, mailbox->format,
section->s, cacheitem, SIZE(msgno),
fetchargs->start_octet, fetchargs->octet_count);
if (!r) sepchar = ' ';
}
if (sepchar != '(') {
prot_printf(imapd_out, ")\r\n");
}
if (msg_base) {
mailbox_unmap_message(mailbox, UID(msgno), &msg_base, &msg_size);
}
return r;
}
int index_urlfetch(struct mailbox *mailbox, unsigned msgno,
const char *section,
unsigned long start_octet, unsigned long octet_count,
struct protstream *pout, unsigned long *outsize)
{
const char *msg_base = 0;
unsigned long msg_size = 0;
const char *cacheitem;
int skip = 0;
int fetchmime = 0;
unsigned size, offset = 0;
int n, r = 0;
if (outsize) *outsize = 0;
if (mailbox_map_message(mailbox, UID(msgno), &msg_base, &msg_size)) {
return IMAP_NO_MSGGONE;
}
cacheitem = cache_base + CACHE_OFFSET(msgno);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
size = SIZE(msgno);
cacheitem += CACHE_ITEM_SIZE_SKIP;
if (!section || !*section) {
}
else {
char *p = ucase((char *) section);
while (*p && *p != 'M') {
skip = 0;
while (cyrus_isdigit((int) *p)) {
skip = skip * 10 + *p++ - '0';
}
if (*p == '.') p++;
if (skip >= CACHE_ITEM_BIT32(cacheitem)) {
r = IMAP_BADURL;
goto done;
}
if (!skip) {
switch (*p) {
case 'H':
p += 6;
fetchmime++;
break;
case 'T':
p += 4;
break;
default:
fetchmime++;
break;
}
}
if (*p && *p != 'M') {
cacheitem +=
CACHE_ITEM_BIT32(cacheitem) * 5 * 4 + CACHE_ITEM_SIZE_SKIP;
while (--skip) {
if (CACHE_ITEM_BIT32(cacheitem) > 0) {
skip += CACHE_ITEM_BIT32(cacheitem)-1;
cacheitem += CACHE_ITEM_BIT32(cacheitem) * 5 * 4;
}
cacheitem += CACHE_ITEM_SIZE_SKIP;
}
}
}
if (*p == 'M') fetchmime++;
cacheitem += skip * 5 * 4 + CACHE_ITEM_SIZE_SKIP +
(fetchmime ? 0 : 2 * 4);
if (CACHE_ITEM_BIT32(cacheitem + CACHE_ITEM_SIZE_SKIP) == -1) {
r = IMAP_BADURL;
goto done;
}
offset = CACHE_ITEM_BIT32(cacheitem);
size = CACHE_ITEM_BIT32(cacheitem + CACHE_ITEM_SIZE_SKIP);
}
offset += start_octet;
if (octet_count) size = octet_count;
if (size && (offset + size > msg_size))
n = msg_size - offset;
else
n = size;
if (outsize)
*outsize = n;
else
prot_printf(pout, "{%u}\r\n", n);
prot_write(pout, msg_base + offset, n);
done:
mailbox_unmap_message(mailbox, UID(msgno), &msg_base, &msg_size);
return r;
}
static int index_storeseen(struct mailbox *mailbox, unsigned msgno, void *rock)
{
struct storeargs *storeargs = (struct storeargs *)rock;
int val = (storeargs->operation == STORE_ADD) ? 1 : 0;
int i;
bit32 user_flags[MAX_USER_FLAGS/32];
if (seenflag[msgno] == val) return 0;
seenflag[msgno] = val;
if (storeargs->silent) return 0;
for (i=0; i < VECTOR_SIZE(user_flags); i++) {
user_flags[i] = USER_FLAGS(msgno, i);
}
index_fetchflags(mailbox, msgno, SYSTEM_FLAGS(msgno), user_flags,
LAST_UPDATED(msgno));
if (storeargs->usinguid) {
prot_printf(imapd_out, " UID %u", UID(msgno));
}
prot_printf(imapd_out, ")\r\n");
return 0;
}
static int index_storeflag(struct mailbox *mailbox,
unsigned msgno, void *rock)
{
struct storeargs *storeargs = (struct storeargs *)rock;
int i;
struct index_record record;
int uid = UID(msgno);
int low=1, high=mailbox->exists;
int mid = 0;
int r;
int firsttry = 1;
int dirty = 0;
bit32 oldflags;
int sepchar = '(';
if (MODSEQ(msgno) > storeargs->unchangedsince) return 0;
if (storeargs->operation == STORE_REPLACE && (mailbox->myrights & ACL_SEEN))
{
if (seenflag[msgno] != storeargs->seen) dirty++;
seenflag[msgno] = storeargs->seen;
}
else if (storeargs->seen) {
i = (storeargs->operation == STORE_ADD) ? 1 : 0;
if (seenflag[msgno] != i) dirty++;
seenflag[msgno] = i;
}
while (low <= high) {
if (firsttry && msgno == storeargs->last_msgno+1) {
mid = storeargs->last_found + 1;
if (mid > high) mid = high;
}
else {
mid = (high - low)/2 + low;
}
firsttry = 0;
r = mailbox_read_index_record(mailbox, mid, &record);
if (r) return r;
if (record.uid == uid) {
break;
}
else if (record.uid > uid) {
high = mid - 1;
}
else {
low = mid + 1;
}
}
storeargs->last_msgno = msgno;
storeargs->last_found = mid;
if (low > high) {
if (storeargs->usinguid) {
return 0;
}
mid = 0;
storeargs->last_found = high;
record.system_flags = SYSTEM_FLAGS(msgno);
for (i = 0; i < VECTOR_SIZE(record.user_flags); i++) {
record.user_flags[i] = USER_FLAGS(msgno, i);
}
}
oldflags = record.system_flags;
if (storeargs->operation == STORE_REPLACE) {
if (!(mailbox->myrights & ACL_WRITE)) {
if ((record.system_flags & FLAG_DELETED) !=
(storeargs->system_flags & FLAG_DELETED)) {
dirty++;
}
record.system_flags = (record.system_flags&~FLAG_DELETED) |
(storeargs->system_flags&FLAG_DELETED);
}
else {
if (!(mailbox->myrights & ACL_DELETEMSG)) {
if ((record.system_flags & ~FLAG_DELETED) !=
(storeargs->system_flags & ~FLAG_DELETED)) {
dirty++;
}
record.system_flags = (record.system_flags&FLAG_DELETED) |
(storeargs->system_flags&~FLAG_DELETED);
}
else {
if (record.system_flags != storeargs->system_flags) dirty++;
record.system_flags = storeargs->system_flags;
}
for (i = 0; i < VECTOR_SIZE(record.user_flags); i++) {
if (record.user_flags[i] != storeargs->user_flags[i]) dirty++;
record.user_flags[i] = storeargs->user_flags[i];
}
}
}
else if (storeargs->operation == STORE_ADD) {
if (~record.system_flags & storeargs->system_flags) dirty++;
record.system_flags |= storeargs->system_flags;
for (i = 0; i < VECTOR_SIZE(record.user_flags); i++) {
if (~record.user_flags[i] & storeargs->user_flags[i]) dirty++;
record.user_flags[i] |= storeargs->user_flags[i];
}
}
else {
if (record.system_flags & storeargs->system_flags) dirty++;
record.system_flags &= ~storeargs->system_flags;
for (i = 0; i < VECTOR_SIZE(record.user_flags); i++) {
if (record.user_flags[i] & storeargs->user_flags[i]) dirty++;
record.user_flags[i] &= ~storeargs->user_flags[i];
}
}
if (dirty) {
if (mailbox->options & OPT_IMAP_CONDSTORE) {
record.modseq = mailbox->highestmodseq + 1;
}
if ( (record.system_flags & FLAG_DELETED) && !(oldflags & FLAG_DELETED))
mailbox->deleted++;
if ( !(record.system_flags & FLAG_DELETED) && (oldflags & FLAG_DELETED))
mailbox->deleted--;
if ( (record.system_flags & FLAG_ANSWERED) && !(oldflags & FLAG_ANSWERED))
mailbox->answered++;
if ( !(record.system_flags & FLAG_ANSWERED) && (oldflags & FLAG_ANSWERED))
mailbox->answered--;
if ( (record.system_flags & FLAG_FLAGGED) && !(oldflags & FLAG_FLAGGED))
mailbox->flagged++;
if ( !(record.system_flags & FLAG_FLAGGED) && (oldflags & FLAG_FLAGGED))
mailbox->flagged--;
mailbox->dirty = 1;
if (storeargs->silent &&
flagreport[msgno] == record.last_updated) {
flagreport[msgno] =
(record.last_updated >= storeargs->update_time) ?
record.last_updated + 1 : storeargs->update_time;
}
record.last_updated =
(record.last_updated >= storeargs->update_time) ?
record.last_updated + 1 : storeargs->update_time;
}
if (!storeargs->silent) {
index_fetchflags(mailbox, msgno, record.system_flags,
record.user_flags, record.last_updated);
sepchar = ' ';
}
if ((mailbox->options & OPT_IMAP_CONDSTORE) && imapd_condstore_client) {
if (sepchar == '(') {
prot_printf(imapd_out, "* %u FETCH ", msgno);
}
prot_printf(imapd_out, "%cMODSEQ (" MODSEQ_FMT ")",
sepchar, record.modseq);
sepchar = ' ';
}
if (sepchar != '(') {
if (storeargs->usinguid) {
prot_printf(imapd_out, " UID %u", UID(msgno));
}
prot_printf(imapd_out, ")\r\n");
}
if (dirty && mid) {
r = mailbox_write_index_record(mailbox, mid, &record, 0);
if (r) return r;
}
return 0;
}
static int index_search_evaluate(struct mailbox *mailbox,
struct searchargs *searchargs,
unsigned msgno,
struct mapfile *msgfile)
{
int i;
struct strlist *l, *h;
const char *cacheitem;
int cachelen;
struct searchsub *s;
if ((searchargs->flags & SEARCH_RECENT_SET) && msgno <= lastnotrecent)
return 0;
if ((searchargs->flags & SEARCH_RECENT_UNSET) && msgno > lastnotrecent)
return 0;
if ((searchargs->flags & SEARCH_SEEN_SET) && !seenflag[msgno]) return 0;
if ((searchargs->flags & SEARCH_SEEN_UNSET) && seenflag[msgno]) return 0;
if (searchargs->smaller && SIZE(msgno) >= searchargs->smaller) return 0;
if (searchargs->larger && SIZE(msgno) <= searchargs->larger) return 0;
if (searchargs->after && INTERNALDATE(msgno) < searchargs->after)
return 0;
if (searchargs->before && INTERNALDATE(msgno) > searchargs->before)
return 0;
if (searchargs->sentafter && SENTDATE(msgno) < searchargs->sentafter)
return 0;
if (searchargs->sentbefore && SENTDATE(msgno) > searchargs->sentbefore)
return 0;
if (searchargs->modseq && MODSEQ(msgno) < searchargs->modseq) return 0;
if (~SYSTEM_FLAGS(msgno) & searchargs->system_flags_set) return 0;
if (SYSTEM_FLAGS(msgno) & searchargs->system_flags_unset) return 0;
for (i = 0; i < VECTOR_SIZE(searchargs->user_flags_set); i++) {
if (~USER_FLAGS(msgno,i) & searchargs->user_flags_set[i])
return 0;
if (USER_FLAGS(msgno,i) & searchargs->user_flags_unset[i])
return 0;
}
for (l = searchargs->sequence; l; l = l->next) {
if (!index_insequence(msgno, l->s, 0)) return 0;
}
for (l = searchargs->uidsequence; l; l = l->next) {
if (!index_insequence(UID(msgno), l->s, 1)) return 0;
}
if (searchargs->from || searchargs->to || searchargs->cc ||
searchargs->bcc || searchargs->subject || searchargs->messageid) {
cacheitem = cache_base + CACHE_OFFSET(msgno);
cachelen = CACHE_ITEM_LEN(cacheitem);
if (searchargs->messageid) {
char *tmpenv;
char *envtokens[NUMENVTOKENS];
char *msgid;
int msgidlen;
tmpenv = xstrndup(cacheitem + CACHE_ITEM_SIZE_SKIP + 1,
cachelen - 2);
parse_cached_envelope(tmpenv, envtokens, VECTOR_SIZE(envtokens));
if (!envtokens[ENV_MSGID]) {
free(tmpenv);
return 0;
}
msgid = lcase(envtokens[ENV_MSGID]);
msgidlen = strlen(msgid);
for (l = searchargs->messageid; l; l = l->next) {
if (!charset_searchstring(l->s, l->p, msgid, msgidlen)) {
break;
}
}
free(tmpenv);
if (l) return 0;
}
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cachelen = CACHE_ITEM_LEN(cacheitem);
for (l = searchargs->from; l; l = l->next) {
if (cachelen == 0 ||
!charset_searchstring(l->s, l->p,
cacheitem + CACHE_ITEM_SIZE_SKIP,
cachelen))
return 0;
}
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cachelen = CACHE_ITEM_LEN(cacheitem);
for (l = searchargs->to; l; l = l->next) {
if (cachelen == 0 ||
!charset_searchstring(l->s, l->p,
cacheitem + CACHE_ITEM_SIZE_SKIP,
cachelen))
return 0;
}
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cachelen = CACHE_ITEM_LEN(cacheitem);
for (l = searchargs->cc; l; l = l->next) {
if (cachelen == 0 ||
!charset_searchstring(l->s, l->p,
cacheitem + CACHE_ITEM_SIZE_SKIP,
cachelen))
return 0;
}
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cachelen = CACHE_ITEM_LEN(cacheitem);
for (l = searchargs->bcc; l; l = l->next) {
if (cachelen == 0 ||
!charset_searchstring(l->s, l->p,
cacheitem + CACHE_ITEM_SIZE_SKIP,
cachelen))
return 0;
}
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cachelen = CACHE_ITEM_LEN(cacheitem);
for (l = searchargs->subject; l; l = l->next) {
if ((cachelen == 3 &&
!strcmp(cacheitem + CACHE_ITEM_SIZE_SKIP, "NIL")) ||
!charset_searchstring(l->s, l->p,
cacheitem + CACHE_ITEM_SIZE_SKIP,
cachelen))
return 0;
}
}
for (s = searchargs->sublist; s; s = s->next) {
if (index_search_evaluate(mailbox, s->sub1, msgno, msgfile)) {
if (!s->sub2) return 0;
}
else {
if (s->sub2 &&
!index_search_evaluate(mailbox, s->sub2, msgno, msgfile))
return 0;
}
}
if (searchargs->body || searchargs->text ||
searchargs->cache_atleast > CACHE_VERSION(msgno)) {
if (! msgfile->size) {
if (mailbox_map_message(mailbox, UID(msgno),
&msgfile->base, &msgfile->size)) {
return 0;
}
}
h = searchargs->header_name;
for (l = searchargs->header; l; (l = l->next), (h = h->next)) {
if (!index_searchheader(h->s, l->s, l->p, msgfile, mailbox->format,
HEADER_SIZE(msgno))) return 0;
}
cacheitem = cache_base + CACHE_OFFSET(msgno);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
for (l = searchargs->body; l; l = l->next) {
if (!index_searchmsg(l->s, l->p, msgfile, mailbox->format, 1,
cacheitem)) return 0;
}
for (l = searchargs->text; l; l = l->next) {
if (!index_searchmsg(l->s, l->p, msgfile, mailbox->format, 0,
cacheitem)) return 0;
}
}
else if (searchargs->header_name) {
h = searchargs->header_name;
for (l = searchargs->header; l; (l = l->next), (h = h->next)) {
if (!index_searchcacheheader(msgno, h->s, l->s, l->p)) return 0;
}
}
return 1;
}
static int
index_searchmsg(char *substr,
comp_pat *pat,
struct mapfile *msgfile,
int format,
int skipheader,
const char *cacheitem)
{
int partsleft = 1;
int subparts;
int start, len, charset, encoding;
char *p, *q;
if (msgfile->size == 0) return 0;
cacheitem += CACHE_ITEM_SIZE_SKIP;
while (partsleft--) {
subparts = CACHE_ITEM_BIT32(cacheitem);
cacheitem += 4;
if (subparts) {
partsleft += subparts-1;
if (skipheader) {
skipheader = 0;
}
else {
len = CACHE_ITEM_BIT32(cacheitem + CACHE_ITEM_SIZE_SKIP);
if (len > 0) {
p = index_readheader(msgfile->base, msgfile->size,
format, CACHE_ITEM_BIT32(cacheitem),
len);
q = charset_decode_mimeheader(p, NULL, 0);
if (charset_searchstring(substr, pat, q, strlen(q))) {
free(q);
return 1;
}
free(q);
}
}
cacheitem += 5*4;
while (--subparts) {
start = CACHE_ITEM_BIT32(cacheitem+2*4);
len = CACHE_ITEM_BIT32(cacheitem+3*4);
charset = CACHE_ITEM_BIT32(cacheitem+4*4) >> 16;
encoding = CACHE_ITEM_BIT32(cacheitem+4*4) & 0xff;
if (start < msgfile->size && len > 0 &&
charset >= 0 && charset < 0xffff) {
if (charset_searchfile(substr, pat,
msgfile->base + start,
format == MAILBOX_FORMAT_NETNEWS,
len, charset, encoding)) return 1;
}
cacheitem += 5*4;
}
}
}
return 0;
}
static int index_searchheader(char *name,
char *substr,
comp_pat *pat,
struct mapfile *msgfile,
int format,
int size)
{
char *p, *q;
int r;
static struct strlist header;
header.s = name;
p = index_readheader(msgfile->base, msgfile->size, format, 0, size);
index_pruneheader(p, &header, 0);
if (!*p) return 0;
if (!*substr) return 1;
q = charset_decode_mimeheader(strchr(p, ':') + 1, NULL, 0);
r = charset_searchstring(substr, pat, q, strlen(q));
free(q);
return r;
}
static int index_searchcacheheader(unsigned msgno,
char *name, char *substr,
comp_pat *pat)
{
char *q;
static struct strlist header;
static char *buf;
static int bufsize;
const char *cacheitem;
unsigned size;
int r;
cacheitem = cache_base + CACHE_OFFSET(msgno);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
size = CACHE_ITEM_LEN(cacheitem);
if (!size) return 0;
cacheitem += sizeof(bit32);
if (bufsize < size+2) {
bufsize = size+100;
buf = xrealloc(buf, bufsize);
}
memcpy(buf, cacheitem, size);
buf[size] = '\0';
header.s = name;
index_pruneheader(buf, &header, 0);
if (!*buf) return 0;
if (!*substr) return 1;
q = charset_decode_mimeheader(strchr(buf, ':') + 1, NULL, 0);
r = charset_searchstring(substr, pat, q, strlen(q));
free(q);
return r;
}
static void index_getsearchtextmsg(struct mailbox* mailbox,
int uid,
index_search_text_receiver_t receiver,
void* rock,
char const* cacheitem) {
struct mapfile msgfile;
int partsleft = 1;
int subparts;
int start, len, charset, encoding;
int partcount = 0;
char *p, *q;
int format = mailbox->format;
if (mailbox_map_message(mailbox, uid, &msgfile.base, &msgfile.size)) {
return;
}
if (msgfile.size > 0) {
cacheitem += 4;
while (partsleft--) {
subparts = CACHE_ITEM_BIT32(cacheitem);
cacheitem += 4;
if (subparts) {
partsleft += subparts-1;
partcount++;
len = CACHE_ITEM_BIT32(cacheitem+4);
if (len > 0) {
p = index_readheader(msgfile.base, msgfile.size,
format, CACHE_ITEM_BIT32(cacheitem),
len);
q = charset_decode_mimeheader(p, NULL, 0);
if (partcount == 1) {
receiver(uid, SEARCHINDEX_PART_HEADERS,
SEARCHINDEX_CMD_STUFFPART, q, strlen(q), rock);
receiver(uid, SEARCHINDEX_PART_BODY,
SEARCHINDEX_CMD_BEGINPART, NULL, 0, rock);
} else {
receiver(uid, SEARCHINDEX_PART_BODY,
SEARCHINDEX_CMD_APPENDPART, q, strlen(q), rock);
}
free(q);
}
cacheitem += 5*4;
while (--subparts) {
start = CACHE_ITEM_BIT32(cacheitem+2*4);
len = CACHE_ITEM_BIT32(cacheitem+3*4);
charset = CACHE_ITEM_BIT32(cacheitem+4*4) >> 16;
encoding = CACHE_ITEM_BIT32(cacheitem+4*4) & 0xff;
if (start < msgfile.size && len > 0 &&
charset >= 0 && charset < 0xffff) {
charset_extractfile(receiver, rock, uid,
msgfile.base + start,
format == MAILBOX_FORMAT_NETNEWS,
len, charset, encoding);
}
cacheitem += 5*4;
}
}
}
receiver(uid, SEARCHINDEX_PART_BODY,
SEARCHINDEX_CMD_ENDPART, NULL, 0, rock);
}
mailbox_unmap_message(mailbox, uid, &msgfile.base, &msgfile.size);
}
void index_getsearchtext(struct mailbox* mailbox,
index_search_text_receiver_t receiver,
void* rock) {
int i;
for (i = 1; i <= imapd_exists; i++) {
const char *cacheitem;
int uid = UID(i);
cacheitem = cache_base + CACHE_OFFSET(i);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
index_getsearchtextmsg(mailbox, uid, receiver, rock, cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
receiver(uid, SEARCHINDEX_PART_FROM, SEARCHINDEX_CMD_STUFFPART,
cacheitem + 4, CACHE_ITEM_LEN(cacheitem), rock);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
receiver(uid, SEARCHINDEX_PART_TO, SEARCHINDEX_CMD_STUFFPART,
cacheitem + 4, CACHE_ITEM_LEN(cacheitem), rock);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
receiver(uid, SEARCHINDEX_PART_CC, SEARCHINDEX_CMD_STUFFPART,
cacheitem + 4, CACHE_ITEM_LEN(cacheitem), rock);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
receiver(uid, SEARCHINDEX_PART_BCC, SEARCHINDEX_CMD_STUFFPART,
cacheitem + 4, CACHE_ITEM_LEN(cacheitem), rock);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
receiver(uid, SEARCHINDEX_PART_SUBJECT, SEARCHINDEX_CMD_STUFFPART,
cacheitem + 4, CACHE_ITEM_LEN(cacheitem), rock);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
}
}
#define COPYARGSGROW 30
static int
index_copysetup(mailbox, msgno, rock)
struct mailbox *mailbox;
unsigned msgno;
void *rock;
{
struct copyargs *copyargs = (struct copyargs *)rock;
int flag = 0;
unsigned userflag;
bit32 flagmask = 0;
if (copyargs->nummsg == copyargs->msgalloc) {
copyargs->msgalloc += COPYARGSGROW;
copyargs->copymsg = (struct copymsg *)
xrealloc((char *)copyargs->copymsg,
copyargs->msgalloc * sizeof(struct copymsg));
}
copyargs->copymsg[copyargs->nummsg].uid = UID(msgno);
copyargs->copymsg[copyargs->nummsg].internaldate = INTERNALDATE(msgno);
copyargs->copymsg[copyargs->nummsg].sentdate = SENTDATE(msgno);
copyargs->copymsg[copyargs->nummsg].size = SIZE(msgno);
copyargs->copymsg[copyargs->nummsg].header_size = HEADER_SIZE(msgno);
copyargs->copymsg[copyargs->nummsg].content_lines = CONTENT_LINES(msgno);
copyargs->copymsg[copyargs->nummsg].cache_version = CACHE_VERSION(msgno);
copyargs->copymsg[copyargs->nummsg].cache_begin = cache_base + CACHE_OFFSET(msgno);
message_uuid_unpack(©args->copymsg[copyargs->nummsg].uuid,
(unsigned char *)
INDEC_OFFSET(msgno)+OFFSET_MESSAGE_UUID);
if (mailbox->format != MAILBOX_FORMAT_NORMAL) {
copyargs->copymsg[copyargs->nummsg].cache_len = 0;
}
else if (msgno < imapd_exists) {
copyargs->copymsg[copyargs->nummsg].cache_len =
CACHE_OFFSET(msgno+1) - CACHE_OFFSET(msgno);
}
else {
copyargs->copymsg[copyargs->nummsg].cache_len =
cache_end - CACHE_OFFSET(msgno);
}
copyargs->copymsg[copyargs->nummsg].seen = seenflag[msgno];
copyargs->copymsg[copyargs->nummsg].system_flags = SYSTEM_FLAGS(msgno);
for (userflag = 0; userflag < MAX_USER_FLAGS; userflag++) {
if ((userflag & 31) == 0) {
flagmask = USER_FLAGS(msgno,userflag/32);
}
if (!mailbox->flagname[userflag] && (flagmask & (1<<(userflag&31)))) {
mailbox_read_header(mailbox);
index_listflags(mailbox);
break;
}
}
for (userflag = 0; userflag < MAX_USER_FLAGS; userflag++) {
if ((userflag & 31) == 0) {
flagmask = USER_FLAGS(msgno,userflag/32);
}
if (mailbox->flagname[userflag] && (flagmask & (1<<(userflag&31)))) {
copyargs->copymsg[copyargs->nummsg].flag[flag++] =
mailbox->flagname[userflag];
}
}
copyargs->copymsg[copyargs->nummsg].flag[flag] = 0;
copyargs->nummsg++;
return 0;
}
#define ANNOTGROWSIZE 10
static MsgData *index_msgdata_load(unsigned *msgno_list, int n,
struct sortcrit *sortcrit)
{
MsgData *md, *cur;
const char *cacheitem = NULL, *env = NULL,
*headers = NULL, *from = NULL, *to = NULL, *cc = NULL, *subj = NULL;
int i, j;
char *tmpenv;
char *envtokens[NUMENVTOKENS];
int did_cache, did_env;
int label;
int annotsize;
if (!n)
return NULL;
md = (MsgData *) xmalloc(n * sizeof(MsgData));
memset(md, 0, n * sizeof(MsgData));
for (i = 0, cur = md; i < n; i++, cur = cur->next) {
cur->msgno = msgno_list[i];
cur->next = (i+1 < n ? cur+1 : NULL);
did_cache = did_env = 0;
tmpenv = NULL;
annotsize = 0;
for (j = 0; sortcrit[j].key; j++) {
label = sortcrit[j].key;
if ((label == SORT_CC || label == SORT_DATE ||
label == SORT_FROM || label == SORT_SUBJECT ||
label == SORT_TO || label == LOAD_IDS) &&
!did_cache) {
env = cache_base + CACHE_OFFSET(cur->msgno);
cacheitem = CACHE_ITEM_NEXT(env);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
headers = CACHE_ITEM_NEXT(cacheitem);
from = CACHE_ITEM_NEXT(headers);
to = CACHE_ITEM_NEXT(from);
cc = CACHE_ITEM_NEXT(to);
cacheitem = CACHE_ITEM_NEXT(cc);
subj = CACHE_ITEM_NEXT(cacheitem);
did_cache++;
}
if ((label == SORT_DATE || label == LOAD_IDS) &&
!did_env) {
tmpenv = xstrndup(env + CACHE_ITEM_SIZE_SKIP + 1,
CACHE_ITEM_LEN(env) - 2);
parse_cached_envelope(tmpenv, envtokens,
VECTOR_SIZE(envtokens));
did_env++;
}
switch (label) {
case SORT_CC:
cur->cc = get_localpart_addr(cc + CACHE_ITEM_SIZE_SKIP);
break;
case SORT_DATE:
cur->date = message_parse_date(envtokens[ENV_DATE],
PARSE_TIME | PARSE_ZONE
| PARSE_NOCREATE);
break;
case SORT_FROM:
cur->from = get_localpart_addr(from + CACHE_ITEM_SIZE_SKIP);
break;
case SORT_SUBJECT:
cur->xsubj = index_extract_subject(subj + CACHE_ITEM_SIZE_SKIP,
CACHE_ITEM_LEN(subj),
&cur->is_refwd);
cur->xsubj_hash = strhash(cur->xsubj);
break;
case SORT_TO:
cur->to = get_localpart_addr(to + CACHE_ITEM_SIZE_SKIP);
break;
case SORT_ANNOTATION:
if (cur->nannot == annotsize) {
annotsize += ANNOTGROWSIZE;
cur->annot = (char **)
xrealloc(cur->annot, annotsize * sizeof(char *));
}
cur->annot[cur->nannot] = xstrdup(sortcrit[j].args.annot.attrib);
cur->nannot++;
break;
case LOAD_IDS:
index_get_ids(cur, envtokens, headers + CACHE_ITEM_SIZE_SKIP);
break;
}
}
if (tmpenv) free(tmpenv);
}
return md;
}
static void parse_cached_envelope(char *env, char *tokens[], int tokens_size)
{
char *c;
int i = 0, ncom = 0, len;
c = env;
while (*c != '\0') {
switch (*c) {
case ' ':
if (!ncom) *c = '\0';
c++;
break;
case 'N':
case 'n':
if (!ncom) {
if(i>=tokens_size) break;
tokens[i++] = NULL;
}
c += 3;
break;
case '"':
c++;
if (!ncom) {
if(i>=tokens_size) break;
tokens[i++] = c;
}
while (*c != '"') {
if (*c == '\0') {
fatal("Quoted string w/o end quote in parse_cached_envelope",
EC_SOFTWARE);
}
if (*c == '\\') c++;
c++;
}
if (!ncom) *c = '\0';
c++;
break;
case '{':
c++;
len = 0;
while (cyrus_isdigit((int) *c)) {
len = len*10 + *c - '0';
c++;
}
c += 3;
if (!ncom){
if(i>=tokens_size) break;
tokens[i++] = c;
}
c += len;
break;
case '(':
c++;
if (!ncom) {
if(i>=tokens_size) break;
tokens[i++] = c;
}
ncom++;
break;
case ')':
c++;
if (ncom) {
ncom--;
if (!ncom)
*(c-1) = '\0';
}
break;
default:
c++;
break;
}
}
}
static char *get_localpart_addr(const char *header)
{
struct address *addr = NULL;
char *ret;
parseaddr_list(header, &addr);
ret = xstrdup(addr && addr->mailbox ? addr->mailbox : "");
parseaddr_free(addr);
return ret;
}
static char *index_extract_subject(const char *subj, size_t len, int *is_refwd)
{
char *buf, *s, *base;
if (!strcmp(subj, "NIL")) {
return xstrdup("");
} else if (*subj == '"') {
buf = xstrndup(subj + 1, len - 2);
} else {
s = strchr(subj, '}') + 3;
buf = xstrndup(s, len - (s - subj));
}
for (s = buf;;) {
base = _index_extract_subject(s, is_refwd);
if (!strncasecmp(base, "[fwd:", 5) &&
base[strlen(base) - 1] == ']') {
*is_refwd += 1;
base[strlen(base) - 1] = '\0';
s = base + 5;
}
else
break;
}
base = xstrdup(base);
free(buf);
return base;
}
static char *_index_extract_subject(char *s, int *is_refwd)
{
char *base, *x;
for (x = s + strlen(s) - 1; x >= s;) {
if (isspace((int) *x)) {
*x = '\0';
x--;
}
else if (x - s >= 4 &&
!strncasecmp(x-4, "(fwd)", 5)) {
*(x-4) = '\0';
x -= 5;
*is_refwd += 1;
}
else
break;
}
for (base = s; base;) {
if (isspace((int) *base)) base++;
else if ((!strncasecmp(base, "re", 2) &&
(x = base + 2)) ||
(!strncasecmp(base, "fwd", 3) &&
(x = base + 3)) ||
(!strncasecmp(base, "fw", 2) &&
(x = base + 2))) {
int count = 0;
while (isspace((int) *x)) x++;
if (*x == '[') {
for (x++; x;) {
if (!*x) {
x = NULL;
break;
}
else if (*x == ']') {
break;
} else if (cyrus_isdigit((int) *x) && count != -1) {
count = count * 10 + *x - '0';
if (count < 0) {
count = -1;
}
} else {
count = -1;
}
x++;
}
if (x)
x++;
else
break;
}
while (isspace((int) *x)) x++;
if (*x == ':') {
base = x + 1;
*is_refwd += (count > 0 ? count : 1);
}
else
break;
}
#if 0
else if (*base == '[') {
int count = 1;
x = base + 1;
while (count) {
if (!*x) {
x = NULL;
break;
}
else if (*x == '[')
count++;
else if (*x == ']')
count--;
x++;
}
if (!x)
break;
else if (*x)
base = x;
#else
else if (*base == '[' &&
(x = strpbrk(base+1, "[]")) &&
*x == ']') {
if (*(x+1))
base = x + 1;
#endif
else
break;
}
else
break;
}
return base;
}
#define MSGID_SPECIALS "<> @\\"
static char *find_msgid(char *str, char **rem)
{
char *msgid, *src, *dst, *cp;
if (!str) return NULL;
msgid = NULL;
src = str;
while ((cp = src = strpbrk(src, "<\r")) != NULL) {
if (*cp++ == '\r') {
if (*cp++ == '\n' && !(*cp == ' ' || *cp == '\t')) {
break;
}
src++;
continue;
}
if (*cp == '\"') {
do {
++cp; cp = strchr(cp, '\"');
} while (cp && *(cp-1) == '\\');
if (!cp) {
src++;
continue;
}
}
if ((cp = strchr(cp, '>')) == NULL)
return NULL;
dst = msgid = (char*) xrealloc(msgid, cp - src + 2);
*dst++ = *src++;
if (*src == '\"') {
src++;
while (*src != '\"') {
if (*src == '\\') {
src++;
}
*dst++ = *src++;
}
src++;
}
else {
while (!strchr(MSGID_SPECIALS, *src))
*dst++ = *src++;
}
if (*src != '@' || *(dst-1) == '<') continue;
*dst++ = *src++;
while (!strchr(MSGID_SPECIALS, *src))
*dst++ = *src++;
if (*src != '>' || *(dst-1) == '@') continue;
*dst++ = *src++;
*dst = '\0';
if (rem) *rem = src;
return msgid;
}
if (msgid) free(msgid);
return NULL;
}
#define REFGROWSIZE 10
void index_get_ids(MsgData *msgdata, char *envtokens[], const char *headers)
{
char *refstr, *ref, *in_reply_to;
int refsize = REFGROWSIZE;
char buf[100];
msgdata->msgid = find_msgid(envtokens[ENV_MSGID], NULL);
if (!msgdata->msgid) {
snprintf(buf, sizeof(buf), "<Empty-ID: %u>", msgdata->msgno);
msgdata->msgid = xstrdup(buf);
}
if ((refstr = stristr(headers, "references:"))) {
msgdata->ref = (char **) xmalloc(refsize * sizeof(char *));
while ((ref = find_msgid(refstr, &refstr)) != NULL) {
if (msgdata->nref == refsize) {
refsize += REFGROWSIZE;
msgdata->ref = (char **)
xrealloc(msgdata->ref, refsize * sizeof(char *));
}
msgdata->ref[msgdata->nref++] = ref;
}
}
if (!msgdata->nref) {
in_reply_to = find_msgid(envtokens[ENV_INREPLYTO], NULL);
if (in_reply_to) {
msgdata->ref = (char **) xmalloc(sizeof(char *));
msgdata->ref[msgdata->nref++] = in_reply_to;
}
}
}
static void *index_sort_getnext(MsgData *node)
{
return node->next;
}
static void index_sort_setnext(MsgData *node, MsgData *next)
{
node->next = next;
}
static int numcmp(modseq_t n1, modseq_t n2)
{
return ((n1 < n2) ? -1 : (n1 > n2) ? 1 : 0);
}
static int index_sort_compare(MsgData *md1, MsgData *md2,
struct sortcrit *sortcrit)
{
int reverse, ret = 0, i = 0, ann = 0;
do {
reverse = sortcrit[i].flags & SORT_REVERSE;
switch (sortcrit[i].key) {
case SORT_SEQUENCE:
ret = numcmp(md1->msgno, md2->msgno);
break;
case SORT_ARRIVAL:
ret = numcmp(INTERNALDATE(md1->msgno), INTERNALDATE(md2->msgno));
break;
case SORT_CC:
ret = strcmp(md1->cc, md2->cc);
break;
case SORT_DATE: {
time_t d1 = md1->date ? md1->date : INTERNALDATE(md1->msgno);
time_t d2 = md2->date ? md2->date : INTERNALDATE(md2->msgno);
ret = numcmp(d1, d2);
break;
}
case SORT_FROM:
ret = strcmp(md1->from, md2->from);
break;
case SORT_SIZE:
ret = numcmp(SIZE(md1->msgno), SIZE(md2->msgno));
break;
case SORT_SUBJECT:
ret = strcmp(md1->xsubj, md2->xsubj);
break;
case SORT_TO:
ret = strcmp(md1->to, md2->to);
break;
case SORT_ANNOTATION:
ret = strcmp(md1->annot[ann], md2->annot[ann]);
ann++;
break;
case SORT_MODSEQ:
ret = numcmp(MODSEQ(md1->msgno), MODSEQ(md2->msgno));
break;
}
} while (!ret && sortcrit[i++].key != SORT_SEQUENCE);
return (reverse ? -ret : ret);
}
static void index_msgdata_free(MsgData *md)
{
#define FREE(x) if (x) free(x)
int i;
if (!md)
return;
FREE(md->cc);
FREE(md->from);
FREE(md->to);
FREE(md->xsubj);
FREE(md->msgid);
for (i = 0; i < md->nref; i++)
free(md->ref[i]);
FREE(md->ref);
for (i = 0; i < md->nannot; i++)
free(md->annot[i]);
FREE(md->annot);
}
static void *index_thread_getnext(Thread *thread)
{
return thread->next;
}
static void index_thread_setnext(Thread *thread, Thread *next)
{
thread->next = next;
}
static int index_thread_compare(Thread *t1, Thread *t2,
struct sortcrit *call_data)
{
MsgData *md1, *md2;
md1 = t1->msgdata ? t1->msgdata : t1->child->msgdata;
md2 = t2->msgdata ? t2->msgdata : t2->child->msgdata;
return index_sort_compare(md1, md2, call_data);
}
static void index_thread_sort(Thread *root, struct sortcrit *sortcrit)
{
Thread *child;
child = root->child;
while (child) {
if (child->child)
index_thread_sort(child, sortcrit);
child = child->next;
}
root->child = lsort(root->child,
(void * (*)(void*)) index_thread_getnext,
(void (*)(void*,void*)) index_thread_setnext,
(int (*)(void*,void*,void*)) index_thread_compare,
sortcrit);
}
static void index_thread_orderedsubj(unsigned *msgno_list, int nmsg,
int usinguid)
{
MsgData *msgdata, *freeme;
struct sortcrit sortcrit[] = {{ SORT_SUBJECT, 0, {{NULL, NULL}} },
{ SORT_DATE, 0, {{NULL, NULL}} },
{ SORT_SEQUENCE, 0, {{NULL, NULL}} }};
unsigned psubj_hash = 0;
char *psubj;
Thread *head, *newnode, *cur, *parent, *last;
freeme = msgdata = index_msgdata_load(msgno_list, nmsg, sortcrit);
msgdata = lsort(msgdata,
(void * (*)(void*)) index_sort_getnext,
(void (*)(void*,void*)) index_sort_setnext,
(int (*)(void*,void*,void*)) index_sort_compare,
sortcrit);
head = (Thread *) xmalloc((nmsg + 1) * sizeof(Thread));
memset(head, 0, (nmsg + 1) * sizeof(Thread));
newnode = head + 1;
parent = head;
psubj = NULL;
cur = NULL;
last = NULL;
while (msgdata) {
newnode->msgdata = msgdata;
if (!psubj ||
(msgdata->xsubj_hash == psubj_hash &&
!strcmp(msgdata->xsubj, psubj))) {
if (!parent->child) {
last = parent->child = newnode;
if (!cur)
parent = cur = parent->child;
}
else {
last->next = newnode;
last = last->next;
}
}
else {
cur->next = newnode;
parent = cur = cur->next;
}
psubj_hash = msgdata->xsubj_hash;
psubj = msgdata->xsubj;
msgdata = msgdata->next;
newnode++;
}
index_thread_sort(head, sortcrit+1);
index_thread_print(head, usinguid);
free(head);
free(freeme);
}
static void _index_thread_print(Thread *thread, int usinguid)
{
Thread *child;
while (thread) {
prot_printf(imapd_out, "(");
if (thread->msgdata) {
prot_printf(imapd_out, "%u",
usinguid ? UID(thread->msgdata->msgno) :
thread->msgdata->msgno);
if (thread->child) prot_printf(imapd_out, " ");
index_msgdata_free(thread->msgdata);
}
child = thread->child;
while (child) {
if (child->next) {
_index_thread_print(child, usinguid);
break;
}
else {
prot_printf(imapd_out, "%u",
usinguid ? UID(child->msgdata->msgno) :
child->msgdata->msgno);
if (child->child) prot_printf(imapd_out, " ");
index_msgdata_free(child->msgdata);
child = child->child;
}
}
prot_printf(imapd_out, ")");
thread = thread->next;
}
}
static void index_thread_print(Thread *thread, int usinguid)
{
prot_printf(imapd_out, "* THREAD");
if (thread) {
prot_printf(imapd_out, " ");
_index_thread_print(thread->child, usinguid);
}
}
int find_thread_algorithm(char *arg)
{
int alg;
ucase(arg);
for (alg = 0; thread_algs[alg].alg_name; alg++) {
if (!strcmp(arg, thread_algs[alg].alg_name))
return alg;
}
return -1;
}
static int thread_is_descendent(Thread *parent, Thread *child)
{
Thread *kid;
if (parent == child)
return 1;
for (kid = parent->child; kid; kid = kid->next) {
if (thread_is_descendent(kid, child))
return 1;
}
return 0;
}
static void thread_adopt_child(Thread *parent, Thread *child)
{
child->parent = parent;
child->next = parent->child;
parent->child = child;
}
static void thread_orphan_child(Thread *child)
{
Thread *prev, *cur;
for (prev = NULL, cur = child->parent->child;
cur != child && cur != NULL; prev = cur, cur = cur->next);
if (!cur) {
return;
}
if (!prev)
child->parent->child = child->next;
else
prev->next = child->next;
child->parent = child->next = NULL;
}
static void ref_link_messages(MsgData *msgdata, Thread **newnode,
struct hash_table *id_table)
{
Thread *cur, *parent, *ref;
int dup_count = 0;
char buf[100];
int i;
while (msgdata) {
if ((cur = (Thread *) hash_lookup(msgdata->msgid, id_table))) {
if (cur->msgdata) {
snprintf(buf, sizeof(buf), "-dup%d", dup_count++);
msgdata->msgid =
(char *) xrealloc(msgdata->msgid,
strlen(msgdata->msgid) + strlen(buf) + 1);
strcat(msgdata->msgid, buf);
cur = NULL;
}
else
cur->msgdata = msgdata;
}
if (!cur) {
cur = *newnode;
cur->msgdata = msgdata;
hash_insert(msgdata->msgid, cur, id_table);
(*newnode)++;
}
for (i = 0, parent = NULL; i < msgdata->nref; i++) {
if (!(ref = (Thread *) hash_lookup(msgdata->ref[i], id_table))) {
ref = *newnode;
hash_insert(msgdata->ref[i], ref, id_table);
(*newnode)++;
}
if (!ref->parent &&
parent && !thread_is_descendent(ref, parent)) {
thread_adopt_child(parent, ref);
}
parent = ref;
}
if (cur->parent) thread_orphan_child(cur);
if (parent && !thread_is_descendent(cur, parent))
thread_adopt_child(parent, cur);
msgdata = msgdata->next;
}
}
static void ref_gather_orphans(char *key __attribute__((unused)),
Thread *node,
struct rootset *rootset)
{
if (!node->parent) {
if (node->next) {
return;
}
node->next = rootset->root->child;
rootset->root->child = node;
rootset->nroot++;
}
}
static void ref_prune_tree(Thread *parent)
{
Thread *cur, *prev, *next, *child;
for (prev = NULL, cur = parent->child, next = cur->next;
cur;
prev = cur, cur = next, next = (cur ? cur->next : NULL)) {
if (!cur->msgdata && !cur->child) {
if (!prev)
parent->child = cur->next;
else
prev->next = cur->next;
cur = prev;
}
else if (!cur->msgdata && cur->child &&
(cur->parent || !cur->child->next)) {
if (!prev)
parent->child = cur->child;
else
prev->next = cur->child;
child = cur->child;
do {
child->parent = cur->parent;
} while (child->next && (child = child->next));
child->next = cur->next;
next = cur->child;
cur->child = cur->next = NULL;
cur = prev;
}
else if (cur->child)
ref_prune_tree(cur);
}
}
static void ref_sort_root(Thread *root)
{
Thread *cur;
struct sortcrit sortcrit[] = {{ SORT_DATE, 0, {{NULL, NULL}} },
{ SORT_SEQUENCE, 0, {{NULL, NULL}} }};
cur = root->child;
while (cur) {
if (!cur->msgdata) {
cur->child = lsort(cur->child,
(void * (*)(void*)) index_thread_getnext,
(void (*)(void*,void*)) index_thread_setnext,
(int (*)(void*,void*,void*)) index_thread_compare,
sortcrit);
}
cur = cur->next;
}
root->child = lsort(root->child,
(void * (*)(void*)) index_thread_getnext,
(void (*)(void*,void*)) index_thread_setnext,
(int (*)(void*,void*,void*)) index_thread_compare,
sortcrit);
}
static void ref_group_subjects(Thread *root, unsigned nroot, Thread **newnode)
{
Thread *cur, *old, *prev, *next, *child;
struct hash_table subj_table;
char *subj;
construct_hash_table(&subj_table, nroot, 1);
for (cur = root->child; cur; cur = cur->next) {
if (cur->msgdata)
subj = cur->msgdata->xsubj;
else
subj = cur->child->msgdata->xsubj;
if (!strlen(subj)) continue;
old = (Thread *) hash_lookup(subj, &subj_table);
if (!old ||
(!cur->msgdata && old->msgdata) ||
(old->msgdata && old->msgdata->is_refwd &&
cur->msgdata && !cur->msgdata->is_refwd)) {
hash_insert(subj, cur, &subj_table);
}
}
for (prev = NULL, cur = root->child, next = cur->next;
cur;
prev = cur, cur = next, next = (next ? next->next : NULL)) {
if (cur->msgdata)
subj = cur->msgdata->xsubj;
else
subj = cur->child->msgdata->xsubj;
if (!strlen(subj)) continue;
old = (Thread *) hash_lookup(subj, &subj_table);
if (old == cur) continue;
if (!prev)
root->child = cur->next;
else
prev->next = cur->next;
cur->next = NULL;
if (!old->msgdata && !cur->msgdata) {
for (child = old->child; child->next; child = child->next);
child->next = cur->child;
for (child = cur->child; child; child = child->next)
child->parent = old;
cur->child = NULL;
}
else if (!old->msgdata ||
(cur->msgdata && cur->msgdata->is_refwd &&
!old->msgdata->is_refwd)) {
thread_adopt_child(old, cur);
}
else {
Thread *new = (*newnode)++;
new->msgdata = old->msgdata;
new->child = old->child;
new->next = NULL;
for (child = new->child; child; child = child->next)
child->parent = new;
cur->parent = old;
new->parent = old;
old->msgdata = NULL;
old->child = cur;
cur->next = new;
}
cur = prev;
}
free_hash_table(&subj_table, NULL);
}
static void index_thread_free(Thread *thread)
{
Thread *child;
if (thread->msgdata) index_msgdata_free(thread->msgdata);
child = thread->child;
while (child) {
index_thread_free(child);
child = child->next;
}
}
static int _index_thread_search(Thread *thread, int (*searchproc) (MsgData *))
{
Thread *child;
if (thread->msgdata && searchproc(thread->msgdata)) return 1;
child = thread->child;
while (child) {
if (_index_thread_search(child, searchproc)) return 1;
child = child->next;
}
return 0;
}
static void index_thread_search(Thread *root, int (*searchproc) (MsgData *))
{
Thread *cur, *prev, *next;
for (prev = NULL, cur = root->child, next = cur->next;
cur;
prev = cur, cur= next, next = (cur ? cur->next : NULL)) {
if (!_index_thread_search(cur, searchproc)) {
if (!prev)
root->child = cur->next;
else
prev->next = cur->next;
index_thread_free(cur);
cur = prev;
}
}
}
static void _index_thread_ref(unsigned *msgno_list, int nmsg,
struct sortcrit loadcrit[],
int (*searchproc) (MsgData *),
struct sortcrit sortcrit[], int usinguid)
{
MsgData *msgdata, *freeme, *md;
int tref, nnode;
Thread *newnode;
struct hash_table id_table;
struct rootset rootset;
freeme = msgdata = index_msgdata_load(msgno_list, nmsg, loadcrit);
for (md = msgdata, tref = 0; md; md = md->next)
tref += md->nref;
nnode = (int) (1.5 * nmsg + 1 + tref);
rootset.root = (Thread *) xmalloc(nnode * sizeof(Thread));
memset(rootset.root, 0, nnode * sizeof(Thread));
newnode = rootset.root + 1;
construct_hash_table(&id_table, nmsg + tref, 1);
ref_link_messages(msgdata, &newnode, &id_table);
rootset.nroot = 0;
hash_enumerate(&id_table, (void (*)(char*,void*,void*)) ref_gather_orphans,
&rootset);
free_hash_table(&id_table, NULL);
ref_prune_tree(rootset.root);
ref_sort_root(rootset.root);
ref_group_subjects(rootset.root, rootset.nroot, &newnode);
if (searchproc) index_thread_search(rootset.root, searchproc);
if (sortcrit) index_thread_sort(rootset.root, sortcrit);
index_thread_print(rootset.root, usinguid);
free(rootset.root);
free(freeme);
}
static void index_thread_ref(unsigned *msgno_list, int nmsg, int usinguid)
{
struct sortcrit loadcrit[] = {{ LOAD_IDS, 0, {{NULL,NULL}} },
{ SORT_SUBJECT, 0, {{NULL,NULL}} },
{ SORT_DATE, 0, {{NULL,NULL}} },
{ SORT_SEQUENCE, 0, {{NULL,NULL}} }};
struct sortcrit sortcrit[] = {{ SORT_DATE, 0, {{NULL,NULL}} },
{ SORT_SEQUENCE, 0, {{NULL,NULL}} }};
_index_thread_ref(msgno_list, nmsg, loadcrit, NULL, sortcrit, usinguid);
}
char *index_get_msgid(struct mailbox *mailbox __attribute__((unused)),
unsigned msgno)
{
const char *cacheitem;
int cachelen;
char *env;
char *envtokens[NUMENVTOKENS];
char *msgid;
cacheitem = cache_base + CACHE_OFFSET(msgno);
cachelen = CACHE_ITEM_LEN(cacheitem);
env = xstrndup(cacheitem + CACHE_ITEM_SIZE_SKIP + 1, cachelen - 2);
parse_cached_envelope(env, envtokens, VECTOR_SIZE(envtokens));
msgid = envtokens[ENV_MSGID] ? xstrdup(envtokens[ENV_MSGID]) : NULL;
free(env);
return msgid;
}
static void massage_header(char *hdr)
{
int n = 0;
char *p, c;
for (p = hdr; *p; p++) {
if (*p == ' ' || *p == '\t' || *p == '\r') {
if (!n || *(p+1) == '\n') {
continue;
}
c = ' ';
}
else if (*p == '\n') {
if (*(p+1) == ' ' || *(p+1) == '\t') {
continue;
}
break;
}
else
c = *p;
hdr[n++] = c;
}
hdr[n] = '\0';
}
static char *parse_nstring(char **str)
{
char *cp = *str, *val;
if (*cp == '"') {
val = ++cp;
do {
cp = strchr(cp, '"');
} while (*(cp-1) == '\\');
*cp++ = '\0';
}
else {
val = NULL;
cp += 3;
}
*str = cp;
return val;
}
static void parse_env_address(char *str, struct address *addr)
{
str++;
addr->name = parse_nstring(&str);
str++;
addr->route = parse_nstring(&str);
str++;
addr->mailbox = parse_nstring(&str);
str++;
addr->domain = parse_nstring(&str);
}
extern struct nntp_overview *index_overview(struct mailbox *mailbox,
unsigned msgno)
{
static struct nntp_overview over;
static char *env = NULL, *from = NULL, *hdr = NULL;
static int envsize = 0, fromsize = 0, hdrsize = 0;
const char *cacheitem;
int size;
char *envtokens[NUMENVTOKENS];
struct address addr = { NULL, NULL, NULL, NULL, NULL, NULL };
cacheitem = cache_base + CACHE_OFFSET(msgno);
size = CACHE_ITEM_LEN(cacheitem) - 2 + 1;
if (envsize < size) {
envsize = size;
env = xrealloc(env, envsize);
}
strlcpy(env, cacheitem + CACHE_ITEM_SIZE_SKIP + 1, size);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
size = CACHE_ITEM_LEN(cacheitem) + 1;
if (hdrsize < size) {
hdrsize = size;
hdr = xrealloc(hdr, hdrsize);
}
strlcpy(hdr, cacheitem + CACHE_ITEM_SIZE_SKIP, size);
parse_cached_envelope(env, envtokens, VECTOR_SIZE(envtokens));
over.uid = UID(msgno);
over.bytes = SIZE(msgno);
over.lines = index_getlines(mailbox, msgno);
over.date = envtokens[ENV_DATE];
over.msgid = envtokens[ENV_MSGID];
if ((over.subj = envtokens[ENV_SUBJECT]))
massage_header(over.subj);
if (envtokens[ENV_FROM])
parse_env_address(envtokens[ENV_FROM], &addr);
if (addr.mailbox && addr.domain) {
size = (addr.name ? strlen(addr.name) + 3 : 0) +
strlen(addr.mailbox) + strlen(addr.domain) + 4;
if (fromsize < size) {
fromsize = size;
from = xrealloc(from, fromsize);
}
from[0] = '\0';
if (addr.name) sprintf(from, "\"%s\" ", addr.name);
snprintf(from + strlen(from), fromsize - strlen(from),
"<%s@%s>", addr.mailbox, addr.domain);
over.from = from;
}
else
over.from = NULL;
if ((over.ref = stristr(hdr, "references:"))) {
over.ref += 11;
massage_header(over.ref);
}
return &over;
}
extern char *index_getheader(struct mailbox *mailbox, unsigned msgno,
char *hdr)
{
static const char *msg_base = 0;
static unsigned long msg_size = 0;
struct strlist headers = { NULL, NULL, NULL, NULL };
static char *alloc = NULL;
static int allocsize = 0;
const char *cacheitem;
unsigned size;
char *buf;
headers.s = hdr;
if (msg_base) {
mailbox_unmap_message(NULL, 0, &msg_base, &msg_size);
msg_base = 0;
msg_size = 0;
}
if (mailbox_cached_header(hdr) != BIT32_MAX) {
cacheitem = cache_base + CACHE_OFFSET(msgno);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
cacheitem = CACHE_ITEM_NEXT(cacheitem);
size = CACHE_ITEM_LEN(cacheitem);
if (allocsize < size+2) {
allocsize = size+100;
alloc = xrealloc(alloc, allocsize);
}
memcpy(alloc, cacheitem+CACHE_ITEM_SIZE_SKIP, size);
alloc[size] = '\0';
buf = alloc;
}
else {
if (mailbox_map_message(mailbox, UID(msgno), &msg_base, &msg_size))
return NULL;
buf = index_readheader(msg_base, msg_size, mailbox->format, 0,
HEADER_SIZE(msgno));
}
index_pruneheader(buf, &headers, NULL);
if (*buf) {
buf += strlen(hdr) + 1;
massage_header(buf);
}
return buf;
}
extern unsigned long index_getsize(struct mailbox *mailbox __attribute__((unused)),
unsigned msgno)
{
return SIZE(msgno);
}
extern unsigned long index_getlines(struct mailbox *mailbox, unsigned msgno)
{
unsigned long lines = CONTENT_LINES(msgno);
if (lines == BIT32_MAX) {
int r;
char fname[MAX_MAILBOX_PATH+1];
FILE *msgfile;
char buf[4096];
struct index_record record;
lines = 0;
r = mailbox_lock_index(mailbox);
if (r) return lines;
r = mailbox_read_index_record(mailbox, msgno, &record);
if (r) goto done;
strlcpy(fname, mailbox->path, sizeof(fname));
strlcat(fname, "/", sizeof(fname));
mailbox_message_get_fname(mailbox, record.uid,
fname + strlen(fname),
sizeof(fname) - strlen(fname));
msgfile = fopen(fname, "r");
if (!msgfile) goto done;
while (fgets(buf, sizeof(buf), msgfile)) {
if (buf[0] == '\r' && buf[1] == '\n') {
break;
}
}
while (fgets(buf, sizeof(buf), msgfile)) {
while (buf[strlen(buf)-1] != '\n' &&
fgets(buf, sizeof(buf), msgfile));
lines++;
}
fclose(msgfile);
record.content_lines = lines;
record.last_updated = time(0);
r = mailbox_write_index_record(mailbox, msgno, &record, 1);
done:
mailbox_unlock_index(mailbox);
}
return lines;
}