#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "spool.h"
#include "prot.h"
#include "util.h"
#include "xmalloc.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "nntp_err.h"
#include "global.h"
#define HEADERCACHESIZE 4009
typedef struct Header header_t;
struct Header {
char *name;
int ncontents;
char *contents[1];
};
hdrcache_t spool_new_hdrcache()
{
int i;
hdrcache_t cache;
cache = (hdrcache_t) xmalloc(HEADERCACHESIZE * sizeof(header_t*));
if (!cache) return NULL;
for (i = 0; i < HEADERCACHESIZE; i++)
cache[i] = NULL;
return cache;
}
static int hashheader(char *header)
{
int x = 0;
for (; !iscntrl((int) *header) && (*header != ' ') && (*header != ':');
header++) {
x *= 256;
x += *header;
x %= HEADERCACHESIZE;
}
return x;
}
typedef enum {
NAME_START,
NAME,
COLON,
BODY_START,
BODY
} state;
enum {
NAMEINC = 128,
BODYINC = 1024
};
static int parseheader(struct protstream *fin, FILE *fout,
char **headname, char **contents,
const char **skipheaders)
{
int c;
static char *name = NULL, *body = NULL;
static int namelen = 0, bodylen = 0;
int off = 0;
state s = NAME_START;
int r = 0;
int reject8bit = config_getswitch(IMAPOPT_REJECT8BIT);
const char **skip = NULL;
if (namelen == 0) {
namelen += NAMEINC;
name = (char *) xrealloc(name, namelen * sizeof(char));
}
if (bodylen == 0) {
bodylen += BODYINC;
body = (char *) xrealloc(body, bodylen * sizeof(char));
}
while ((c = prot_getc(fin)) != EOF) {
switch (s) {
case NAME_START:
if (c == '.') {
int peek;
peek = prot_getc(fin);
prot_ungetc(peek, fin);
if (peek == '\r' || peek == '\n') {
r = 0;
goto ph_error;
}
}
if (c == '\r' || c == '\n') {
r = 0;
goto ph_error;
}
if (!((c >= 33 && c <= 57) || (c >= 59 && c <= 126))) {
r = IMAP_MESSAGE_BADHEADER;
goto ph_error;
}
name[0] = c;
off = 1;
s = NAME;
break;
case NAME:
if (c == ' ' || c == '\t' || c == ':') {
name[off] = '\0';
for (skip = skipheaders;
skip && *skip && strcasecmp(name, *skip); skip++);
if (!skip || !*skip) {
fputs(name, fout);
skip = NULL;
}
s = (c == ':' ? BODY_START : COLON);
break;
}
if (!((c >= 33 && c <= 57) || (c >= 59 && c <= 126))) {
r = IMAP_MESSAGE_BADHEADER;
goto ph_error;
}
name[off++] = c;
if (off >= namelen - 3) {
namelen += NAMEINC;
name = (char *) xrealloc(name, namelen);
}
break;
case COLON:
if (c == ':') {
s = BODY_START;
} else if (c != ' ' && c != '\t') {
while (c == '.') {
if (!skip) fputc(c, fout);
c = prot_getc(fin);
}
r = IMAP_MESSAGE_BADHEADER;
goto ph_error;
}
break;
case BODY_START:
if (c == ' ' || c == '\t')
break;
off = 0;
s = BODY;
case BODY:
if (c == '\r' || c == '\n') {
int peek;
peek = prot_getc(fin);
if (!skip) {
fputc('\r', fout);
fputc('\n', fout);
}
if (c == '\r' && peek == '\n') {
c = prot_getc(fin);
} else {
c = peek;
}
if (c != ' ' && c != '\t') {
body[off] = '\0';
prot_ungetc(c, fin);
goto got_header;
}
break;
} else {
if (c >= 0x80) {
if (reject8bit) {
r = IMAP_MESSAGE_CONTAINS8BIT;
goto ph_error;
} else {
}
}
body[off++] = c;
if (off >= bodylen - 3) {
bodylen += BODYINC;
body = (char *) xrealloc(body, bodylen);
}
}
}
if (s != NAME && !skip) fputc(c, fout);
}
ph_error:
prot_ungetc(c, fin);
if (headname != NULL) *headname = NULL;
if (contents != NULL) *contents = NULL;
return r;
got_header:
if (headname != NULL) *headname = xstrdup(name);
if (contents != NULL) *contents = xstrdup(body);
return 0;
}
void spool_cache_header(char *name, char *body, hdrcache_t cache)
{
int cl, clinit;
lcase(name);
clinit = cl = hashheader(name);
while (cache[cl] != NULL && strcmp(name, cache[cl]->name)) {
cl++;
cl %= HEADERCACHESIZE;
if (cl == clinit) break;
}
if (cache[cl]) {
cache[cl]->contents[cache[cl]->ncontents++] = body;
if (!(cache[cl]->ncontents % 8)) {
cache[cl] = (header_t *)
xrealloc(cache[cl],sizeof(header_t) +
((8 + cache[cl]->ncontents) * sizeof(char *)));
}
free(name);
} else {
cache[cl] = (header_t *) xmalloc(sizeof(header_t) + 8 * sizeof(char*));
cache[cl]->name = name;
cache[cl]->contents[0] = body;
cache[cl]->ncontents = 1;
}
cache[cl]->contents[cache[cl]->ncontents] = NULL;
}
int spool_fill_hdrcache(struct protstream *fin, FILE *fout, hdrcache_t cache,
const char **skipheaders)
{
int r = 0;
for (;;) {
char *name, *body;
if ((r = parseheader(fin, fout, &name, &body, skipheaders)) < 0) {
break;
}
if (!name) {
break;
}
spool_cache_header(name, body, cache);
}
return r;
}
const char **spool_getheader(hdrcache_t cache, const char *phead)
{
char *head;
const char **ret = NULL;
int clinit, cl;
assert(cache && phead);
head = xstrdup(phead);
lcase(head);
clinit = cl = hashheader(head);
while (cache[cl] != NULL) {
if (!strcmp(head, cache[cl]->name)) {
ret = (const char **) cache[cl]->contents;
break;
}
cl++;
cl %= HEADERCACHESIZE;
if (cl == clinit) break;
}
free(head);
return ret;
}
void spool_free_hdrcache(hdrcache_t cache)
{
int i, j;
if (!cache) return;
for (i = 0; i < HEADERCACHESIZE; i++) {
if (cache[i]) {
free(cache[i]->name);
for (j = 0; j < cache[i]->ncontents; j++) {
free(cache[i]->contents[j]);
}
free(cache[i]);
}
}
free(cache);
}
int spool_copy_msg(struct protstream *fin, FILE *fout)
{
char buf[8192], *p;
int r = 0;
while (prot_fgets(buf, sizeof(buf)-2, fin)) {
p = buf + strlen(buf) - 1;
if (p < buf) {
r = IMAP_MESSAGE_CONTAINSNULL;
continue;
}
else if (buf[0] == '\r' && buf[1] == '\0') {
r = IMAP_MESSAGE_CONTAINSNULL;
continue;
}
else if (p[0] == '\r') {
prot_ungetc('\r', fin);
*p = '\0';
}
else if (p[0] == '\n' && (p == buf || p[-1] != '\r')) {
p[0] = '\r';
p[1] = '\n';
p[2] = '\0';
}
else if (p[0] != '\n' && (strlen(buf) < sizeof(buf)-2)) {
r = IMAP_MESSAGE_CONTAINSNULL;
continue;
}
while ((p = strchr(buf, '\r')) && p[1] != '\n') {
memmove(p, p+1, strlen(p));
}
if (buf[0] == '.') {
if (buf[1] == '\r' && buf[2] == '\n') {
goto dot;
}
if (fout) fputs(buf+1, fout);
} else {
if (fout) fputs(buf, fout);
}
}
return IMAP_IOERROR;
dot:
return r;
}