/* rfc822parse.c - Simple mail and MIME parser
* Copyright (C) 1999, 2000 Werner Koch, Duesseldorf
* Copyright (C) 2003, 2004 g10 Code GmbH
*
* This file 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 2.1 of
* the License, or (at your option) any later version.
*
* This file 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 program; if not, see .
*/
/* According to RFC822 binary zeroes are allowed at many places. We do
* not handle this correct especially in the field parsing code. It
* should be easy to fix and the API provides a interfaces which
* returns the length but in addition makes sure that returned strings
* are always ended by a \0.
*
* Furthermore, the case of field names is changed and thus it is not
* always a good idea to use these modified header
* lines (e.g. signatures may break).
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include "rfc822parse.h"
/* All valid characters in a header name. */
#define HEADER_NAME_CHARS ("abcdefghijklmnopqrstuvwxyz" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"-01234567890")
enum token_type
{
tSPACE,
tATOM,
tQUOTED,
tDOMAINLIT,
tSPECIAL
};
/* For now we directly use our TOKEN as the parse context */
typedef struct rfc822parse_field_context *TOKEN;
struct rfc822parse_field_context
{
TOKEN next;
enum token_type type;
struct {
unsigned int cont:1;
unsigned int lowered:1;
} flags;
/*TOKEN owner_pantry; */
char data[1];
};
struct hdr_line
{
struct hdr_line *next;
int cont; /* This is a continuation of the previous line. */
unsigned char line[1];
};
typedef struct hdr_line *HDR_LINE;
struct part
{
struct part *right; /* The next part. */
struct part *down; /* A contained part. */
HDR_LINE hdr_lines; /* Header lines os that part. */
HDR_LINE *hdr_lines_tail; /* Helper for adding lines. */
const char *last_hdr_line;/* NULL or a ptr to the last inserted hdr. */
char *boundary; /* Only used in the first part. */
};
typedef struct part *part_t;
struct rfc822parse_context
{
rfc822parse_cb_t callback;
void *callback_value;
int callback_error;
int in_body;
int in_preamble; /* Whether we are before the first boundary. */
part_t parts; /* The tree of parts. */
part_t current_part; /* Whom we are processing (points into parts). */
const char *boundary; /* Current boundary. */
};
static HDR_LINE find_header (rfc822parse_t msg, const char *name,
int which, HDR_LINE * rprev);
static size_t
length_sans_trailing_ws (const unsigned char *line, size_t len)
{
const unsigned char *p, *mark;
size_t n;
for (mark=NULL, p=line, n=0; n < len; n++, p++)
{
if (strchr (" \t\r\n", *p ))
{
if( !mark )
mark = p;
}
else
mark = NULL;
}
if (mark)
return mark - line;
return len;
}
static void
lowercase_string (unsigned char *string)
{
for (; *string; string++)
if (*string >= 'A' && *string <= 'Z')
*string = *string - 'A' + 'a';
}
static int
my_toupper (int c)
{
if (c >= 'a' && c <= 'z')
c &= ~0x20;
return c;
}
/* This is the same as ascii_strcasecmp. */
static int
my_strcasecmp (const char *a, const char *b)
{
if (a == b)
return 0;
for (; *a && *b; a++, b++)
{
if (*a != *b && my_toupper(*a) != my_toupper(*b))
break;
}
return *a == *b? 0 : (my_toupper (*a) - my_toupper (*b));
}
#ifndef HAVE_STPCPY
static char *
my_stpcpy (char *a,const char *b)
{
while (*b)
*a++ = *b++;
*a = 0;
return (char*)a;
}
#define stpcpy my_stpcpy
#endif
/* If a callback has been registered, call it for the event of type
EVENT. */
static int
do_callback (rfc822parse_t msg, rfc822parse_event_t event)
{
int rc;
if (!msg->callback || msg->callback_error)
return 0;
rc = msg->callback (msg->callback_value, event, msg);
if (rc)
msg->callback_error = rc;
return rc;
}
static part_t
new_part (void)
{
part_t part;
part = calloc (1, sizeof *part);
if (part)
{
part->hdr_lines_tail = &part->hdr_lines;
}
return part;
}
static void
release_part (part_t part)
{
part_t tmp;
HDR_LINE hdr, hdr2;
for (; part; part = tmp)
{
tmp = part->right;
if (part->down)
release_part (part->down);
for (hdr = part->hdr_lines; hdr; hdr = hdr2)
{
hdr2 = hdr->next;
free (hdr);
}
free (part->boundary);
free (part);
}
}
static void
release_handle_data (rfc822parse_t msg)
{
release_part (msg->parts);
msg->parts = NULL;
msg->current_part = NULL;
msg->boundary = NULL;
}
/* Wrapper around free becuase in this moulde we use a plain free. */
void
rfc822_free (void *a)
{
if (a)
free (a);
}
/* Check that the header name is valid. We allow all lower and
* uppercase letters and, except for the first character, digits and
* the dash. The check stops at the first colon or at string end.
* Returns true if the name is valid. */
int
rfc822_valid_header_name_p (const char *name)
{
const char *s;
size_t namelen;
if ((s=strchr (name, ':')))
namelen = s - name;
else
namelen = strlen (name);
if (!namelen
|| strspn (name, HEADER_NAME_CHARS) != namelen
|| strchr ("-0123456789", *name))
return 0;
return 1;
}
/* Transform a header NAME into a standard capitalized format.
* Conversion stops at the colon. */
void
rfc822_capitalize_header_name (char *name)
{
unsigned char *p = name;
int first = 1;
/* Special cases first. */
if (!my_strcasecmp (name, "MIME-Version"))
{
strcpy (name, "MIME-Version");
return;
}
/* Regular cases. */
for (; *p && *p != ':'; p++)
{
if (*p == '-')
first = 1;
else if (first)
{
if (*p >= 'a' && *p <= 'z')
*p = *p - 'a' + 'A';
first = 0;
}
else if (*p >= 'A' && *p <= 'Z')
*p = *p - 'A' + 'a';
}
}
/* This is an strcmp which considers a colon also as end-of-string.
* Use this function to compare capitalized header names. */
int
rfc822_cmp_header_name (const char *a, const char *b)
{
for (; *a && *a != ':' && *b && *b != ':'; a++, b++)
{
if (*a != *b )
break;
}
if (*a == *b || (!*a && *b == ':') || (!*b && *a == ':'))
return 0;
else
return (*(signed char *)a - *(signed char *)b);
}
/* Create a new parsing context for an entire rfc822 message and
return it. CB and CB_VALUE may be given to callback for certain
events. NULL is returned on error with errno set appropriately. */
rfc822parse_t
rfc822parse_open (rfc822parse_cb_t cb, void *cb_value)
{
rfc822parse_t msg = calloc (1, sizeof *msg);
if (msg)
{
msg->parts = msg->current_part = new_part ();
if (!msg->parts)
{
free (msg);
msg = NULL;
}
else
{
msg->callback = cb;
msg->callback_value = cb_value;
if (do_callback (msg, RFC822PARSE_OPEN))
{
release_handle_data (msg);
free (msg);
msg = NULL;
}
}
}
return msg;
}
void
rfc822parse_cancel (rfc822parse_t msg)
{
if (msg)
{
do_callback (msg, RFC822PARSE_CANCEL);
release_handle_data (msg);
free (msg);
}
}
void
rfc822parse_close (rfc822parse_t msg)
{
if (msg)
{
do_callback (msg, RFC822PARSE_CLOSE);
release_handle_data (msg);
free (msg);
}
}
static part_t
find_parent (part_t tree, part_t target)
{
part_t part;
for (part = tree->down; part; part = part->right)
{
if (part == target)
return tree; /* Found. */
if (part->down)
{
part_t tmp = find_parent (part, target);
if (tmp)
return tmp;
}
}
return NULL;
}
static void
set_current_part_to_parent (rfc822parse_t msg)
{
part_t parent;
assert (msg->current_part);
parent = find_parent (msg->parts, msg->current_part);
if (!parent)
return; /* Already at the top. */
#ifndef NDEBUG
{
part_t part;
for (part = parent->down; part; part = part->right)
if (part == msg->current_part)
break;
assert (part);
}
#endif
msg->current_part = parent;
parent = find_parent (msg->parts, parent);
msg->boundary = parent? parent->boundary: NULL;
}
/****************
* We have read in all header lines and are about to receive the body
* part. The delimiter line has already been processed.
*
* FIXME: we's better return an error in case of memory failures.
*/
static int
transition_to_body (rfc822parse_t msg)
{
rfc822parse_field_t ctx;
int rc;
rc = do_callback (msg, RFC822PARSE_T2BODY);
if (!rc)
{
/* Store the boundary if we have multipart type. */
ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
if (ctx)
{
const char *s;
s = rfc822parse_query_media_type (ctx, NULL);
if (s && !strcmp (s,"multipart"))
{
s = rfc822parse_query_parameter (ctx, "boundary", 0);
if (s)
{
if (msg->current_part->boundary)
{
errno = ENOENT;
return -1;
}
msg->current_part->boundary = malloc (strlen (s) + 1);
if (msg->current_part->boundary)
{
part_t part;
strcpy (msg->current_part->boundary, s);
msg->boundary = msg->current_part->boundary;
part = new_part ();
if (!part)
{
int save_errno = errno;
rfc822parse_release_field (ctx);
errno = save_errno;
return -1;
}
rc = do_callback (msg, RFC822PARSE_LEVEL_DOWN);
if (msg->current_part->down)
{
errno = ENOENT;
return -1;
}
msg->current_part->down = part;
msg->current_part = part;
msg->in_preamble = 1;
}
}
}
rfc822parse_release_field (ctx);
}
}
return rc;
}
/* We have just passed a MIME boundary and need to prepare for new part.
headers. */
static int
transition_to_header (rfc822parse_t msg)
{
part_t part;
if (!(msg->current_part
&& !msg->current_part->right))
{
errno = ENOENT;
return -1;
}
part = new_part ();
if (!part)
return -1;
msg->current_part->right = part;
msg->current_part = part;
return 0;
}
static int
insert_header (rfc822parse_t msg, const unsigned char *line, size_t length)
{
HDR_LINE hdr;
int new_hdr = 0;
if (!msg->current_part)
{
errno = ENOENT;
return -1;
}
if (!length)
{
msg->in_body = 1;
return transition_to_body (msg);
}
if (!msg->current_part->hdr_lines)
do_callback (msg, RFC822PARSE_BEGIN_HEADER);
length = length_sans_trailing_ws (line, length);
hdr = malloc (sizeof (*hdr) + length);
if (!hdr)
return -1;
hdr->next = NULL;
hdr->cont = (*line == ' ' || *line == '\t');
memcpy (hdr->line, line, length);
hdr->line[length] = 0; /* Make it a string. */
/* Transform a field name into canonical format. */
if (!hdr->cont && strchr (line, ':'))
{
rfc822_capitalize_header_name (hdr->line);
msg->current_part->last_hdr_line = hdr->line;
new_hdr = 1;
}
else
msg->current_part->last_hdr_line = NULL;
*msg->current_part->hdr_lines_tail = hdr;
msg->current_part->hdr_lines_tail = &hdr->next;
if (new_hdr)
do_callback (msg, RFC822PARSE_HEADER_SEEN);
/* Lets help the caller to prevent mail loops and issue an event for
* every Received header. */
if (length >= 9 && !memcmp (line, "Received:", 9))
do_callback (msg, RFC822PARSE_RCVD_SEEN);
return 0;
}
/****************
* Note: We handle the body transparent to allow binary zeroes in it.
*/
static int
insert_body (rfc822parse_t msg, const unsigned char *line, size_t length)
{
int rc = 0;
if (length > 2 && *line == '-' && line[1] == '-' && msg->boundary)
{
size_t blen = strlen (msg->boundary);
if (length == blen + 2
&& !memcmp (line+2, msg->boundary, blen))
{
rc = do_callback (msg, RFC822PARSE_BOUNDARY);
msg->in_body = 0;
if (!rc && !msg->in_preamble)
rc = transition_to_header (msg);
msg->in_preamble = 0;
}
else if (length == blen + 4
&& line[length-2] =='-' && line[length-1] == '-'
&& !memcmp (line+2, msg->boundary, blen))
{
rc = do_callback (msg, RFC822PARSE_LAST_BOUNDARY);
msg->boundary = NULL; /* No current boundary anymore. */
set_current_part_to_parent (msg);
/* Fixme: The next should actually be send right before the
next boundary, so that we can mark the epilogue. */
if (!rc)
rc = do_callback (msg, RFC822PARSE_LEVEL_UP);
}
}
if (msg->in_preamble && !rc)
rc = do_callback (msg, RFC822PARSE_PREAMBLE);
return rc;
}
/* Insert the next line into the parser. Return 0 on success or true
on error with errno set appropriately. */
int
rfc822parse_insert (rfc822parse_t msg, const unsigned char *line, size_t length)
{
return (msg->in_body
? insert_body (msg, line, length)
: insert_header (msg, line, length));
}
/* Tell the parser that we have finished the message. */
int
rfc822parse_finish (rfc822parse_t msg)
{
return do_callback (msg, RFC822PARSE_FINISH);
}
/* If the last inserted line was a header and not a continuation of a
* header line, return a pointer to that line. This function may be
* used on the RFC822PARSE_HEADER_SEEN event to get the name of the
* current header. Returns NULL if no header is available. */
const char *
rfc822parse_last_header_line (rfc822parse_t msg)
{
if (!msg || !msg->current_part)
return NULL;
return msg->current_part->last_hdr_line;
}
/****************
* Get a copy of a header line. The line is returned as one long
* string with LF to separate the continuation line. Caller must free
* the return buffer. WHICH may be used to enumerate over all lines.
* Wildcards are allowed. This function works on the current headers;
* i.e. the regular mail headers or the MIME headers of the current
* part.
*
* WHICH gives the mode:
* -1 := Take the last occurrence
* n := Take the n-th one.
*
* Returns a newly allocated buffer or NULL on error. errno is set in
* case of a memory failure or set to 0 if the requested field is not
* available.
*
* If VALUEOFF is not NULL it will receive the offset of the first non
* space character in the value part of the line (i.e. after the first
* colon).
*/
char *
rfc822parse_get_field (rfc822parse_t msg, const char *name, int which,
size_t *valueoff)
{
HDR_LINE h, h2;
char *buf, *p;
size_t n;
h = find_header (msg, name, which, NULL);
if (!h)
{
errno = 0;
return NULL; /* no such field */
}
n = strlen (h->line) + 1;
for (h2 = h->next; h2 && h2->cont; h2 = h2->next)
n += strlen (h2->line) + 1;
buf = p = malloc (n);
if (buf)
{
p = stpcpy (p, h->line);
*p++ = '\n';
for (h2 = h->next; h2 && h2->cont; h2 = h2->next)
{
p = stpcpy (p, h2->line);
*p++ = '\n';
}
p[-1] = 0;
}
if (valueoff)
{
p = strchr (buf, ':');
if (!p)
*valueoff = 0; /* Oops: should never happen. */
else
{
p++;
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')
p++;
*valueoff = p - buf;
}
}
return buf;
}
/****************
* Enumerate all header. Caller has to provide the address of a pointer
* which has to be initialized to NULL, the caller should then never change this
* pointer until he has closed the enumeration by passing again the address
* of the pointer but with msg set to NULL.
* The function returns pointers to all the header lines or NULL when
* all lines have been enumerated or no headers are available.
*/
const char *
rfc822parse_enum_header_lines (rfc822parse_t msg, void **context)
{
HDR_LINE l;
if (!msg) /* Close. */
return NULL;
if (*context == msg || !msg->current_part)
return NULL;
l = *context ? (HDR_LINE) *context : msg->current_part->hdr_lines;
if (l)
{
*context = l->next ? (void *) (l->next) : (void *) msg;
return l->line;
}
*context = msg; /* Mark end of list. */
return NULL;
}
/****************
* Find a header field. If the Name does end in an asterisk this is meant
* to be a wildcard.
*
* which -1 : Retrieve the last field
* >0 : Retrieve the n-th field
*
* RPREV may be used to return the predecessor of the returned field;
* which may be NULL for the very first one. It has to be initialized
* to either NULL in which case the search start at the first header line,
* or it may point to a headerline, where the search should start
*/
static HDR_LINE
find_header (rfc822parse_t msg, const char *name, int which, HDR_LINE *rprev)
{
HDR_LINE hdr, prev = NULL, mark = NULL;
unsigned char *p;
size_t namelen, n;
int found = 0;
int glob = 0;
if (!msg->current_part)
return NULL;
namelen = strlen (name);
if (namelen && name[namelen - 1] == '*')
{
namelen--;
glob = 1;
}
hdr = msg->current_part->hdr_lines;
if (rprev && *rprev)
{
/* spool forward to the requested starting place.
* we cannot simply set this as we have to return
* the previous list element too */
for (; hdr && hdr != *rprev; prev = hdr, hdr = hdr->next)
;
}
for (; hdr; prev = hdr, hdr = hdr->next)
{
if (hdr->cont)
continue;
if (!(p = strchr (hdr->line, ':')))
continue; /* invalid header, just skip it. */
n = p - hdr->line;
if (!n)
continue; /* invalid name */
if ((glob ? (namelen <= n) : (namelen == n))
&& !memcmp (hdr->line, name, namelen))
{
found++;
if (which == -1)
mark = hdr;
else if (found == which)
{
if (rprev)
*rprev = prev;
return hdr;
}
}
}
if (mark && rprev)
*rprev = prev;
return mark;
}
static const char *
skip_ws (const char *s)
{
while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
s++;
return s;
}
static void
release_token_list (TOKEN t)
{
while (t)
{
TOKEN t2 = t->next;
/* fixme: If we have owner_pantry, put the token back to
* this pantry so that it can be reused later */
free (t);
t = t2;
}
}
static TOKEN
new_token (enum token_type type, const char *buf, size_t length)
{
TOKEN t;
/* fixme: look through our pantries to find a suitable
* token for reuse */
t = malloc (sizeof *t + length);
if (t)
{
t->next = NULL;
t->type = type;
memset (&t->flags, 0, sizeof (t->flags));
t->data[0] = 0;
if (buf)
{
memcpy (t->data, buf, length);
t->data[length] = 0; /* Make sure it is a C string. */
}
else
t->data[0] = 0;
}
return t;
}
static TOKEN
append_to_token (TOKEN old, const char *buf, size_t length)
{
size_t n = strlen (old->data);
TOKEN t;
t = malloc (sizeof *t + n + length);
if (t)
{
t->next = old->next;
t->type = old->type;
t->flags = old->flags;
memcpy (t->data, old->data, n);
memcpy (t->data + n, buf, length);
t->data[n + length] = 0;
old->next = NULL;
release_token_list (old);
}
return t;
}
/*
Parse a field into tokens as defined by rfc822.
*/
static TOKEN
parse_field (HDR_LINE hdr)
{
static const char specials[] = "<>@.,;:\\[]\"()";
static const char specials2[] = "<>@.,;:";
static const char tspecials[] = "/?=<>@,;:\\[]\"()";
static const char tspecials2[] = "/?=<>@.,;:"; /* FIXME: really
include '.'?*/
static struct
{
const unsigned char *name;
size_t namelen;
} tspecial_header[] = {
{ "Content-Type", 12},
{ "Content-Transfer-Encoding", 25},
{ "Content-Disposition", 19},
{ NULL, 0}
};
const char *delimiters;
const char *delimiters2;
const unsigned char *line, *s, *s2;
size_t n;
int i, invalid = 0;
TOKEN t, tok, *tok_tail;
errno = 0;
if (!hdr)
return NULL;
tok = NULL;
tok_tail = &tok;
line = hdr->line;
if (!(s = strchr (line, ':')))
return NULL; /* oops */
n = s - line;
if (!n)
return NULL; /* oops: invalid name */
delimiters = specials;
delimiters2 = specials2;
for (i = 0; tspecial_header[i].name; i++)
{
if (n == tspecial_header[i].namelen
&& !memcmp (line, tspecial_header[i].name, n))
{
delimiters = tspecials;
delimiters2 = tspecials2;
break;
}
}
s++; /* Move over the colon. */
for (;;)
{
while (!*s)
{
if (!hdr->next || !hdr->next->cont)
return tok; /* Ready. */
/* Next item is a header continuation line. */
hdr = hdr->next;
s = hdr->line;
}
if (*s == '(')
{
int level = 1;
int in_quote = 0;
invalid = 0;
for (s++;; s++)
{
while (!*s)
{
if (!hdr->next || !hdr->next->cont)
goto oparen_out;
/* Next item is a header continuation line. */
hdr = hdr->next;
s = hdr->line;
}
if (in_quote)
{
if (*s == '\"')
in_quote = 0;
else if (*s == '\\' && s[1]) /* what about continuation? */
s++;
}
else if (*s == ')')
{
if (!--level)
break;
}
else if (*s == '(')
level++;
else if (*s == '\"')
in_quote = 1;
}
oparen_out:
if (!*s)
; /* Actually this is an error, but we don't care about it. */
else
s++;
}
else if (*s == '\"' || *s == '[')
{
/* We do not check for non-allowed nesting of domainliterals */
int term = *s == '\"' ? '\"' : ']';
invalid = 0;
s++;
t = NULL;
for (;;)
{
for (s2 = s; *s2; s2++)
{
if (*s2 == term)
break;
else if (*s2 == '\\' && s2[1]) /* what about continuation? */
s2++;
}
t = (t
? append_to_token (t, s, s2 - s)
: new_token (term == '\"'? tQUOTED : tDOMAINLIT, s, s2 - s));
if (!t)
goto failure;
if (*s2 || !hdr->next || !hdr->next->cont)
break;
/* Next item is a header continuation line. */
hdr = hdr->next;
s = hdr->line;
}
*tok_tail = t;
tok_tail = &t->next;
s = s2;
if (*s)
s++; /* skip the delimiter */
}
else if ((s2 = strchr (delimiters2, *s)))
{ /* Special characters which are not handled above. */
invalid = 0;
t = new_token (tSPECIAL, s, 1);
if (!t)
goto failure;
*tok_tail = t;
tok_tail = &t->next;
s++;
}
else if (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
{
invalid = 0;
s = skip_ws (s + 1);
}
else if (*s > 0x20 && !(*s & 128))
{ /* Atom. */
invalid = 0;
for (s2 = s + 1; *s2 > 0x20
&& !(*s2 & 128) && !strchr (delimiters, *s2); s2++)
;
t = new_token (tATOM, s, s2 - s);
if (!t)
goto failure;
*tok_tail = t;
tok_tail = &t->next;
s = s2;
}
else
{ /* Invalid character. */
if (!invalid)
{ /* For parsing we assume only one space. */
t = new_token (tSPACE, NULL, 0);
if (!t)
goto failure;
*tok_tail = t;
tok_tail = &t->next;
invalid = 1;
}
s++;
}
}
/*NOTREACHED*/
failure:
{
int save = errno;
release_token_list (tok);
errno = save;
}
return NULL;
}
/****************
* Find and parse a header field.
* WHICH indicates what to do if there are multiple instance of the same
* field (like "Received"); the following value are defined:
* -1 := Take the last occurrence
* 0 := Reserved
* n := Take the n-th one.
* Returns a handle for further operations on the parse context of the field
* or NULL if the field was not found.
*/
rfc822parse_field_t
rfc822parse_parse_field (rfc822parse_t msg, const char *name, int which)
{
HDR_LINE hdr;
if (!which)
return NULL;
hdr = find_header (msg, name, which, NULL);
if (!hdr)
return NULL;
return parse_field (hdr);
}
void
rfc822parse_release_field (rfc822parse_field_t ctx)
{
if (ctx)
release_token_list (ctx);
}
/****************
* Check whether T points to a parameter.
* A parameter starts with a semicolon and it is assumed that t
* points to exactly this one.
*/
static int
is_parameter (TOKEN t)
{
t = t->next;
if (!t || t->type != tATOM)
return 0;
t = t->next;
if (!t || !(t->type == tSPECIAL && t->data[0] == '='))
return 0;
t = t->next;
if (!t)
return 1; /* We assume that an non existing value is an empty one. */
return t->type == tQUOTED || t->type == tATOM;
}
/*
Some header (Content-type) have a special syntax where attribute=value
pairs are used after a leading semicolon. The parse_field code
knows about these fields and changes the parsing to the one defined
in RFC2045.
Returns a pointer to the value which is valid as long as the
parse context is valid; NULL is returned in case that attr is not
defined in the header, a missing value is reppresented by an empty string.
With LOWER_VALUE set to true, a matching field value will be
lowercased.
Note, that ATTR should be lowercase.
*/
const char *
rfc822parse_query_parameter (rfc822parse_field_t ctx, const char *attr,
int lower_value)
{
TOKEN t, a;
for (t = ctx; t; t = t->next)
{
/* skip to the next semicolon */
for (; t && !(t->type == tSPECIAL && t->data[0] == ';'); t = t->next)
;
if (!t)
return NULL;
if (is_parameter (t))
{ /* Look closer. */
a = t->next; /* We know that this is an atom */
if ( !a->flags.lowered )
{
lowercase_string (a->data);
a->flags.lowered = 1;
}
if (!strcmp (a->data, attr))
{ /* found */
t = a->next->next;
/* Either T is now an atom, a quoted string or NULL in
* which case we return an empty string. */
if ( lower_value && t && !t->flags.lowered )
{
lowercase_string (t->data);
t->flags.lowered = 1;
}
return t ? t->data : "";
}
}
}
return NULL;
}
/****************
* This function may be used for the Content-Type header to figure out
* the media type and subtype. Note, that the returned strings are
* guaranteed to be lowercase as required by MIME.
*
* Returns: a pointer to the media type and if subtype is not NULL,
* a pointer to the subtype.
*/
const char *
rfc822parse_query_media_type (rfc822parse_field_t ctx, const char **subtype)
{
TOKEN t = ctx;
const char *type;
if (t->type != tATOM)
return NULL;
if (!t->flags.lowered)
{
lowercase_string (t->data);
t->flags.lowered = 1;
}
type = t->data;
t = t->next;
if (!t || t->type != tSPECIAL || t->data[0] != '/')
return NULL;
t = t->next;
if (!t || t->type != tATOM)
return NULL;
if (subtype)
{
if (!t->flags.lowered)
{
lowercase_string (t->data);
t->flags.lowered = 1;
}
*subtype = t->data;
}
return type;
}
#ifdef TESTING
/* Internal debug function to print the structure of the message. */
static void
dump_structure (rfc822parse_t msg, part_t part, int indent)
{
if (!part)
{
printf ("*** Structure of this message:\n");
part = msg->parts;
}
for (; part; part = part->right)
{
rfc822parse_field_t ctx;
part_t save_part; /* ugly hack - we should have a function to
get part information. */
const char *s;
save_part = msg->current_part;
msg->current_part = part;
ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
msg->current_part = save_part;
if (ctx)
{
const char *s1, *s2;
s1 = rfc822parse_query_media_type (ctx, &s2);
if (s1)
printf ("*** %*s %s/%s", indent*2, "", s1, s2);
else
printf ("*** %*s [not found]", indent*2, "");
s = rfc822parse_query_parameter (ctx, "boundary", 0);
if (s)
printf (" (boundary=\"%s\")", s);
rfc822parse_release_field (ctx);
}
else
printf ("*** %*s text/plain [assumed]", indent*2, "");
putchar('\n');
if (part->down)
dump_structure (msg, part->down, indent + 1);
}
}
static void
show_param (rfc822parse_field_t ctx, const char *name)
{
const char *s;
if (!ctx)
return;
s = rfc822parse_query_parameter (ctx, name, 0);
if (s)
printf ("*** %s: '%s'\n", name, s);
}
static void
show_event (rfc822parse_event_t event)
{
const char *s;
switch (event)
{
case RFC822PARSE_OPEN: s= "Open"; break;
case RFC822PARSE_CLOSE: s= "Close"; break;
case RFC822PARSE_CANCEL: s= "Cancel"; break;
case RFC822PARSE_T2BODY: s= "T2Body"; break;
case RFC822PARSE_FINISH: s= "Finish"; break;
case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break;
case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break;
case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break;
case RFC822PARSE_BOUNDARY: s= "Boundary"; break;
case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break;
case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break;
case RFC822PARSE_PREAMBLE: s= "Preamble"; break;
case RFC822PARSE_EPILOGUE: s= "Epilogue"; break;
default: s= "***invalid event***"; break;
}
printf ("*** got RFC822 event %s\n", s);
}
static int
msg_cb (void *dummy_arg, rfc822parse_event_t event, rfc822parse_t msg)
{
show_event (event);
if (event == RFC822PARSE_T2BODY)
{
rfc822parse_field_t ctx;
void *ectx;
const char *line;
for (ectx=NULL; (line = rfc822parse_enum_header_lines (msg, &ectx)); )
{
printf ("*** HDR: %s\n", line);
}
rfc822parse_enum_header_lines (NULL, &ectx); /* Close enumerator. */
ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
if (ctx)
{
const char *s1, *s2;
s1 = rfc822parse_query_media_type (ctx, &s2);
if (s1)
printf ("*** media: '%s/%s'\n", s1, s2);
else
printf ("*** media: [not found]\n");
show_param (ctx, "boundary");
show_param (ctx, "protocol");
rfc822parse_release_field (ctx);
}
else
printf ("*** media: text/plain [assumed]\n");
}
return 0;
}
int
main (int argc, char **argv)
{
char line[5000];
size_t length;
rfc822parse_t msg;
msg = rfc822parse_open (msg_cb, NULL);
if (!msg)
abort ();
while (fgets (line, sizeof (line), stdin))
{
length = strlen (line);
if (length && line[length - 1] == '\n')
line[--length] = 0;
if (length && line[length - 1] == '\r')
line[--length] = 0;
if (rfc822parse_insert (msg, line, length))
abort ();
}
dump_structure (msg, NULL, 0);
rfc822parse_close (msg);
return 0;
}
#endif
/*
Local Variables:
compile-command: "gcc -Wall -Wno-pointer-sign -g -DTESTING -o rfc822parse rfc822parse.c"
End:
*/