/* dlist.c - list protocol for dump and sync
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <syslog.h>
#include <string.h>
#include <sys/wait.h>
#include <dirent.h>
#include "global.h"
#include "assert.h"
#include "mboxlist.h"
#include "mailbox.h"
#include "quota.h"
#include "xmalloc.h"
#include "seen.h"
#include "mboxname.h"
#include "map.h"
#include "imapd.h"
#include "message.h"
#include "util.h"
#include "prot.h"
/* generated headers are not necessarily in current directory */
#include "imap/imap_err.h"
#include "dlist.h"
/* Parse routines */
static const char *lastkey = NULL;
static void printfile(struct protstream *out, const struct dlist *dl)
{
struct stat sbuf;
FILE *f;
unsigned long size;
struct message_guid guid2;
const char *msg_base = NULL;
size_t msg_len = 0;
assert(dlist_isfile(dl));
f = fopen(dl->sval, "r");
if (!f) {
syslog(LOG_ERR, "IOERROR: Failed to read file %s", dl->sval);
prot_printf(out, "NIL");
return;
}
if (fstat(fileno(f), &sbuf) == -1) {
syslog(LOG_ERR, "IOERROR: Failed to stat file %s", dl->sval);
prot_printf(out, "NIL");
fclose(f);
return;
}
size = sbuf.st_size;
if (size != dl->nval) {
syslog(LOG_ERR, "IOERROR: Size mismatch %s (%lu != " MODSEQ_FMT ")",
dl->sval, size, dl->nval);
prot_printf(out, "NIL");
fclose(f);
return;
}
map_refresh(fileno(f), 1, &msg_base, &msg_len, sbuf.st_size,
"new message", 0);
message_guid_generate(&guid2, msg_base, msg_len);
if (!message_guid_equal(&guid2, dl->gval)) {
syslog(LOG_ERR, "IOERROR: GUID mismatch %s",
dl->sval);
prot_printf(out, "NIL");
fclose(f);
map_free(&msg_base, &msg_len);
return;
}
prot_printf(out, "%%{");
prot_printastring(out, dl->part);
prot_printf(out, " ");
prot_printastring(out, message_guid_encode(dl->gval));
prot_printf(out, " %lu}\r\n", size);
prot_write(out, msg_base, msg_len);
fclose(f);
map_free(&msg_base, &msg_len);
}
/* XXX - these two functions should be out in append.c or reserve.c
* or something more general */
EXPORTED const char *dlist_reserve_path(const char *part, int isarchive, int isbackup,
const struct message_guid *guid)
{
static char buf[MAX_MAILBOX_PATH];
const char *base;
/* part must be a configured partition name on this server */
if (isbackup) {
base = config_backupstagingpath();
}
else {
base = isarchive ? config_archivepartitiondir(part)
: config_partitiondir(part);
}
/* we expect to have a base at this point, so let's assert that */
assert(base != NULL);
snprintf(buf, MAX_MAILBOX_PATH, "%s/sync./%lu/%s",
base, (unsigned long)getpid(),
message_guid_encode(guid));
/* gotta make sure we can create files */
if (cyrus_mkdir(buf, 0755)) {
/* it's going to fail later, but at least this will help */
syslog(LOG_ERR, "IOERROR: failed to create %s/sync./%lu/ for reserve: %m",
base, (unsigned long)getpid());
}
return buf;
}
static int reservefile(struct protstream *in, const char *part,
struct message_guid *guid, unsigned long size,
int isbackup, const char **fname)
{
FILE *file;
char buf[8192+1];
int r = 0;
/* XXX - write to a temporary file then move in to place! */
*fname = dlist_reserve_path(part, /*isarchive*/0, isbackup, guid);
/* remove any duplicates if they're still here */
unlink(*fname);
file = fopen(*fname, "w+");
if (!file) {
syslog(LOG_ERR,
"IOERROR: failed to upload file %s", message_guid_encode(guid));
r = IMAP_IOERROR;
/* Note: we still read the file's data from the wire,
* to avoid losing protocol sync */
}
/* XXX - calculate sha1 on the fly? */
while (size) {
size_t n = prot_read(in, buf, size > 8192 ? 8192 : size);
if (!n) {
syslog(LOG_ERR,
"IOERROR: reading message: unexpected end of file");
r = IMAP_IOERROR;
break;
}
size -= n;
if (fwrite(buf, 1, n, file) != n) {
syslog(LOG_ERR, "IOERROR: writing to file '%s': %m", *fname);
r = IMAP_IOERROR;
break;
}
}
if (r)
goto error;
/* Make sure that message flushed to disk just incase mmap has problems */
fflush(file);
if (ferror(file)) {
r = IMAP_IOERROR;
goto error;
}
if (fsync(fileno(file)) < 0) {
syslog(LOG_ERR, "IOERROR: fsyncing file '%s': %m", *fname);
r = IMAP_IOERROR;
goto error;
}
fclose(file);
return 0;
error:
if (file) {
fclose(file);
unlink(*fname);
*fname = NULL;
}
return r;
}
/* DLIST STUFF */
EXPORTED void dlist_stitch(struct dlist *parent, struct dlist *child)
{
assert(!child->next);
if (parent->tail) {
parent->tail->next = child;
parent->tail = child;
}
else {
parent->head = parent->tail = child;
}
}
EXPORTED void dlist_unstitch(struct dlist *parent, struct dlist *child)
{
struct dlist *prev = NULL;
struct dlist *replace = NULL;
/* find old record */
for (replace = parent->head; replace; replace = replace->next) {
if (replace == child) break;
prev = replace;
}
assert(replace);
if (prev) prev->next = child->next;
else parent->head = child->next;
if (parent->tail == child) parent->tail = prev;
child->next = NULL;
}
static struct dlist *dlist_child(struct dlist *dl, const char *name)
{
struct dlist *i = xzmalloc(sizeof(struct dlist));
if (name) i->name = xstrdup(name);
i->type = DL_NIL;
if (dl)
dlist_stitch(dl, i);
return i;
}
static void _dlist_free_children(struct dlist *dl)
{
struct dlist *next;
struct dlist *i;
if (!dl) return;
i = dl->head;
while (i) {
next = i->next;
dlist_free(&i);
i = next;
}
dl->head = dl->tail = NULL;
}
static void _dlist_clean(struct dlist *dl)
{
if (!dl) return;
/* remove any children */
_dlist_free_children(dl);
/* clean out values */
free(dl->part);
dl->part = NULL;
free(dl->sval);
dl->sval = NULL;
free(dl->gval);
dl->gval = NULL;
dl->nval = 0;
}
void dlist_makeatom(struct dlist *dl, const char *val)
{
if (!dl) return;
_dlist_clean(dl);
if (val) {
dl->type = DL_ATOM;
dl->sval = xstrdup(val);
dl->nval = strlen(val);
}
else
dl->type = DL_NIL;
}
void dlist_makeflag(struct dlist *dl, const char *val)
{
if (!dl) return;
_dlist_clean(dl);
if (val) {
dl->type = DL_FLAG;
dl->sval = xstrdup(val);
dl->nval = strlen(val);
}
else
dl->type = DL_NIL;
}
void dlist_makenum32(struct dlist *dl, uint32_t val)
{
if (!dl) return;
_dlist_clean(dl);
dl->type = DL_NUM;
dl->nval = val;
}
void dlist_makenum64(struct dlist *dl, bit64 val)
{
if (!dl) return;
_dlist_clean(dl);
dl->type = DL_NUM;
dl->nval = val;
}
void dlist_makedate(struct dlist *dl, time_t val)
{
if (!dl) return;
_dlist_clean(dl);
dl->type = DL_DATE;
dl->nval = val;
}
void dlist_makehex64(struct dlist *dl, bit64 val)
{
if (!dl) return;
_dlist_clean(dl);
dl->type = DL_HEX;
dl->nval = val;
}
void dlist_makeguid(struct dlist *dl, const struct message_guid *guid)
{
if (!dl) return;
_dlist_clean(dl);
if (guid) {
dl->type = DL_GUID,
dl->gval = xzmalloc(sizeof(struct message_guid));
message_guid_copy(dl->gval, guid);
}
else
dl->type = DL_NIL;
}
void dlist_makefile(struct dlist *dl,
const char *part, const struct message_guid *guid,
unsigned long size, const char *fname)
{
if (!dl) return;
_dlist_clean(dl);
if (part && guid && fname) {
dl->type = DL_FILE;
dl->gval = xzmalloc(sizeof(struct message_guid));
message_guid_copy(dl->gval, guid);
dl->sval = xstrdup(fname);
dl->nval = size;
dl->part = xstrdup(part);
}
else
dl->type = DL_NIL;
}
EXPORTED void dlist_makemap(struct dlist *dl, const char *val, size_t len)
{
if (!dl) return;
_dlist_clean(dl);
if (val) {
dl->type = DL_BUF;
/* WARNING - DO NOT replace this with xstrndup - the
* data may be binary, and xstrndup does not copy
* binary data correctly - but we still want to NULL
* terminate for non-binary data */
dl->sval = xmalloc(len+1);
memcpy(dl->sval, val, len);
dl->sval[len] = '\0'; /* make it string safe too */
dl->nval = len;
}
else
dl->type = DL_NIL;
}
EXPORTED struct dlist *dlist_newkvlist(struct dlist *parent, const char *name)
{
struct dlist *dl = dlist_child(parent, name);
dl->type = DL_KVLIST;
return dl;
}
EXPORTED struct dlist *dlist_newlist(struct dlist *parent, const char *name)
{
struct dlist *dl = dlist_child(parent, name);
dl->type = DL_ATOMLIST;
return dl;
}
EXPORTED struct dlist *dlist_newpklist(struct dlist *parent, const char *name)
{
struct dlist *dl = dlist_child(parent, name);
dl->type = DL_ATOMLIST;
dl->nval = 1;
return dl;
}
EXPORTED struct dlist *dlist_setatom(struct dlist *parent, const char *name, const char *val)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makeatom(dl, val);
return dl;
}
EXPORTED struct dlist *dlist_setflag(struct dlist *parent, const char *name, const char *val)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makeflag(dl, val);
return dl;
}
EXPORTED struct dlist *dlist_setnum64(struct dlist *parent, const char *name, bit64 val)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makenum64(dl, val);
return dl;
}
EXPORTED struct dlist *dlist_setnum32(struct dlist *parent, const char *name, uint32_t val)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makenum32(dl, val);
return dl;
}
EXPORTED struct dlist *dlist_setdate(struct dlist *parent, const char *name, time_t val)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makedate(dl, val);
return dl;
}
EXPORTED struct dlist *dlist_sethex64(struct dlist *parent, const char *name, bit64 val)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makehex64(dl, val);
return dl;
}
EXPORTED struct dlist *dlist_setmap(struct dlist *parent, const char *name,
const char *val, size_t len)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makemap(dl, val, len);
return dl;
}
EXPORTED struct dlist *dlist_setguid(struct dlist *parent, const char *name,
const struct message_guid *guid)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makeguid(dl, guid);
return dl;
}
EXPORTED struct dlist *dlist_setfile(struct dlist *parent, const char *name,
const char *part, const struct message_guid *guid,
size_t size, const char *fname)
{
struct dlist *dl = dlist_child(parent, name);
dlist_makefile(dl, part, guid, size, fname);
return dl;
}
static struct dlist *dlist_updatechild(struct dlist *parent, const char *name)
{
struct dlist *dl = dlist_getchild(parent, name);
if (!dl) dl = dlist_child(parent, name);
return dl;
}
struct dlist *dlist_updateatom(struct dlist *parent, const char *name, const char *val)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makeatom(dl, val);
return dl;
}
struct dlist *dlist_updateflag(struct dlist *parent, const char *name, const char *val)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makeflag(dl, val);
return dl;
}
struct dlist *dlist_updatenum64(struct dlist *parent, const char *name, bit64 val)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makenum64(dl, val);
return dl;
}
struct dlist *dlist_updatenum32(struct dlist *parent, const char *name, uint32_t val)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makenum32(dl, val);
return dl;
}
struct dlist *dlist_updatedate(struct dlist *parent, const char *name, time_t val)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makedate(dl, val);
return dl;
}
struct dlist *dlist_updatehex64(struct dlist *parent, const char *name, bit64 val)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makehex64(dl, val);
return dl;
}
struct dlist *dlist_updatemap(struct dlist *parent, const char *name,
const char *val, size_t len)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makemap(dl, val, len);
return dl;
}
struct dlist *dlist_updateguid(struct dlist *parent, const char *name,
const struct message_guid *guid)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makeguid(dl, guid);
return dl;
}
struct dlist *dlist_updatefile(struct dlist *parent, const char *name,
const char *part, const struct message_guid *guid,
size_t size, const char *fname)
{
struct dlist *dl = dlist_updatechild(parent, name);
dlist_makefile(dl, part, guid, size, fname);
return dl;
}
EXPORTED void dlist_print(const struct dlist *dl, int printkeys,
struct protstream *out)
{
struct dlist *di;
if (printkeys) {
prot_printastring(out, dl->name);
prot_putc(' ', out);
}
switch (dl->type) {
case DL_NIL:
prot_printf(out, "NIL");
break;
case DL_ATOM:
prot_printastring(out, dl->sval);
break;
case DL_FLAG:
prot_printf(out, "%s", dl->sval);
break;
case DL_NUM:
case DL_DATE: /* for now, we will format it later */
prot_printf(out, "%llu", dl->nval);
break;
case DL_FILE:
printfile(out, dl);
break;
case DL_BUF:
if (strlen(dl->sval) == dl->nval)
prot_printastring(out, dl->sval);
else
prot_printliteral(out, dl->sval, dl->nval);
break;
case DL_GUID:
prot_printf(out, "%s", message_guid_encode(dl->gval));
break;
case DL_HEX:
{
char buf[17];
snprintf(buf, 17, "%016llx", dl->nval);
prot_printf(out, "%s", buf);
}
break;
case DL_KVLIST:
prot_printf(out, "%%(");
for (di = dl->head; di; di = di->next) {
dlist_print(di, 1, out);
if (di->next) {
prot_printf(out, " ");
}
}
prot_printf(out, ")");
break;
case DL_ATOMLIST:
prot_printf(out, "(");
for (di = dl->head; di; di = di->next) {
dlist_print(di, dl->nval, out);
if (di->next)
prot_printf(out, " ");
}
prot_printf(out, ")");
break;
}
}
EXPORTED void dlist_printbuf(const struct dlist *dl, int printkeys, struct buf *outbuf)
{
struct protstream *outstream;
outstream = prot_writebuf(outbuf);
dlist_print(dl, printkeys, outstream);
prot_flush(outstream);
prot_free(outstream);
}
EXPORTED void dlist_unlink_files(struct dlist *dl)
{
struct dlist *i;
if (!dl) return;
for (i = dl->head; i; i = i->next) {
dlist_unlink_files(i);
}
if (dl->type != DL_FILE) return;
if (!dl->sval) return;
syslog(LOG_DEBUG, "%s: unlinking %s", __func__, dl->sval);
unlink(dl->sval);
}
EXPORTED void dlist_free(struct dlist **dlp)
{
if (!*dlp) return;
_dlist_clean(*dlp);
free((*dlp)->name);
free(*dlp);
*dlp = NULL;
}
struct dlist_stack_node {
const struct dlist *dl;
int printkeys;
struct dlist_stack_node *next;
};
struct dlist_print_iter {
int printkeys;
struct dlist_stack_node *parent;
const struct dlist *next;
};
EXPORTED struct dlist_print_iter *dlist_print_iter_new(const struct dlist *dl, int printkeys)
{
struct dlist_print_iter *iter = xzmalloc(sizeof *iter);
iter->printkeys = printkeys;
iter->next = dl;
return iter;
}
EXPORTED const char *dlist_print_iter_step(struct dlist_print_iter *iter, struct buf *outbuf)
{
/* already finished */
if (!iter->next) return NULL;
buf_reset(outbuf);
/* Bundle short steps together to minimise call overhead.
* Note that outbuf can grow significantly longer than this limit, if a
* single item in the dlist is very long (e.g. a message), but then it
* won't bundle up more than that.
*/
while (iter->next != NULL && buf_len(outbuf) < 1024) {
const struct dlist *curr = iter->next;
struct dlist_stack_node *parent = NULL;
int descend = 0;
/* output */
switch (curr->type) {
case DL_KVLIST:
case DL_ATOMLIST:
// XXX should use equiv to "prot_printastring" for curr->name
if (iter->printkeys)
buf_printf(outbuf, "%s ", curr->name);
buf_appendcstr(outbuf, curr->type == DL_KVLIST ? "%(" : "(");
if (curr->head) {
descend = 1;
}
else {
buf_putc(outbuf, ')');
if (curr->next)
buf_putc(outbuf, ' ');
}
break;
default:
dlist_printbuf(curr, iter->printkeys, outbuf);
if (curr->next)
buf_putc(outbuf, ' ');
break;
}
/* increment */
if (descend) {
parent = xmalloc(sizeof *parent);
parent->printkeys = iter->printkeys;
parent->dl = curr;
parent->next = iter->parent;
iter->parent = parent;
iter->next = curr->head;
// XXX can this always be 1? we know an atom list here is non-empty
iter->printkeys = curr->type == DL_KVLIST ? 1 : curr->nval;
}
else if (curr->next) {
iter->next = curr->next;
}
else if (iter->parent) {
/* multiple parents might be ending at the same point
* don't mistake one parent ending for end of entire tree
*/
do {
buf_putc(outbuf, ')');
parent = iter->parent;
iter->parent = iter->parent->next;
iter->next = parent->dl->next;
iter->printkeys = parent->printkeys;
free(parent);
if (iter->next) {
/* found an unfinished dlist, stop closing parents */
buf_putc(outbuf, ' ');
break;
}
} while (iter->parent);
}
else {
iter->next = NULL;
}
}
/* and return */
return buf_cstringnull(outbuf);
}
EXPORTED void dlist_print_iter_free(struct dlist_print_iter **iterp)
{
struct dlist_print_iter *iter = *iterp;
struct dlist_stack_node *tmp = NULL;
*iterp = NULL;
while (iter->parent) {
tmp = iter->parent;
iter->parent = iter->parent->next;
free(tmp);
}
free(iter);
}
struct dlistsax_state {
const char *base;
const char *p;
const char *end;
dlistsax_cb_t *proc;
int depth;
struct dlistsax_data d;
struct buf gbuf;
};
static int _parseqstring(struct dlistsax_state *s, struct buf *buf)
{
buf->len = 0;
/* get over the first quote */
if (*s->p++ != '"') return IMAP_INVALID_IDENTIFIER;
while (s->p < s->end) {
/* found the end quote */
if (*s->p == '"') {
s->p++;
return 0;
}
/* backslash just quotes the next char, no matter what it is */
if (*s->p == '\\') {
s->p++;
if (s->p == s->end) break;
/* fall through */
}
buf_putc(buf, *s->p++);
}
return IMAP_INVALID_IDENTIFIER;
}
static int _parseliteral(struct dlistsax_state *s, struct buf *buf)
{
size_t len = 0;
if (*s->p++ != '{') return IMAP_INVALID_IDENTIFIER;
while (s->p < s->end) {
if (cyrus_isdigit(*s->p)) {
len = (len * 10) + (*s->p++ - '0');
}
else if (*s->p == '}') {
if (s->p + 3 + len >= s->end) break;
if (s->p[1] != '\r') break;
if (s->p[2] != '\n') break;
buf_setmap(buf, s->p+3, len);
s->p += len = 3;
return 0;
}
}
return IMAP_INVALID_IDENTIFIER;
}
static int _parseitem(struct dlistsax_state *s, struct buf *buf)
{
const char *sp;
if (*s->p == '"')
return _parseqstring(s, buf);
else if (*s->p == '{')
return _parseliteral(s, buf);
sp = memchr(s->p, ' ', s->end - s->p);
if (!sp) sp = s->end;
while (sp[-1] == ')' && sp > s->p) sp--;
/* this is much faster because it doesn't do a reset and check the MMAP flag */
buf->len = 0;
buf_appendmap(buf, s->p, sp - s->p);
s->p = sp;
return 0; /* this could be the last thing, so end is OK */
}
static int _parsesax(struct dlistsax_state *s, int parsekey)
{
int r = 0;
s->depth++;
/* handle the key if wanted */
if (parsekey) {
r = _parseitem(s, &s->d.kbuf);
if (r) return r;
if (s->p >= s->end) return IMAP_INVALID_IDENTIFIER;
if (*s->p == ' ') s->p++;
else return IMAP_INVALID_IDENTIFIER;
}
else {
s->d.kbuf.len = 0;
}
if (s->p >= s->end) return IMAP_INVALID_IDENTIFIER;
/* check what sort of value we have */
if (*s->p == '(') {
r = s->proc(DLISTSAX_LISTSTART, &s->d);
if (r) return r;
s->p++;
if (s->p >= s->end) return IMAP_INVALID_IDENTIFIER;
while (*s->p != ')') {
r = _parsesax(s, 0);
if (r) return r;
if (*s->p == ')') break;
if (*s->p == ' ') s->p++;
else return IMAP_INVALID_IDENTIFIER;
if (s->p >= s->end) return IMAP_INVALID_IDENTIFIER;
}
r = s->proc(DLISTSAX_LISTEND, &s->d);
if (r) return r;
s->p++;
}
else if (*s->p == '%') {
s->p++;
if (s->p >= s->end) return IMAP_INVALID_IDENTIFIER;
/* no whitespace allowed here */
if (*s->p == '(') {
r = s->proc(DLISTSAX_KVLISTSTART, &s->d);
if (r) return r;
s->p++;
if (s->p >= s->end) return IMAP_INVALID_IDENTIFIER;
while (*s->p != ')') {
r = _parsesax(s, 1);
if (r) return r;
if (*s->p == ')') break;
if (*s->p == ' ') s->p++;
else return IMAP_INVALID_IDENTIFIER;
if (s->p >= s->end) return IMAP_INVALID_IDENTIFIER;
}
r = s->proc(DLISTSAX_KVLISTEND, &s->d);
if (r) return r;
s->p++;
}
else {
/* unknown percent type */
return IMAP_INVALID_IDENTIFIER;
}
}
else {
r = _parseitem(s, &s->d.buf);
if (r) return r;
r = s->proc(DLISTSAX_STRING, &s->d);
if (r) return r;
}
s->depth--;
/* success */
return 0;
}
EXPORTED int dlist_parsesax(const char *base, size_t len, int parsekey,
dlistsax_cb_t *proc, void *rock)
{
static struct dlistsax_state state;
int r;
state.base = base;
state.p = base;
state.end = base + len;
state.proc = proc;
state.d.rock = rock;
r = _parsesax(&state, parsekey);
if (r) return r;
if (state.p < state.end)
return IMAP_IOERROR;
return 0;
}
static char next_nonspace(struct protstream *in, char c)
{
while (Uisspace(c)) c = prot_getc(in);
return c;
}
EXPORTED int dlist_parse(struct dlist **dlp, int parsekey, int isbackup,
struct protstream *in)
{
struct dlist *dl = NULL;
static struct buf kbuf;
static struct buf vbuf;
int c;
/* handle the key if wanted */
if (parsekey) {
c = getastring(in, NULL, &kbuf);
c = next_nonspace(in, c);
}
else {
buf_setcstr(&kbuf, "");
c = prot_getc(in);
}
/* connection dropped? */
if (c == EOF) goto fail;
/* check what sort of value we have */
if (c == '(') {
dl = dlist_newlist(NULL, kbuf.s);
c = next_nonspace(in, ' ');
while (c != ')') {
struct dlist *di = NULL;
prot_ungetc(c, in);
c = dlist_parse(&di, 0, isbackup, in);
if (di) dlist_stitch(dl, di);
c = next_nonspace(in, c);
if (c == EOF) goto fail;
}
c = prot_getc(in);
}
else if (c == '%') {
/* no whitespace allowed here */
c = prot_getc(in);
if (c == '(') {
dl = dlist_newkvlist(NULL, kbuf.s);
c = next_nonspace(in, ' ');
while (c != ')') {
struct dlist *di = NULL;
prot_ungetc(c, in);
c = dlist_parse(&di, 1, isbackup, in);
if (di) dlist_stitch(dl, di);
c = next_nonspace(in, c);
if (c == EOF) goto fail;
}
}
else if (c == '{') {
struct message_guid tmp_guid;
static struct buf pbuf, gbuf;
unsigned size = 0;
const char *fname;
c = getastring(in, NULL, &pbuf);
if (c != ' ') goto fail;
c = getastring(in, NULL, &gbuf);
if (c != ' ') goto fail;
c = getuint32(in, &size);
if (c != '}') goto fail;
c = prot_getc(in);
if (c == '\r') c = prot_getc(in);
if (c != '\n') goto fail;
if (!message_guid_decode(&tmp_guid, gbuf.s)) goto fail;
if (reservefile(in, pbuf.s, &tmp_guid, size, isbackup, &fname)) goto fail;
dl = dlist_setfile(NULL, kbuf.s, pbuf.s, &tmp_guid, size, fname);
/* file literal */
}
else {
/* unknown percent type */
goto fail;
}
c = prot_getc(in);
}
else if (c == '{') {
prot_ungetc(c, in);
/* could be binary in a literal */
c = getbastring(in, NULL, &vbuf);
dl = dlist_setmap(NULL, kbuf.s, vbuf.s, vbuf.len);
}
else if (c == '\\') { /* special case for flags */
prot_ungetc(c, in);
c = getastring(in, NULL, &vbuf);
dl = dlist_setflag(NULL, kbuf.s, vbuf.s);
}
else {
prot_ungetc(c, in);
c = getnastring(in, NULL, &vbuf);
dl = dlist_setatom(NULL, kbuf.s, vbuf.s);
}
/* success */
*dlp = dl;
return c;
fail:
dlist_free(&dl);
return EOF;
}
EXPORTED int dlist_parse_asatomlist(struct dlist **dlp, int parsekey,
struct protstream *in)
{
int c = dlist_parse(dlp, parsekey, 0, in);
/* make a list with one item */
if (*dlp && !dlist_isatomlist(*dlp)) {
struct dlist *tmp = dlist_newlist(NULL, "");
dlist_stitch(tmp, *dlp);
*dlp = tmp;
}
return c;
}
EXPORTED int dlist_parsemap(struct dlist **dlp, int parsekey, int isbackup,
const char *base, unsigned len)
{
struct protstream *stream;
int c;
struct dlist *dl = NULL;
stream = prot_readmap(base, len);
prot_setisclient(stream, 1); /* don't sync literals */
c = dlist_parse(&dl, parsekey, isbackup, stream);
prot_free(stream);
if (c != EOF) {
dlist_free(&dl);
return IMAP_IOERROR; /* failed to slurp entire buffer */
}
*dlp = dl;
return 0;
}
EXPORTED struct dlist *dlist_getchild(struct dlist *dl, const char *name)
{
struct dlist *i;
if (!dl) return NULL;
for (i = dl->head; i; i = i->next) {
if (i->name && !strcmp(name, i->name))
return i;
}
lastkey = name;
return NULL;
}
EXPORTED struct dlist *dlist_getchildn(struct dlist *dl, int num)
{
struct dlist *i;
if (!dl) return NULL;
for (i = dl->head; i && num; i = i->next)
num--;
return i;
}
/* duplicate the parent list as a new list, and then move @num
* of the children from the parent onto the new list */
EXPORTED struct dlist *dlist_splice(struct dlist *dl, int num)
{
struct dlist *ret = dlist_newlist(NULL, dl->name);
/* clone exact type */
ret->type = dl->type;
ret->nval = dl->nval;
if (num > 0) {
struct dlist *end = dlist_getchildn(dl, num - 1);
/* take the start of the list */
ret->head = dl->head;
/* leave the end (if any) */
if (end) {
ret->tail = end;
dl->head = end->next;
end->next = NULL;
}
else {
ret->tail = dl->tail;
dl->head = NULL;
dl->tail = NULL;
}
}
return ret;
}
EXPORTED void dlist_splat(struct dlist *parent, struct dlist *child)
{
struct dlist *prev = NULL;
struct dlist *replace;
/* find old record */
for (replace = parent->head; replace; replace = replace->next) {
if (replace == child) break;
prev = replace;
}
assert(replace);
if (child->head) {
/* stitch in children */
if (prev) prev->next = child->head;
else parent->head = child->head;
if (child->next) child->tail->next = child->next;
else parent->tail = child->tail;
}
else {
/* just remove the record */
if (prev) prev->next = child->next;
else parent->head = child->next;
if (!child->next) parent->tail = prev;
}
/* remove the node itself, carefully blanking out
* the now unlinked children */
child->head = NULL;
child->tail = NULL;
dlist_free(&child);
}
struct dlist *dlist_getkvchild_bykey(struct dlist *dl,
const char *key, const char *val)
{
struct dlist *i;
struct dlist *tmp;
if (!dl) return NULL;
for (i = dl->head; i; i = i->next) {
tmp = dlist_getchild(i, key);
if (tmp && !strcmp(tmp->sval, val))
return i;
}
return NULL;
}
int dlist_toatom(struct dlist *dl, const char **valp)
{
const char *str;
size_t len;
if (!dl) return 0;
/* atom can be NULL */
if (dl->type == DL_NIL) {
*valp = NULL;
return 1;
}
/* tomap always adds a trailing \0 */
if (!dlist_tomap(dl, &str, &len))
return 0;
/* got NULLs? */
if (dl->type == DL_BUF && strlen(str) != len)
return 0;
if (valp) *valp = str;
return 1;
}
HIDDEN int dlist_tomap(struct dlist *dl, const char **valp, size_t *lenp)
{
char tmp[30];
if (!dl) return 0;
switch (dl->type) {
case DL_NUM:
case DL_DATE:
snprintf(tmp, 30, "%llu", dl->nval);
dlist_makeatom(dl, tmp);
break;
case DL_HEX:
snprintf(tmp, 30, "%016llx", dl->nval);
dlist_makeatom(dl, tmp);
break;
case DL_GUID:
dlist_makeatom(dl, message_guid_encode(dl->gval));
break;
case DL_ATOM:
case DL_FLAG:
case DL_BUF:
break;
default:
return 0;
}
if (valp) *valp = dl->sval;
if (lenp) *lenp = dl->nval;
return 1;
}
/* ensure value is exactly one number */
static int dlist_tonum64(struct dlist *dl, bit64 *valp)
{
const char *end;
bit64 newval;
if (!dl) return 0;
switch (dl->type) {
case DL_ATOM:
case DL_BUF:
if (parsenum(dl->sval, &end, dl->nval, &newval))
return 0;
if (end - dl->sval != (int)dl->nval)
return 0;
/* successfully parsed - switch to a numeric value */
dlist_makenum64(dl, newval);
break;
case DL_NUM:
case DL_HEX:
case DL_DATE:
break;
default:
return 0;
}
if (valp) *valp = dl->nval;
return 1;
}
EXPORTED int dlist_tonum32(struct dlist *dl, uint32_t *valp)
{
bit64 v;
if (dlist_tonum64(dl, &v)) {
if (valp) *valp = (uint32_t)v;
return 1;
}
return 0;
}
int dlist_todate(struct dlist *dl, time_t *valp)
{
bit64 v;
if (dlist_tonum64(dl, &v)) {
if (valp) *valp = (time_t)v;
dl->type = DL_DATE;
return 1;
}
return 0;
}
static int dlist_tohex64(struct dlist *dl, bit64 *valp)
{
const char *end = NULL;
bit64 newval;
if (!dl) return 0;
switch (dl->type) {
case DL_ATOM:
case DL_BUF:
if (parsehex(dl->sval, &end, dl->nval, &newval))
return 0;
if (end - dl->sval != (int)dl->nval)
return 0;
/* successfully parsed - switch to a numeric value */
dlist_makehex64(dl, newval);
break;
case DL_NUM:
case DL_HEX:
case DL_DATE:
dl->type = DL_HEX;
break;
default:
return 0;
}
if (valp) *valp = dl->nval;
return 1;
}
EXPORTED int dlist_toguid(struct dlist *dl, struct message_guid **valp)
{
struct message_guid tmpguid;
if (!dl) return 0;
switch (dl->type) {
case DL_ATOM:
case DL_BUF:
if (dl->nval != 40)
return 0;
if (!message_guid_decode(&tmpguid, dl->sval))
return 0;
/* successfully parsed - switch to guid value */
dlist_makeguid(dl, &tmpguid);
break;
case DL_GUID:
break;
default:
return 0;
}
if (valp) *valp = dl->gval;
return 1;
}
EXPORTED int dlist_tofile(struct dlist *dl,
const char **partp, struct message_guid **guidp,
unsigned long *sizep, const char **fnamep)
{
if (!dlist_isfile(dl)) return 0;
if (guidp) *guidp = dl->gval;
if (sizep) *sizep = dl->nval;
if (fnamep) *fnamep = dl->sval;
if (partp) *partp = dl->part;
return 1;
}
EXPORTED int dlist_isatomlist(const struct dlist *dl)
{
if (!dl) return 0;
return (dl->type == DL_ATOMLIST);
}
int dlist_iskvlist(const struct dlist *dl)
{
if (!dl) return 0;
return (dl->type == DL_KVLIST);
}
int dlist_isfile(const struct dlist *dl)
{
if (!dl) return 0;
return (dl->type == DL_FILE);
}
/* XXX - these ones aren't const, because they can change
* things... */
int dlist_isnum(struct dlist *dl)
{
bit64 tmp;
if (!dl) return 0;
/* see if it can be parsed as a number */
return dlist_tonum64(dl, &tmp);
}
/* XXX - these ones aren't const, because they can change
* things... */
EXPORTED int dlist_ishex64(struct dlist *dl)
{
bit64 tmp;
if (!dl) return 0;
/* see if it can be parsed as a number */
return dlist_tohex64(dl, &tmp);
}
/* XXX - these ones aren't const, because they can change
* things... */
int dlist_isguid(struct dlist *dl)
{
struct message_guid *tmp = NULL;
if (!dl) return 0;
return dlist_toguid(dl, &tmp);
}
/* XXX - this stuff is all shitty, rationalise later */
EXPORTED bit64 dlist_num(struct dlist *dl)
{
bit64 v;
if (!dl) return 0;
if (dlist_tonum64(dl, &v))
return v;
return 0;
}
/* XXX - this stuff is all shitty, rationalise later */
EXPORTED const char *dlist_cstring(struct dlist *dl)
{
static char zerochar = '\0';
if (dl) {
const char *res = NULL;
dlist_toatom(dl, &res);
if (res) return res;
}
return &zerochar;
}
EXPORTED int dlist_getatom(struct dlist *parent, const char *name, const char **valp)
{
struct dlist *child = dlist_getchild(parent, name);
return dlist_toatom(child, valp);
}
EXPORTED int dlist_getnum32(struct dlist *parent, const char *name, uint32_t *valp)
{
struct dlist *child = dlist_getchild(parent, name);
return dlist_tonum32(child, valp);
}
EXPORTED int dlist_getnum64(struct dlist *parent, const char *name, bit64 *valp)
{
struct dlist *child = dlist_getchild(parent, name);
return dlist_tonum64(child, valp);
}
EXPORTED int dlist_getdate(struct dlist *parent, const char *name, time_t *valp)
{
struct dlist *child = dlist_getchild(parent, name);
return dlist_todate(child, valp);
}
EXPORTED int dlist_gethex64(struct dlist *parent, const char *name, bit64 *valp)
{
struct dlist *child = dlist_getchild(parent, name);
return dlist_tohex64(child, valp);
}
EXPORTED int dlist_getguid(struct dlist *parent, const char *name,
struct message_guid **valp)
{
struct dlist *child = dlist_getchild(parent, name);
return dlist_toguid(child, valp);
}
EXPORTED int dlist_getmap(struct dlist *parent, const char *name,
const char **valp, size_t *lenp)
{
struct dlist *child = dlist_getchild(parent, name);
return dlist_tomap(child, valp, lenp);
}
EXPORTED int dlist_getbuf(struct dlist *parent, const char *name,
struct buf *value)
{
const char *v = NULL;
size_t l = 0;
if (dlist_getmap(parent, name, &v, &l)) {
buf_init_ro(value, v, l);
return 1;
}
return 0;
}
int dlist_getfile(struct dlist *parent, const char *name,
const char **partp,
struct message_guid **guidp,
unsigned long *sizep,
const char **fnamep)
{
struct dlist *child = dlist_getchild(parent, name);
return dlist_tofile(child, partp, guidp, sizep, fnamep);
}
EXPORTED int dlist_getlist(struct dlist *dl, const char *name, struct dlist **valp)
{
struct dlist *i = dlist_getchild(dl, name);
if (!i) return 0;
*valp = i;
return 1;
}
EXPORTED const char *dlist_lastkey(void)
{
return lastkey;
}