diff --git a/tools/mime-maker.c b/tools/mime-maker.c index 0edc14d78..91eab8258 100644 --- a/tools/mime-maker.c +++ b/tools/mime-maker.c @@ -1,816 +1,777 @@ /* mime-maker.c - Create MIME structures * Copyright (C) 2016 g10 Code GmbH * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GnuPG. * * 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 . */ #include #include #include #include #include "../common/util.h" #include "../common/zb32.h" +#include "rfc822parse.h" #include "mime-maker.h" -/* All valid characters in a header name. */ -#define HEADER_NAME_CHARS ("abcdefghijklmnopqrstuvwxyz" \ - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ - "-01234567890") - /* An object to store an header. Also used for a list of headers. */ struct header_s { struct header_s *next; char *value; /* Malloced value. */ char name[1]; /* Name. */ }; typedef struct header_s *header_t; /* An object to store a MIME part. A part is the header plus the * content (body). */ struct part_s { struct part_s *next; /* Next part in the current container. */ struct part_s *child; /* Child container. */ char *boundary; /* Malloced boundary string. */ header_t headers; /* List of headers. */ header_t *headers_tail;/* Address of last header in chain. */ size_t bodylen; /* Length of BODY. */ char *body; /* Malloced buffer with the body. This is the * non-encoded value. */ unsigned int partid; /* The part ID. */ }; typedef struct part_s *part_t; /* Definition of the mime parser object. */ struct mime_maker_context_s { void *cookie; /* Cookie passed to all callbacks. */ unsigned int verbose:1; /* Enable verbose mode. */ unsigned int debug:1; /* Enable debug mode. */ part_t mail; /* The MIME tree. */ part_t current_part; unsigned int partid_counter; /* Counter assign part ids. */ int boundary_counter; /* Used to create easy to read boundaries. */ char *boundary_suffix; /* Random string used in the boundaries. */ struct b64state *b64state; /* NULL or malloced Base64 decoder state. */ /* Helper to convey the output stream to recursive functions. */ estream_t outfp; }; /* Create a new mime make object. COOKIE is a values woich will be * used as first argument for all callbacks registered with this * object. */ gpg_error_t mime_maker_new (mime_maker_t *r_maker, void *cookie) { mime_maker_t ctx; *r_maker = NULL; ctx = xtrycalloc (1, sizeof *ctx); if (!ctx) return gpg_error_from_syserror (); ctx->cookie = cookie; *r_maker = ctx; return 0; } static void release_parts (part_t part) { while (part) { part_t partnext = part->next; while (part->headers) { header_t hdrnext = part->headers->next; xfree (part->headers); part->headers = hdrnext; } release_parts (part->child); xfree (part->boundary); xfree (part->body); xfree (part); part = partnext; } } /* Release a mime maker object. */ void mime_maker_release (mime_maker_t ctx) { if (!ctx) return; release_parts (ctx->mail); xfree (ctx->boundary_suffix); xfree (ctx); } /* Set verbose and debug mode. */ void mime_maker_set_verbose (mime_maker_t ctx, int level) { if (!level) { ctx->verbose = 0; ctx->debug = 0; } else { ctx->verbose = 1; if (level > 10) ctx->debug = 1; } } static void dump_parts (part_t part, int level) { header_t hdr; for (; part; part = part->next) { log_debug ("%*s[part %u]\n", level*2, "", part->partid); for (hdr = part->headers; hdr; hdr = hdr->next) { log_debug ("%*s%s: %s\n", level*2, "", hdr->name, hdr->value); } if (part->body) log_debug ("%*s[body %zu bytes]\n", level*2, "", part->bodylen); if (part->child) { log_debug ("%*s[container]\n", level*2, ""); dump_parts (part->child, level+1); } } } /* Dump the mime tree for debugging. */ void mime_maker_dump_tree (mime_maker_t ctx) { dump_parts (ctx->mail, 0); } /* Find the parent node for NEEDLE starting at ROOT. */ static part_t find_parent (part_t root, part_t needle) { part_t node, n; for (node = root->child; node; node = node->next) { if (node == needle) return root; if ((n = find_parent (node, needle))) return n; } return NULL; } /* Find the part node from the PARTID. */ static part_t find_part (part_t root, unsigned int partid) { part_t node, n; for (node = root->child; node; node = node->next) { if (node->partid == partid) return root; if ((n = find_part (node, partid))) return n; } return NULL; } /* Create a boundary string. Outr codes is aware of the general * structure of that string (gebins with "=-=") so that * it can protect against accidentally-used boundaries within the * content. */ static char * generate_boundary (mime_maker_t ctx) { if (!ctx->boundary_suffix) { char buffer[12]; gcry_create_nonce (buffer, sizeof buffer); ctx->boundary_suffix = zb32_encode (buffer, 8 * sizeof buffer); if (!ctx->boundary_suffix) return NULL; } ctx->boundary_counter++; return es_bsprintf ("=-=%02d-%s=-=", ctx->boundary_counter, ctx->boundary_suffix); } /* Ensure that the context has a MAIL and CURRENT_PART object and * return the parent object if available */ static gpg_error_t ensure_part (mime_maker_t ctx, part_t *r_parent) { if (!ctx->mail) { ctx->mail = xtrycalloc (1, sizeof *ctx->mail); if (!ctx->mail) { if (r_parent) *r_parent = NULL; return gpg_error_from_syserror (); } log_assert (!ctx->current_part); ctx->current_part = ctx->mail; ctx->current_part->headers_tail = &ctx->current_part->headers; } log_assert (ctx->current_part); if (r_parent) *r_parent = find_parent (ctx->mail, ctx->current_part); return 0; } -/* Transform a header name into a standard capitalized format. - * "Content-Type". Conversion stops at the colon. */ -static void -capitalize_header_name (char *name) -{ - unsigned char *p = name; - int first = 1; - - /* Special cases first. */ - if (!ascii_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'; - } -} - - /* Check whether a header with NAME has already been set into PART. * NAME must be in canonical capitalized format. Return true or * false. */ static int have_header (part_t part, const char *name) { header_t hdr; for (hdr = part->headers; hdr; hdr = hdr->next) if (!strcmp (hdr->name, name)) return 1; return 0; } /* Helper to add a header to a part. */ static gpg_error_t add_header (part_t part, const char *name, const char *value) { gpg_error_t err; header_t hdr; size_t namelen; const char *s; char *p; if (!value) { s = strchr (name, '='); if (!s) return gpg_error (GPG_ERR_INV_ARG); namelen = s - name; value = s+1; } else namelen = strlen (name); hdr = xtrymalloc (sizeof *hdr + namelen); if (!hdr) return gpg_error_from_syserror (); hdr->next = NULL; memcpy (hdr->name, name, namelen); hdr->name[namelen] = 0; - /* Check that the header name is valid. We allow all lower and - * uppercase letters and, except for the first character, digits and - * the dash. */ - if (strspn (hdr->name, HEADER_NAME_CHARS) != namelen - || strchr ("-0123456789", *hdr->name)) + /* Check that the header name is valid. */ + if (!rfc822_valid_header_name_p (hdr->name)) { xfree (hdr); return gpg_error (GPG_ERR_INV_NAME); } - capitalize_header_name (hdr->name); + rfc822_capitalize_header_name (hdr->name); hdr->value = xtrystrdup (value); if (!hdr->value) { err = gpg_error_from_syserror (); xfree (hdr); return err; } for (p = hdr->value + strlen (hdr->value) - 1; (p >= hdr->value && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')); p--) *p = 0; if (!(p >= hdr->value)) { xfree (hdr->value); xfree (hdr); return gpg_error (GPG_ERR_INV_VALUE); /* Only spaces. */ } if (part) { *part->headers_tail = hdr; part->headers_tail = &hdr->next; } else xfree (hdr); return 0; } /* Add a header with NAME and VALUE to the current mail. A LF in the * VALUE will be handled automagically. If NULL is used for VALUE it * is expected that the NAME has the format "NAME=VALUE" and VALUE is * taken from there. * * If no container has been added, the header will be used for the * regular mail headers and not for a MIME part. If the current part * is in a container and a body has been added, we append a new part * to the current container. Thus for a non-MIME mail the caller * needs to call this function followed by a call to add a body. When * adding a Content-Type the boundary parameter must not be included. */ gpg_error_t mime_maker_add_header (mime_maker_t ctx, const char *name, const char *value) { gpg_error_t err; part_t part, parent; /* Hack to use this function for a syntax check of NAME and VALUE. */ if (!ctx) return add_header (NULL, name, value); err = ensure_part (ctx, &parent); if (err) return err; part = ctx->current_part; if ((part->body || part->child) && !parent) { /* We already have a body but no parent. Adding another part is * thus not possible. */ return gpg_error (GPG_ERR_CONFLICT); } if (part->body || part->child) { /* We already have a body and there is a parent. We now append * a new part to the current container. */ part = xtrycalloc (1, sizeof *part); if (!part) return gpg_error_from_syserror (); part->partid = ++ctx->partid_counter; part->headers_tail = &part->headers; log_assert (!ctx->current_part->next); ctx->current_part->next = part; ctx->current_part = part; } /* If no NAME and no VALUE has been given we do not add a header. * This can be used to create a new part without any header. */ if (!name && !value) return 0; /* If we add Content-Type, make sure that we have a MIME-version * header first; this simply looks better. */ if (!ascii_strcasecmp (name, "Content-Type") && !have_header (ctx->mail, "MIME-Version")) { err = add_header (ctx->mail, "MIME-Version", "1.0"); if (err) return err; } return add_header (part, name, value); } /* Helper for mime_maker_add_{body,stream}. */ static gpg_error_t add_body (mime_maker_t ctx, const void *data, size_t datalen) { gpg_error_t err; part_t part, parent; err = ensure_part (ctx, &parent); if (err) return err; part = ctx->current_part; if (part->body) return gpg_error (GPG_ERR_CONFLICT); part->body = xtrymalloc (datalen? datalen : 1); if (!part->body) return gpg_error_from_syserror (); part->bodylen = datalen; if (data) memcpy (part->body, data, datalen); return 0; } /* Add STRING as body to the mail or the current MIME container. A * second call to this function or mime_make_add_body_data is not * allowed. * * FIXME: We may want to have an append_body to add more data to a body. */ gpg_error_t mime_maker_add_body (mime_maker_t ctx, const char *string) { return add_body (ctx, string, strlen (string)); } /* Add (DATA,DATALEN) as body to the mail or the current MIME * container. Note that a second call to this function or to * mime_make_add_body is not allowed. */ gpg_error_t mime_maker_add_body_data (mime_maker_t ctx, const void *data, size_t datalen) { return add_body (ctx, data, datalen); } /* This is the same as mime_maker_add_body but takes a stream as * argument. As of now the stream is copied to the MIME object but * eventually we may delay that and read the stream only at the time * it is needed. Note that the address of the stream object must be * passed and that the ownership of the stream is transferred to this * MIME object. To indicate the latter the function will store NULL * at the ADDR_STREAM so that a caller can't use that object anymore * except for es_fclose which accepts a NULL pointer. */ gpg_error_t mime_maker_add_stream (mime_maker_t ctx, estream_t *stream_addr) { void *data; size_t datalen; es_rewind (*stream_addr); if (es_fclose_snatch (*stream_addr, &data, &datalen)) return gpg_error_from_syserror (); *stream_addr = NULL; return add_body (ctx, data, datalen); } /* Add a new MIME container. A container can be used instead of a * body. */ gpg_error_t mime_maker_add_container (mime_maker_t ctx) { gpg_error_t err; part_t part; err = ensure_part (ctx, NULL); if (err) return err; part = ctx->current_part; if (part->body) return gpg_error (GPG_ERR_CONFLICT); /* There is already a body. */ if (part->child || part->boundary) return gpg_error (GPG_ERR_CONFLICT); /* There is already a container. */ /* Create a child node. */ part->child = xtrycalloc (1, sizeof *part->child); if (!part->child) return gpg_error_from_syserror (); part->child->headers_tail = &part->child->headers; part->boundary = generate_boundary (ctx); if (!part->boundary) { err = gpg_error_from_syserror (); xfree (part->child); part->child = NULL; return err; } part = part->child; part->partid = ++ctx->partid_counter; ctx->current_part = part; return 0; } /* Finish the current container. */ gpg_error_t mime_maker_end_container (mime_maker_t ctx) { gpg_error_t err; part_t parent; err = ensure_part (ctx, &parent); if (err) return err; if (!parent) return gpg_error (GPG_ERR_CONFLICT); /* No container. */ while (parent->next) parent = parent->next; ctx->current_part = parent; return 0; } /* Return the part-ID of the current part. */ unsigned int mime_maker_get_partid (mime_maker_t ctx) { if (ensure_part (ctx, NULL)) return 0; /* Ooops. */ return ctx->current_part->partid; } /* Write a header and handle emdedded LFs. If BOUNDARY is not NULL it * is appended to the value. */ /* Fixme: Add automatic line wrapping. */ static gpg_error_t write_header (mime_maker_t ctx, const char *name, const char *value, const char *boundary) { const char *s; es_fprintf (ctx->outfp, "%s: ", name); /* Note that add_header made sure that VALUE does not end with a LF. * Thus we can assume that a LF is followed by non-whitespace. */ for (s = value; *s; s++) { if (*s == '\n') es_fputs ("\r\n\t", ctx->outfp); else es_fputc (*s, ctx->outfp); } if (boundary) { if (s > value && s[-1] != ';') es_fputc (';', ctx->outfp); es_fprintf (ctx->outfp, "\r\n\tboundary=\"%s\"", boundary); } es_fputs ("\r\n", ctx->outfp); return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0; } static gpg_error_t write_gap (mime_maker_t ctx) { es_fputs ("\r\n", ctx->outfp); return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0; } static gpg_error_t write_boundary (mime_maker_t ctx, const char *boundary, int last) { es_fprintf (ctx->outfp, "\r\n--%s%s\r\n", boundary, last?"--":""); return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0; } /* Fixme: Apply required encoding. */ static gpg_error_t write_body (mime_maker_t ctx, const void *body, size_t bodylen) { const char *s; for (s = body; bodylen; s++, bodylen--) { if (*s == '\n' && !(s > (const char *)body && s[-1] == '\r')) es_fputc ('\r', ctx->outfp); es_fputc (*s, ctx->outfp); } return es_ferror (ctx->outfp)? gpg_error_from_syserror () : 0; } /* Recursive worker for mime_maker_make. */ static gpg_error_t write_tree (mime_maker_t ctx, part_t parent, part_t part) { gpg_error_t err; header_t hdr; for (; part; part = part->next) { for (hdr = part->headers; hdr; hdr = hdr->next) { if (part->child && !strcmp (hdr->name, "Content-Type")) err = write_header (ctx, hdr->name, hdr->value, part->boundary); else err = write_header (ctx, hdr->name, hdr->value, NULL); if (err) return err; } err = write_gap (ctx); if (err) return err; if (part->body) { err = write_body (ctx, part->body, part->bodylen); if (err) return err; } if (part->child) { log_assert (part->boundary); err = write_boundary (ctx, part->boundary, 0); if (!err) err = write_tree (ctx, part, part->child); if (!err) err = write_boundary (ctx, part->boundary, 1); if (err) return err; } if (part->next) { log_assert (parent && parent->boundary); err = write_boundary (ctx, parent->boundary, 0); if (err) return err; } } return 0; } /* Add headers we always require. */ static gpg_error_t add_missing_headers (mime_maker_t ctx) { gpg_error_t err; if (!ctx->mail) return gpg_error (GPG_ERR_NO_DATA); if (!have_header (ctx->mail, "MIME-Version")) { /* Even if a Content-Type has never been set, we want to * announce that we do MIME. */ err = add_header (ctx->mail, "MIME-Version", "1.0"); if (err) goto leave; } if (!have_header (ctx->mail, "Date")) { char *p = rfctimestamp (make_timestamp ()); if (!p) err = gpg_error_from_syserror (); else err = add_header (ctx->mail, "Date", p); xfree (p); if (err) goto leave; } err = 0; leave: return err; } /* Create message from the tree MIME and write it to FP. Note that * the output uses only a LF and a later called sendmail(1) is * expected to convert them to network line endings. */ gpg_error_t mime_maker_make (mime_maker_t ctx, estream_t fp) { gpg_error_t err; err = add_missing_headers (ctx); if (err) return err; ctx->outfp = fp; err = write_tree (ctx, NULL, ctx->mail); ctx->outfp = NULL; return err; } /* Create a stream object from the MIME part identified by PARTID and * store it at R_STREAM. If PARTID identifies a container the entire * tree is returned. Using that function may read stream objects * which have been added as MIME bodies. The caller must close the * stream object. */ gpg_error_t mime_maker_get_part (mime_maker_t ctx, unsigned int partid, estream_t *r_stream) { gpg_error_t err; part_t part; estream_t fp; *r_stream = NULL; /* When the entire tree is requested, we make sure that all missing * headers are applied. We don't do that if only a part is * requested because the additional headers (like Date:) will only * be added to part 0 headers anyway. */ if (!partid) { err = add_missing_headers (ctx); if (err) return err; part = ctx->mail; } else part = find_part (ctx->mail, partid); /* For now we use a memory stream object; however it would also be * possible to create an object created on the fly while the caller * is reading the returned stream. */ fp = es_fopenmem (0, "w+b"); if (!fp) return gpg_error_from_syserror (); ctx->outfp = fp; err = write_tree (ctx, NULL, part); ctx->outfp = NULL; if (!err) { es_rewind (fp); *r_stream = fp; } else es_fclose (fp); return err; } diff --git a/tools/rfc822parse.c b/tools/rfc822parse.c index e8cdb0215..0a4e2bc94 100644 --- a/tools/rfc822parse.c +++ b/tools/rfc822parse.c @@ -1,1266 +1,1331 @@ /* 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. */ 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; /* Wether 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'; } -/* Transform a header name into a standard capitalized format; i.e - "Content-Type". Conversion stops at the colon. As usual we don't - use the localized versions of ctype.h. - */ -static void -capitalize_header_name (unsigned char *name) + +static int +my_toupper (int c) { - int first = 1; + 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 (; *name && *name != ':'; name++) - if (*name == '-') - first = 1; - else if (first) - { - if (*name >= 'a' && *name <= 'z') - *name = *name - 'a' + 'A'; - first = 0; - } - else if (*name >= 'A' && *name <= 'Z') - *name = *name - 'A' + 'a'; + 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 registerd, 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; } +/* 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'; + } +} + + + /* 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) { assert (!msg->current_part->boundary); 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); assert (!msg->current_part->down); 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; assert (msg->current_part); assert (!msg->current_part->right); 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; assert (msg->current_part); 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, ':')) - capitalize_header_name (hdr->line); + rfc822_capitalize_header_name (hdr->line); *msg->current_part->hdr_lines_tail = hdr; msg->current_part->hdr_lines_tail = &hdr->next; /* 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); } /**************** * 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 initialzed 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 initialzed * 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 valuebe 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: */ diff --git a/tools/rfc822parse.h b/tools/rfc822parse.h index 177d8271a..e2f2bedac 100644 --- a/tools/rfc822parse.h +++ b/tools/rfc822parse.h @@ -1,79 +1,81 @@ /* rfc822parse.h - Simple mail and MIME parser * Copyright (C) 1999 Werner Koch, Duesseldorf * Copyright (C) 2003 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 . */ #ifndef RFC822PARSE_H #define RFC822PARSE_H struct rfc822parse_context; typedef struct rfc822parse_context *rfc822parse_t; typedef enum { RFC822PARSE_OPEN = 1, RFC822PARSE_CLOSE, RFC822PARSE_CANCEL, RFC822PARSE_T2BODY, RFC822PARSE_FINISH, RFC822PARSE_RCVD_SEEN, RFC822PARSE_LEVEL_DOWN, RFC822PARSE_LEVEL_UP, RFC822PARSE_BOUNDARY, RFC822PARSE_LAST_BOUNDARY, RFC822PARSE_BEGIN_HEADER, RFC822PARSE_PREAMBLE, RFC822PARSE_EPILOGUE } rfc822parse_event_t; struct rfc822parse_field_context; typedef struct rfc822parse_field_context *rfc822parse_field_t; typedef int (*rfc822parse_cb_t) (void *opaque, rfc822parse_event_t event, rfc822parse_t msg); +int rfc822_valid_header_name_p (const char *name); +void rfc822_capitalize_header_name (char *name); rfc822parse_t rfc822parse_open (rfc822parse_cb_t cb, void *opaque_value); void rfc822parse_close (rfc822parse_t msg); void rfc822parse_cancel (rfc822parse_t msg); int rfc822parse_finish (rfc822parse_t msg); int rfc822parse_insert (rfc822parse_t msg, const unsigned char *line, size_t length); char *rfc822parse_get_field (rfc822parse_t msg, const char *name, int which, size_t *valueoff); const char *rfc822parse_enum_header_lines (rfc822parse_t msg, void **context); rfc822parse_field_t rfc822parse_parse_field (rfc822parse_t msg, const char *name, int which); void rfc822parse_release_field (rfc822parse_field_t field); const char *rfc822parse_query_parameter (rfc822parse_field_t ctx, const char *attr, int lower_value); const char *rfc822parse_query_media_type (rfc822parse_field_t ctx, const char **subtype); #endif /*RFC822PARSE_H */