/* ** Licensed to the Apache Software Foundation (ASF) under one or more ** contributor license agreements. See the NOTICE file distributed with ** this work for additional information regarding copyright ownership. ** The ASF licenses this file to You under the Apache License, Version 2.0 ** (the "License"); you may not use this file except in compliance with ** the License. You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ #include #include "apreq_parser.h" #include "apreq_error.h" #include "apreq_util.h" #include "apr_lib.h" /* for apr_iscntrl() & co */ #define PARSER_STATUS_CHECK(PREFIX) do { \ if (ctx->status == PREFIX##_ERROR) \ return APREQ_ERROR_GENERAL; \ else if (ctx->status == PREFIX##_COMPLETE) \ return APR_SUCCESS; \ else if (bb == NULL) \ return APR_INCOMPLETE; \ } while (0); struct hdr_ctx { apr_bucket_brigade *bb; apr_size_t nlen; apr_size_t glen; apr_size_t vlen; enum { HDR_NAME, HDR_GAP, HDR_VALUE, HDR_NEWLINE, HDR_ENDLINE, HDR_FOLDLINE, HDR_LASTLINE, HDR_COMPLETE, HDR_ERROR } status; }; /********************* header parsing utils ********************/ static apr_bucket *split_header_line(apr_bucket *e, apr_size_t *off, const char **data, apr_size_t *dlen) { if (*off > 1) { apr_bucket_split(e, *off - 1); e = APR_BUCKET_NEXT(e); *dlen -= *off - 1; *data += *off - 1; *off = 1; } return e; } static apr_status_t consume_header_line(apreq_param_t **p, apr_pool_t *pool, apr_bucket_brigade *bb, apr_size_t nlen, apr_size_t glen, apr_size_t vlen) { apreq_param_t *param; apreq_value_t *v; apr_bucket *e, *f; apr_status_t s; struct iovec vec[APREQ_DEFAULT_NELTS], *iov; apr_array_header_t arr; char *dest; const char *data; apr_size_t dlen; int i, eol = 0; param = apreq_param_make(pool, NULL, nlen, NULL, vlen); if (param == NULL) return APR_ENOMEM; *(const apreq_value_t **)&v = ¶m->v; arr.pool = pool; arr.elt_size = sizeof(struct iovec); arr.nelts = 0; arr.nalloc = APREQ_DEFAULT_NELTS; arr.elts = (char *)vec; e = APR_BRIGADE_FIRST(bb); /* store name in a temporary iovec array */ do { apr_size_t len; assert(e != APR_BRIGADE_SENTINEL(bb)); iov = (struct iovec *)apr_array_push(&arr); s = apr_bucket_read(e, (const char **)&iov->iov_base, &len, APR_BLOCK_READ); if (s != APR_SUCCESS) return s; iov->iov_len = len; assert(nlen >= len); nlen -= len; e = APR_BUCKET_NEXT(e); } while (nlen > 0); /* skip gap */ do { assert(e != APR_BRIGADE_SENTINEL(bb)); s = apr_bucket_read(e, &data, &dlen, APR_BLOCK_READ); if (s != APR_SUCCESS) return s; assert(glen >= dlen); glen -= dlen; e = APR_BUCKET_NEXT(e); } while (glen > 0); /* copy value */ dest = v->data; do { apr_size_t off; assert(e != APR_BRIGADE_SENTINEL(bb)); s = apr_bucket_read(e, &data, &dlen, APR_BLOCK_READ); if (s != APR_SUCCESS) return s; for (off = 0; off < dlen; ++off) { const char ch = data[off]; if (ch == '\r' || ch == '\n') { /* Eat [CR]LF of continuation or end of line */ if (!vlen && ch == '\n') eol = 1; /* done */ continue; } assert(vlen > 0); *dest = ch; ++dest; --vlen; } e = APR_BUCKET_NEXT(e); } while (!eol); v->dlen = dest - v->data; *dest++ = 0; /* write name */ v->name = dest; for (i = 0; i < arr.nelts; ++i) { iov = &((struct iovec *)arr.elts)[i]; memcpy(dest, iov->iov_base, iov->iov_len); dest += iov->iov_len; ++iov; } v->nlen = dest - v->name; *dest = 0; while ((f = APR_BRIGADE_FIRST(bb)) != e) apr_bucket_delete(f); apreq_param_tainted_on(param); *p = param; return APR_SUCCESS; } #define IS_TOKEN_CHAR(c) (apr_isalnum(c) \ || ((c) && strchr("!#$%&'*+-.^_`|~", (c)))) APREQ_DECLARE_PARSER(apreq_parse_headers) { apr_pool_t *pool = parser->pool; apr_bucket *e; struct hdr_ctx *ctx; char ch; if (parser->ctx == NULL) { ctx = apr_pcalloc(pool, sizeof *ctx); ctx->bb = apr_brigade_create(pool, parser->bucket_alloc); parser->ctx = ctx; ctx->status = HDR_NAME; } else ctx = parser->ctx; PARSER_STATUS_CHECK(HDR); e = APR_BRIGADE_LAST(ctx->bb); APR_BRIGADE_CONCAT(ctx->bb, bb); /* parse the brigade for CRLF_CRLF-terminated header block, * each time starting from the front of the brigade. */ for (e = APR_BUCKET_NEXT(e); e != APR_BRIGADE_SENTINEL(ctx->bb); e = APR_BUCKET_NEXT(e)) { apr_size_t off = 0, dlen; const char *data; apr_status_t s; apreq_param_t *param = NULL; /* silences gcc-4.0 warning */ if (APR_BUCKET_IS_EOS(e)) { ctx->status = HDR_COMPLETE; APR_BRIGADE_CONCAT(bb, ctx->bb); return APR_SUCCESS; } s = apr_bucket_read(e, &data, &dlen, APR_BLOCK_READ); if ( s != APR_SUCCESS ) { ctx->status = HDR_ERROR; return s; } if (dlen == 0) continue; parse_hdr_bucket: /* gap nlen = 13 * vvv glen = 3 * Sample-Header: grape vlen = 5 * ^^^^^^^^^^^^^ ^^^^^ * name value */ switch (ctx->status) { case HDR_NAME: while (off < dlen) { ch = data[off++]; switch (ch) { case ':': if (!ctx->nlen) { ctx->status = HDR_ERROR; return APREQ_ERROR_BADHEADER; } e = split_header_line(e, &off, &data, &dlen); ++ctx->glen; ctx->status = HDR_GAP; goto parse_hdr_bucket; default: if (!IS_TOKEN_CHAR(ch)) { ctx->status = HDR_ERROR; return APREQ_ERROR_BADCHAR; } ++ctx->nlen; } } break; case HDR_GAP: while (off < dlen) { ch = data[off++]; switch (ch) { case ' ': case '\t': ++ctx->glen; break; case '\n': e = split_header_line(e, &off, &data, &dlen); ctx->status = HDR_NEWLINE; goto parse_hdr_bucket; case '\r': e = split_header_line(e, &off, &data, &dlen); ctx->status = HDR_ENDLINE; goto parse_hdr_bucket; default: if (apr_iscntrl(ch)) { ctx->status = HDR_ERROR; return APREQ_ERROR_BADCHAR; } e = split_header_line(e, &off, &data, &dlen); ++ctx->vlen; ctx->status = HDR_VALUE; goto parse_hdr_bucket; } } break; case HDR_VALUE: while (off < dlen) { ch = data[off++]; switch (ch) { case '\n': ctx->status = HDR_NEWLINE; goto parse_hdr_bucket; case '\r': ctx->status = HDR_ENDLINE; goto parse_hdr_bucket; default: if (apr_iscntrl(ch)) { ctx->status = HDR_ERROR; return APREQ_ERROR_BADCHAR; } ++ctx->vlen; } } break; case HDR_ENDLINE: case HDR_LASTLINE: if (off == dlen) break; if (data[off++] != '\n') { ctx->status = HDR_ERROR; return APREQ_ERROR_BADHEADER; } if (ctx->status == HDR_LASTLINE) { ctx->status = HDR_COMPLETE; goto parse_hdr_bucket; } /* fall thru */ ctx->status = HDR_NEWLINE; case HDR_NEWLINE: if (off == dlen) break; ch = data[off++]; switch (ch) { case ' ': case '\t': ++ctx->vlen; break; default: e = split_header_line(e, &off, &data, &dlen); /* consume from splitted brigade now */ s = consume_header_line(¶m, pool, ctx->bb, ctx->nlen, ctx->glen, ctx->vlen); if (parser->hook != NULL && s == APR_SUCCESS) s = apreq_hook_run(parser->hook, param, NULL); if (s != APR_SUCCESS) { ctx->status = HDR_ERROR; return s; } apreq_value_table_add(¶m->v, t); ctx->nlen = 0; ctx->vlen = 0; ctx->glen = 0; switch (ch) { case '\n': ctx->status = HDR_COMPLETE; break; case '\r': ctx->status = HDR_LASTLINE; break; default: if (!IS_TOKEN_CHAR(ch)) { ctx->status = HDR_ERROR; return APREQ_ERROR_BADCHAR; } ++ctx->nlen; ctx->status = HDR_NAME; break; } goto parse_hdr_bucket; } /* fall thru */ ctx->status = HDR_FOLDLINE; case HDR_FOLDLINE: while (off < dlen) { ch = data[off++]; switch (ch) { case ' ': case '\t': ++ctx->vlen; break; case '\n': ctx->status = HDR_NEWLINE; goto parse_hdr_bucket; case '\r': ctx->status = HDR_ENDLINE; goto parse_hdr_bucket; default: if (apr_iscntrl(ch)) { ctx->status = HDR_ERROR; return APREQ_ERROR_BADCHAR; } ctx->status = HDR_VALUE; ++ctx->vlen; goto parse_hdr_bucket; } } break; case HDR_COMPLETE: if (off < dlen) apr_bucket_split(e, off); e = APR_BUCKET_NEXT(e); do { apr_bucket *f = APR_BRIGADE_FIRST(ctx->bb); apr_bucket_delete(f); } while (e != APR_BRIGADE_FIRST(ctx->bb)); APR_BRIGADE_CONCAT(bb, ctx->bb); return APR_SUCCESS; default: assert(0); /* not reached */ } } apreq_brigade_setaside(ctx->bb,pool); return APR_INCOMPLETE; }