/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2004, 2005, 2007, 2008, 2009, 2010,
2011 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library. If not, see
. */
/* This all header business needs a good rewrite.
* -- Alain Magloire, 2000-07-03 (rev. 1.21)
*
* It's the job that's never started as takes longest to finish.
* -- Hamfast Gamgee, some time in the Third Age
*
* It took almost 7 years to gather the courage to start the job,
* and only one day to finish it.
* -- Sergey Poznyakoff, 2007-06-24
*/
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#ifdef HAVE_STRINGS_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#define HEADER_MODIFIED 0x01
#define HEADER_INVALIDATE 0x02
#define HEADER_SET_MODIFIED(h) \
((h)->flags |= (HEADER_MODIFIED|HEADER_INVALIDATE))
/* mu_hdrent manipulation */
#define MU_HDRENT_NAME(hp,ep) ((hp)->spool + (ep)->fn)
#define MU_HDRENT_VALUE(hp,ep) ((hp)->spool + (ep)->fv)
#define MU_STR_SIZE(nlen,vlen) ((nlen) + 2 + (vlen) + 1)
static struct mu_hdrent *
mu_hdrent_nth (struct _mu_header *hdr, int n)
{
struct mu_hdrent *p;
for (p = hdr->head; p; p = p->next)
if (n-- == 1)
break;
return p;
}
static struct mu_hdrent *
mu_hdrent_find (struct _mu_header *hdr, const char *name, int pos)
{
struct mu_hdrent *p;
if (pos > 0)
{
for (p = hdr->head; p; p = p->next)
if (mu_c_strcasecmp (MU_HDRENT_NAME (hdr,p), name) == 0 && pos-- == 1)
break;
}
else if (pos < 0)
{
for (p = hdr->tail; p; p = p->prev)
if (mu_c_strcasecmp (MU_HDRENT_NAME (hdr,p), name) == 0 && ++pos == 0)
break;
}
else
p = NULL;
return p;
}
static int
mu_hdrent_find_stream_pos (struct _mu_header *hdr, mu_off_t pos,
struct mu_hdrent **pent, size_t *poff)
{
mu_off_t x;
struct mu_hdrent *p;
for (p = hdr->head, x = 0; p; p = p->next)
{
size_t strsize = MU_STR_SIZE (p->nlen, p->vlen);
if (x <= pos && pos < x + strsize)
{
*poff = pos - x;
*pent = p;
return 0;
}
x += strsize;
}
if (x == pos && hdr->tail)
{
/* To supply the trailing '\n' */
p = hdr->tail;
*pent = p;
*poff = MU_STR_SIZE (p->nlen, p->vlen) - 1;
return 0;
}
return 1;
}
static void
mu_hdrent_count (struct _mu_header *hdr, size_t *pcount, size_t *psize,
size_t *plines)
{
if (hdr->flags & HEADER_INVALIDATE)
{
size_t size = 0;
size_t count = 0;
size_t lines = 0;
struct mu_hdrent *p;
for (p = hdr->head; p; p = p->next)
{
count++;
size += MU_STR_SIZE (p->nlen, p->vlen);
lines += p->nlines;
}
hdr->numhdr = count;
hdr->numlines = lines;
hdr->size = size;
hdr->flags &= ~HEADER_INVALIDATE;
}
*pcount = hdr->numhdr;
*psize = hdr->size;
*plines = hdr->numlines;
}
static void
mu_hdrent_remove (struct _mu_header *hdr, struct mu_hdrent *ent)
{
struct mu_hdrent *p = ent->prev;
if (p)
p->next = ent->next;
else
hdr->head = ent->next;
p = ent->next;
if (p)
p->prev = ent->prev;
else
hdr->tail = ent->prev;
}
static void
mu_hdrent_prepend (struct _mu_header *hdr, struct mu_hdrent *ent)
{
struct mu_hdrent *p = hdr->head;
ent->prev = NULL;
ent->next = p;
if (p)
p->prev = ent;
else
hdr->tail = ent;
hdr->head = ent;
}
static void
mu_hdrent_append (struct _mu_header *hdr, struct mu_hdrent *ent)
{
struct mu_hdrent *p = hdr->tail;
ent->next = NULL;
ent->prev = p;
if (p)
p->next = ent;
else
hdr->head = ent;
hdr->tail = ent;
}
static int
mu_hdrent_insert (struct _mu_header *hdr, struct mu_hdrent *ent,
const char *name, int pos, int before)
{
struct mu_hdrent *p;
struct mu_hdrent *ref = mu_hdrent_find (hdr, name, pos);
if (!ref)
return MU_ERR_NOENT;
if (before)
{
ref = ref->prev;
if (!ref)
{
mu_hdrent_prepend (hdr, ent);
return 0;
}
}
p = ref->next;
if (!p)
{
mu_hdrent_append (hdr, ent);
return 0;
}
ent->next = p;
p->prev = ent;
ent->prev = ref;
ref->next = ent;
return 0;
}
#define SPOOLBLKSIZ 1024
static struct mu_hdrent *
mu_hdrent_create (struct _mu_header *ph,
struct mu_hdrent *ent,
const char *name, size_t nsize,
const char *value, size_t vsize)
{
size_t strsize;
size_t sizeleft;
const char *p;
if (!ent)
{
ent = calloc (1, sizeof (*ent));
if (!ent)
return NULL;
}
strsize = MU_STR_SIZE (nsize, vsize);
sizeleft = ph->spool_size - ph->spool_used;
/* Ensure there is enough space in spool */
if (sizeleft < strsize)
{
char *newp;
size_t delta = (strsize - sizeleft + SPOOLBLKSIZ - 1) / SPOOLBLKSIZ;
delta *= SPOOLBLKSIZ;
newp = realloc (ph->spool, ph->spool_size + delta);
if (!newp)
return 0;
ph->spool = newp;
ph->spool_size += delta;
}
/* Copy header name */
ent->fn = ph->spool_used;
ent->nlen = nsize;
memcpy (ph->spool + ph->spool_used, name, nsize);
ph->spool_used += nsize;
ph->spool[ph->spool_used++] = 0;
ph->spool[ph->spool_used++] = ' ';
/* Copy header value */
ent->fv = ph->spool_used;
ent->vlen = vsize;
memcpy (ph->spool + ph->spool_used, value, vsize);
ph->spool_used += vsize;
ph->spool[ph->spool_used++] = 0;
ent->nlines = 1;
for (p = value; p < value + vsize; p++)
if (*p == '\n')
ent->nlines++;
return ent;
}
static void
mu_hdrent_free_list (struct _mu_header *hdr)
{
struct mu_hdrent *p;
for (p = hdr->head; p; )
{
struct mu_hdrent *next = p->next;
free (p);
p = next;
}
hdr->head = hdr->tail = NULL;
hdr->spool_used = 0;
}
#define ISLWSP(c) (((c) == ' ' || (c) == '\t'))
/* Parsing is done in a rather simple fashion, meaning we just consider an
entry to be a field-name an a field-value. So they maybe duplicate of
field-name like "Received" they are just put in the array, see _get_value()
on how to handle the case. in the case of error .i.e a bad header construct
we do a full stop and return what we have so far. */
static int
header_parse (mu_header_t header, const char *blurb, int len)
{
const char *header_end;
const char *header_start;
const char *header_start2;
/* Nothing to parse. */
if (blurb == NULL)
return 0;
header->flags |= HEADER_INVALIDATE;
mu_hdrent_free_list (header);
/* Get a header, a header is:
field-name LWSP ':'
LWSP field-value '\r' '\n'
*[ (' ' | '\t') field-value '\r' '\n' ]
*/
/* First loop goes through the blurb */
for (header_start = blurb; len > 0; header_start = ++header_end)
{
const char *fn, *fn_end, *fv, *fv_end;
struct mu_hdrent *ent;
if (header_start[0] == ' '
|| header_start[0] == '\t'
|| header_start[0] == '\n')
break;
/* Second loop extract one header field. */
for (header_start2 = header_start; len; header_start2 = ++header_end)
{
header_end = memchr (header_start2, '\n', len);
if (header_end == NULL)
{
header_end = header_start2 + len;
len = 0;
break;
}
else
{
len -= (header_end - header_start2 + 1);
if (!len
|| (header_end[1] != ' '
&& header_end[1] != '\t'))
break; /* New header break the inner for. */
}
/* *header_end = ' '; smash LF ? NO */
}
/* Now save the header in the data structure. */
/* Treats unix "From " specially. FIXME: Should we? */
if ((header_end - header_start >= 5)
&& strncmp (header_start, "From ", 5) == 0)
{
fn = header_start;
fn_end = header_start + 5;
fv = header_start + 5;
fv_end = header_end;
}
else /* Break the header in key: value */
{
char *colon = memchr (header_start, ':', header_end - header_start);
/* Houston we have a problem. */
if (colon == NULL)
break; /* FIXME: Disregard the rest and bailout. */
fn = header_start;
fn_end = colon;
/* Shrink any LWSP after the field name -- CRITICAL for
later name comparisons to work correctly! */
while (ISLWSP (fn_end[-1]))
fn_end--;
fv = colon + 1;
fv_end = header_end;
/* Skip any LWSP before the field value -- unnecessary, but
might make some field values look a little tidier. */
while (ISLWSP (fv[0]))
fv++;
}
/* Register this header */
ent = mu_hdrent_create (header, NULL, fn, fn_end - fn, fv, fv_end - fv);
if (!ent)
return ENOMEM;
mu_hdrent_append (header, ent);
} /* for (header_start ...) */
return 0;
}
static int
mu_header_fill (mu_header_t header)
{
int status;
size_t blurb_len = 0;
char *blurb = NULL;
if (header->spool_used)
return 0;
if (header->_fill == NULL)
return 0; /* FIXME: Really? */
/* Bring in the entire header. */
status = header->_fill (header->data, &blurb, &blurb_len);
if (status)
return status;
status = header_parse (header, blurb, blurb_len);
free (blurb);
return status;
}
int
mu_header_create (mu_header_t *ph, const char *blurb, size_t len)
{
mu_header_t header;
int status = 0;
header = calloc (1, sizeof (*header));
if (header == NULL)
return ENOMEM;
status = header_parse (header, blurb, len);
*ph = header;
return status;
}
void
mu_header_destroy (mu_header_t *ph)
{
if (ph && *ph)
{
mu_header_t header = *ph;
mu_stream_destroy (&header->stream);
mu_hdrent_free_list (header);
free (header->spool);
free (header);
*ph = NULL;
}
}
int
mu_header_set_value (mu_header_t header, const char *fn, const char *fv,
int replace)
{
int status;
struct mu_hdrent *ent = NULL;
if (header == NULL || fn == NULL)
return EINVAL;
status = mu_header_fill (header);
if (status)
return status;
/* An fv of NULL means delete the field, but only do it if replace
was also set to true! */
if (fv == NULL && !replace)
return EINVAL;
if (replace)
{
ent = mu_hdrent_find (header, fn, 1);
if (ent)
{
if (fv == NULL)
{
/* Delete the header */
mu_hdrent_remove (header, ent);
free (ent);
return 0;
}
mu_hdrent_create (header, ent, fn, strlen (fn), fv, strlen (fv));
HEADER_SET_MODIFIED (header);
return 0;
}
else if (fv == NULL)
return 0;
}
ent = mu_hdrent_create (header, NULL,
fn, strlen (fn), fv, strlen (fv));
if (!ent)
return ENOMEM;
mu_hdrent_prepend (header, ent);
HEADER_SET_MODIFIED (header);
return 0;
}
int
mu_header_remove (mu_header_t header, const char *fn, int n)
{
int status;
struct mu_hdrent *ent;
if (header == NULL || fn == NULL)
return EINVAL;
status = mu_header_fill (header);
if (status)
return status;
ent = mu_hdrent_find (header, fn, n);
if (!ent)
return MU_ERR_NOENT;
mu_hdrent_remove (header, ent);
HEADER_SET_MODIFIED (header);
free (ent);
return 0;
}
int
mu_header_append (mu_header_t header, const char *fn, const char *fv)
{
int status;
struct mu_hdrent *ent;
if (header == NULL || fn == NULL || fv == NULL)
return EINVAL;
status = mu_header_fill (header);
if (status)
return status;
ent = mu_hdrent_create (header, NULL, fn, strlen (fn), fv, strlen (fv));
if (!ent)
return ENOMEM;
mu_hdrent_append (header, ent);
HEADER_SET_MODIFIED (header);
return 0;
}
int
mu_header_prepend (mu_header_t header, const char *fn, const char *fv)
{
int status;
struct mu_hdrent *ent;
if (header == NULL || fn == NULL || fv == NULL)
return EINVAL;
status = mu_header_fill (header);
if (status)
return status;
ent = mu_hdrent_create (header, NULL, fn, strlen (fn), fv, strlen (fv));
if (!ent)
return ENOMEM;
mu_hdrent_prepend (header, ent);
HEADER_SET_MODIFIED (header);
return 0;
}
int
mu_header_insert (mu_header_t header,
const char *fn, const char *fv,
const char *ref, int n, int flags)
{
int status;
struct mu_hdrent *ent;
if (header == NULL || fn == NULL || fv == NULL)
return EINVAL;
status = mu_header_fill (header);
if (status)
return status;
if (flags & MU_HEADER_REPLACE)
{
if (!ref)
ref = fn;
ent = mu_hdrent_find (header, ref, n);
mu_hdrent_create (header, ent, fn, strlen (fn), fv, strlen (fv));
}
else
{
ent = mu_hdrent_create (header, NULL,
fn, strlen (fn), fv, strlen (fv));
if (!ent)
return ENOMEM;
if (ref)
return mu_hdrent_insert (header, ent, ref, n,
flags & MU_HEADER_BEFORE);
else
mu_hdrent_prepend (header, ent);
}
HEADER_SET_MODIFIED (header);
return 0;
}
int
mu_header_clear (mu_header_t header)
{
int status;
if (header == NULL)
return EINVAL;
status = mu_header_fill (header);
if (status)
return status;
mu_header_invalidate (header);
HEADER_SET_MODIFIED (header);
return 0;
}
int
mu_header_sget_value_n (mu_header_t header,
const char *name, int n,
const char **pval)
{
int status;
struct mu_hdrent *ent;
if (header == NULL || name == NULL)
return EINVAL;
status = mu_header_fill (header);
if (status)
return status;
ent = mu_hdrent_find (header, name, n);
if (!ent)
return MU_ERR_NOENT;
if (pval)
*pval = MU_HDRENT_VALUE (header, ent);
return 0;
}
int
mu_header_aget_value_n (mu_header_t header,
const char *name, int n,
char **pval)
{
const char *s;
int status = mu_header_sget_value_n (header, name, n, &s);
if (status == 0)
{
*pval = strdup (s);
if (!*pval)
status = ENOMEM;
}
return status;
}
int
mu_header_get_value_n (mu_header_t header, const char *name, int n,
char *buffer, size_t buflen, size_t *pn)
{
const char *s;
int status = mu_header_sget_value_n (header, name, n, &s);
if (status == 0)
{
size_t slen = strlen (s);
if (buffer)
{
if (slen > buflen)
slen = buflen;
memcpy (buffer, s, slen);
buffer[slen] = 0;
}
if (pn)
*pn = slen;
}
return status;
}
/* Unfolding functions */
int
mu_header_get_value_unfold_n (mu_header_t header,
const char *name, int n, char *buffer,
size_t buflen, size_t *pn)
{
int rc = mu_header_get_value_n (header, name, n, buffer, buflen, pn);
if (rc == 0)
mu_string_unfold (buffer, pn);
return rc;
}
int
mu_header_aget_value_unfold_n (mu_header_t header, const char *name, int n,
char **pvalue)
{
int rc = mu_header_aget_value_n (header, name, n, pvalue);
if (rc == 0)
mu_string_unfold (*pvalue, NULL);
return rc;
}
int
mu_header_get_address_n (mu_header_t header, const char *name, int n,
mu_address_t *addr)
{
const char *value = NULL;
int status = mu_header_sget_value_n (header, name, n, &value);
if (status)
return status;
return mu_address_create (addr, value);
}
int
mu_header_get_field_count (mu_header_t header, size_t *pcount)
{
size_t count;
size_t size;
size_t lines;
int status;
if (header == NULL)
return EINVAL;
status = mu_header_fill (header);
if (status == 0)
{
mu_hdrent_count (header, &count, &size, &lines);
if (pcount)
*pcount = count;
}
return status;
}
int
mu_header_sget_field_name (mu_header_t header, size_t num, const char **sptr)
{
int status;
if (header == NULL)
return EINVAL;
status = mu_header_fill (header);
if (status == 0)
{
struct mu_hdrent *ent = mu_hdrent_nth (header, num);
if (ent)
*sptr = MU_HDRENT_NAME (header, ent);
else
status = MU_ERR_NOENT;
}
return status;
}
int
mu_header_get_field_name (mu_header_t header, size_t num, char *buffer,
size_t buflen, size_t *pn)
{
const char *s;
int status = mu_header_sget_field_name (header, num, &s);
if (status == 0)
{
size_t slen = strlen (s);
if (buffer)
{
if (slen > buflen)
slen = buflen;
memcpy (buffer, s, slen);
buffer[slen] = 0;
}
if (pn)
*pn = slen;
}
return status;
}
int
mu_header_aget_field_name (mu_header_t header, size_t num, char **pvalue)
{
const char *s;
int status = mu_header_sget_field_name (header, num, &s);
if (status == 0)
{
if ((*pvalue = strdup (s)) == NULL)
status = ENOMEM;
}
return status;
}
int
mu_header_sget_field_value (mu_header_t header, size_t num, const char **sptr)
{
int status;
if (header == NULL)
return EINVAL;
status = mu_header_fill (header);
if (status == 0)
{
struct mu_hdrent *ent = mu_hdrent_nth (header, num);
if (ent)
*sptr = MU_HDRENT_VALUE (header, ent);
else
status = MU_ERR_NOENT;
}
return status;
}
int
mu_header_get_field_value (mu_header_t header, size_t num, char *buffer,
size_t buflen, size_t *pn)
{
const char *s;
int status = mu_header_sget_field_value (header, num, &s);
if (status == 0)
{
size_t slen = strlen (s);
if (buffer)
{
if (slen > buflen)
slen = buflen;
memcpy (buffer, s, slen);
buffer[slen] = 0;
}
if (pn)
*pn = slen;
}
return status;
}
int
mu_header_aget_field_value (mu_header_t header, size_t num, char **pvalue)
{
const char *s;
int status = mu_header_sget_field_value (header, num, &s);
if (status == 0)
{
if ((*pvalue = strdup (s)) == NULL)
status = ENOMEM;
}
return status;
}
int
mu_header_get_field_value_unfold (mu_header_t header, size_t num, char *buf,
size_t buflen, size_t *nwritten)
{
int rc = mu_header_get_field_value (header, num, buf, buflen, nwritten);
if (rc == 0)
mu_string_unfold (buf, nwritten);
return rc;
}
int
mu_header_aget_field_value_unfold (mu_header_t header, size_t num,
char **pvalue)
{
int rc = mu_header_aget_field_value (header, num, pvalue);
if (rc == 0)
mu_string_unfold (*pvalue, NULL);
return rc;
}
int
mu_header_lines (mu_header_t header, size_t *plines)
{
int status;
if (header == NULL)
return EINVAL;
if (plines == NULL)
return MU_ERR_OUT_PTR_NULL;
status = mu_header_fill (header);
if (status == 0)
{
size_t count;
size_t size;
size_t lines;
mu_hdrent_count (header, &count, &size, &lines);
*plines = lines + 1;
}
return status;
}
int
mu_header_size (mu_header_t header, size_t *psize)
{
int status;
if (header == NULL)
return EINVAL;
if (psize == NULL)
return MU_ERR_OUT_PTR_NULL;
status = mu_header_fill (header);
if (status == 0)
{
size_t count;
size_t size;
size_t lines;
mu_hdrent_count (header, &count, &size, &lines);
*psize = size + 1;
}
return status;
}
int
mu_header_invalidate (mu_header_t hdr)
{
if (hdr == NULL)
return EINVAL;
mu_hdrent_free_list (hdr);
return 0;
}
static void
mu_hdrent_fixup (mu_header_t hdr, struct mu_hdrent *ent)
{
char *s = MU_HDRENT_NAME (hdr, ent);
s[ent->nlen] = ':';
s = MU_HDRENT_VALUE (hdr, ent);
s[ent->vlen] = '\n';
}
static void
mu_hdrent_unroll_fixup (mu_header_t hdr, struct mu_hdrent *ent)
{
char *s = MU_HDRENT_NAME (hdr, ent);
s[ent->nlen] = 0;
s = MU_HDRENT_VALUE (hdr, ent);
s[ent->vlen] = 0;
}
int
header_seek (mu_stream_t str, mu_off_t off, mu_off_t *presult)
{
struct _mu_header_stream *hstr = (struct _mu_header_stream *) str;
size_t size;
int status;
status = mu_header_size (hstr->hdr, &size);
if (status)
return status;
if (off < 0 || off > size)
return ESPIPE;
hstr->off = off;
*presult = off;
return 0;
}
static int
header_read (mu_stream_t is, char *buffer, size_t buflen, size_t *pnread)
{
struct _mu_header_stream *hstr = (struct _mu_header_stream *) is;
mu_header_t header;
struct mu_hdrent *ent;
size_t ent_off;
int status;
size_t nread;
if (is == NULL)
return EINVAL;
header = hstr->hdr;
status = mu_header_fill (header);
if (status)
return status;
if (mu_hdrent_find_stream_pos (header, hstr->off, &ent, &ent_off))
{
if (pnread)
*pnread = 0;
return 0;
}
for (nread = 0; nread < buflen && ent; ent = ent->next)
{
size_t rest = buflen - nread;
size_t strsize = MU_STR_SIZE (ent->nlen, ent->vlen) - ent_off;
if (rest > strsize)
rest = strsize;
mu_hdrent_fixup (header, ent);
memcpy (buffer + nread, MU_HDRENT_NAME (header, ent) + ent_off, rest);
mu_hdrent_unroll_fixup (header, ent);
nread += rest;
hstr->off += rest;
ent_off = 0;
}
if (pnread)
*pnread = nread;
return 0;
}
#if 0
/* FIXME: Implement header_readdelim based on this: */
static int
_header_readline (mu_stream_t is, char *buffer, size_t buflen, size_t *pnread)
{
struct _mu_header_stream *hstr = (struct _mu_header_stream *) is;
mu_header_t header = hstr->hdr;
struct mu_hdrent *ent;
size_t ent_off;
int status;
size_t strsize;
char *start, *end;
if (buflen == 0)
return EINVAL;
header = mu_stream_get_owner (is);
status = mu_header_fill (header);
if (status)
return status;
if (mu_hdrent_find_stream_pos (header, hstr->off, &ent, &ent_off))
{
if (pnread)
*pnread = 0;
return 0;
}
buflen--; /* Account for the terminating nul */
mu_hdrent_fixup (header, ent);
strsize = MU_STR_SIZE (ent->nlen, ent->vlen) - ent_off;
start = MU_HDRENT_NAME (header, ent) + ent_off;
end = strchr (start, '\n');
if (end)
{
size_t len = end - start + 1;
if (len < strsize)
strsize = len;
}
if (strsize < buflen)
buflen = strsize;
memcpy (buffer, start, buflen);
buffer[buflen] = 0;
hstr->off += buflen;
mu_hdrent_unroll_fixup (header, ent);
if (pnread)
*pnread = buflen;
return 0;
}
#endif
static int
header_write (mu_stream_t os, const char *buf, size_t buflen, size_t *pnwrite)
{
struct _mu_header_stream *hstr;
mu_header_t header;
int status;
mu_off_t mstream_size;
if (!os || !buf)
return EINVAL;
hstr = (struct _mu_header_stream *) os;
header = hstr->hdr;
if (header == NULL)
return EINVAL;
/* Skip the obvious. */
if (*buf == '\0' || buflen == 0)
{
if (pnwrite)
*pnwrite = 0;
return 0;
}
if (!header->mstream)
{
status = mu_memory_stream_create (&header->mstream, MU_STREAM_RDWR);
if (status)
return status;
}
status = mu_stream_write (header->mstream, buf, buflen, NULL);
if (status)
{
mu_stream_destroy (&header->mstream);
return status;
}
status = mu_stream_size (header->mstream, &mstream_size);
if (status == 0 && mstream_size > 1)
{
char nlbuf[2];
status = mu_stream_seek (header->mstream, -2, MU_SEEK_END, NULL);
if (status == 0)
status = mu_stream_read (header->mstream, nlbuf, 2, NULL);
if (status == 0 && memcmp (nlbuf, "\n\n", 2) == 0)
{
char *blurb;
blurb = calloc (1, mstream_size + 1);
if (blurb)
{
mu_stream_read (header->mstream, blurb, mstream_size, NULL);
status = header_parse (header, blurb, mstream_size);
}
free (blurb);
mu_stream_destroy (&header->mstream);
}
}
if (pnwrite)
*pnwrite = buflen;
return status;
}
static int
header_size (mu_stream_t str, mu_off_t *psize)
{
mu_header_t header;
int status;
size_t size;
if (str == NULL)
return EINVAL;
if (psize == NULL)
return MU_ERR_OUT_PTR_NULL;
header = ((struct _mu_header_stream *) str)->hdr;
status = mu_header_fill (header);
if (status)
return status;
status = mu_header_size (header, &size);
if (status == 0)
*psize = size;
return status;
}
static int
_header_get_stream (mu_header_t header, mu_stream_t *pstream, int ref)
{
if (header == NULL)
return EINVAL;
if (pstream == NULL)
return MU_ERR_OUT_PTR_NULL;
if (header->stream == NULL)
{
struct _mu_header_stream *str =
(struct _mu_header_stream *) _mu_stream_create (sizeof (*str),
MU_STREAM_RDWR|MU_STREAM_SEEK);
if (!str)
return ENOMEM;
str->stream.read = header_read;
/*str->stream.rdelim? */
str->stream.write = header_write;
str->stream.seek = header_seek;
str->stream.size = header_size;
str->hdr = header;
header->stream = (mu_stream_t) str;
}
if (!ref)
{
*pstream = header->stream;
return 0;
}
return mu_streamref_create (pstream, header->stream);
}
int
mu_header_get_stream (mu_header_t header, mu_stream_t *pstream)
{
/* FIXME: Deprecation warning */
return _header_get_stream (header, pstream, 0);
}
int
mu_header_get_streamref (mu_header_t header, mu_stream_t *pstream)
{
return _header_get_stream (header, pstream, 1);
}
int
mu_header_set_fill (mu_header_t header, int
(*_fill) (void *data, char **, size_t *),
void *data)
{
if (header == NULL)
return EINVAL;
header->_fill = _fill;
header->data = data;
return 0;
}
int
mu_header_is_modified (mu_header_t header)
{
return header ? (header->flags & HEADER_MODIFIED) : 0;
}
int
mu_header_clear_modified (mu_header_t header)
{
if (header)
header->flags &= ~HEADER_MODIFIED;
return 0;
}