/* $OpenBSD: rfc2822.c,v 1.5 2015/11/05 08:55:09 gilles Exp $ */ /* * Copyright (c) 2014 Gilles Chehade * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "rfc2822.h" /* default no-op callbacks */ static void hdr_dflt_cb(const struct rfc2822_header *hdr, void *arg) {} static void body_dflt_cb(const char *line, void *arg) {} static void header_reset(struct rfc2822_header *hdr) { struct rfc2822_line *line; while ((line = TAILQ_FIRST(&hdr->lines))) { TAILQ_REMOVE(&hdr->lines, line, next); free(line); } } static void header_callback(struct rfc2822_parser *rp) { struct rfc2822_hdr_cb *hdr_cb; struct rfc2822_hdr_miss_cb *hdr_miss_cb; TAILQ_FOREACH(hdr_cb, &rp->hdr_cb, next) if (strcasecmp(hdr_cb->name, rp->header.name) == 0) { hdr_cb->func(&rp->header, hdr_cb->arg); goto end; } rp->hdr_dflt_cb.func(&rp->header, rp->hdr_dflt_cb.arg); end: TAILQ_FOREACH(hdr_miss_cb, &rp->hdr_miss_cb, next) if (strcasecmp(hdr_miss_cb->name, rp->header.name) == 0) break; if (hdr_miss_cb) TAILQ_REMOVE(&rp->hdr_miss_cb, hdr_miss_cb, next); header_reset(&rp->header); rp->in_hdr = 0; return; } static void missing_headers_callback(struct rfc2822_parser *rp) { struct rfc2822_hdr_miss_cb *hdr_miss_cb; while ((hdr_miss_cb = TAILQ_FIRST(&rp->hdr_miss_cb))) { hdr_miss_cb->func(hdr_miss_cb->name, hdr_miss_cb->arg); TAILQ_REMOVE(&rp->hdr_miss_cb, hdr_miss_cb, next); free(hdr_miss_cb); } } static void body_callback(struct rfc2822_parser *rp, const char *line) { rp->body_line_cb.func(line, rp->body_line_cb.arg); } static int parser_feed_header(struct rfc2822_parser *rp, char *line) { struct rfc2822_line *hdrline; char *pos; /* new header */ if (! isspace(*line) && *line != '\0') { rp->in_hdr = 1; if ((pos = strchr(line, ':')) == NULL) return 0; memset(rp->header.name, 0, sizeof rp->header.name); (void)memcpy(rp->header.name, line, pos - line); if (isspace(*(pos + 1))) return parser_feed_header(rp, pos + 1); else { *pos = ' '; return parser_feed_header(rp, pos); } } /* continuation */ if (! rp->in_hdr) return 0; /* append line to header */ if ((hdrline = calloc(1, sizeof *hdrline)) == NULL) return -1; (void)strlcpy(hdrline->buffer, line, sizeof hdrline->buffer); TAILQ_INSERT_TAIL(&rp->header.lines, hdrline, next); return 1; } static int parser_feed_body(struct rfc2822_parser *rp, const char *line) { /* for now, we only support per-line callbacks */ body_callback(rp, line); return 1; } void rfc2822_parser_init(struct rfc2822_parser *rp) { memset(rp, 0, sizeof *rp); TAILQ_INIT(&rp->hdr_cb); TAILQ_INIT(&rp->hdr_miss_cb); TAILQ_INIT(&rp->header.lines); rfc2822_header_default_callback(rp, hdr_dflt_cb, NULL); rfc2822_body_callback(rp, body_dflt_cb, NULL); } void rfc2822_parser_flush(struct rfc2822_parser *rp) { if (! rp->in_hdrs) return; header_callback(rp); } void rfc2822_parser_reset(struct rfc2822_parser *rp) { header_reset(&rp->header); rp->in_hdrs = 1; } void rfc2822_parser_release(struct rfc2822_parser *rp) { struct rfc2822_hdr_cb *cb; struct rfc2822_hdr_miss_cb *mcb; rfc2822_parser_reset(rp); while ((cb = TAILQ_FIRST(&rp->hdr_cb))) { TAILQ_REMOVE(&rp->hdr_cb, cb, next); free(cb); } while ((mcb = TAILQ_FIRST(&rp->hdr_miss_cb))) { TAILQ_REMOVE(&rp->hdr_miss_cb, mcb, next); free(mcb); } } int rfc2822_parser_feed(struct rfc2822_parser *rp, const char *line) { char buffer[RFC2822_MAX_LINE_SIZE+1]; /* in header and line is not a continuation, execute callback */ if (rp->in_hdr && (*line == '\0' || !isspace(*line))) header_callback(rp); /* no longer in headers */ if (*line == '\0') { if (rp->in_hdrs) missing_headers_callback(rp); rp->in_hdrs = 0; } if (rp->in_hdrs) { /* line exceeds RFC maximum size requirement */ if (strlcpy(buffer, line, sizeof buffer) >= sizeof buffer) return 0; return parser_feed_header(rp, buffer); } /* don't enforce line max length on content, too many MUA break */ return parser_feed_body(rp, line); } int rfc2822_header_callback(struct rfc2822_parser *rp, const char *header, void (*func)(const struct rfc2822_header *, void *), void *arg) { struct rfc2822_hdr_cb *cb; struct rfc2822_hdr_cb *cb_tmp; char buffer[RFC2822_MAX_LINE_SIZE+1]; /* line exceeds RFC maximum size requirement */ if (strlcpy(buffer, header, sizeof buffer) >= sizeof buffer) return 0; TAILQ_FOREACH_SAFE(cb, &rp->hdr_cb, next, cb_tmp) { if (strcasecmp(cb->name, buffer) == 0) { TAILQ_REMOVE(&rp->hdr_cb, cb, next); free(cb); } } if ((cb = calloc(1, sizeof *cb)) == NULL) return -1; (void)strlcpy(cb->name, buffer, sizeof cb->name); cb->func = func; cb->arg = arg; TAILQ_INSERT_TAIL(&rp->hdr_cb, cb, next); return 1; } int rfc2822_missing_header_callback(struct rfc2822_parser *rp, const char *header, void (*func)(const char *, void *), void *arg) { struct rfc2822_hdr_miss_cb *cb; struct rfc2822_hdr_miss_cb *cb_tmp; char buffer[RFC2822_MAX_LINE_SIZE+1]; /* line exceeds RFC maximum size requirement */ if (strlcpy(buffer, header, sizeof buffer) >= sizeof buffer) return 0; TAILQ_FOREACH_SAFE(cb, &rp->hdr_miss_cb, next, cb_tmp) { if (strcasecmp(cb->name, buffer) == 0) { TAILQ_REMOVE(&rp->hdr_miss_cb, cb, next); free(cb); } } if ((cb = calloc(1, sizeof *cb)) == NULL) return -1; (void)strlcpy(cb->name, buffer, sizeof cb->name); cb->func = func; cb->arg = arg; TAILQ_INSERT_TAIL(&rp->hdr_miss_cb, cb, next); return 1; } void rfc2822_header_default_callback(struct rfc2822_parser *rp, void (*func)(const struct rfc2822_header *, void *), void *arg) { struct rfc2822_hdr_cb *cb; cb = &rp->hdr_dflt_cb; cb->func = func; cb->arg = arg; } void rfc2822_body_callback(struct rfc2822_parser *rp, void (*func)(const char *, void *), void *arg) { struct rfc2822_line_cb *cb; cb = &rp->body_line_cb; cb->func = func; cb->arg = arg; }