| /* |
| ** Copyright 2003-2004 The Apache Software Foundation |
| ** |
| ** Licensed 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 "apreq_params.h" |
| #include "apreq_env.h" |
| #include "apr_lib.h" |
| #include "apr_strings.h" |
| #include "apr_strmatch.h" |
| #include "apr_xml.h" |
| #include "apr_hash.h" |
| |
| void apreq_parser_initialize(void); |
| |
| #ifndef MAX |
| #define MAX(A,B) ( (A) > (B) ? (A) : (B) ) |
| #endif |
| #ifndef MIN |
| #define MIN(A,B) ( (A) < (B) ? (A) : (B) ) |
| #endif |
| |
| #ifndef CRLF |
| #define CRLF "\015\012" |
| #endif |
| |
| #define PARSER_STATUS_CHECK(PREFIX) do { \ |
| if (ctx->status == PREFIX##_ERROR) \ |
| return APR_EGENERAL; \ |
| else if (ctx->status == PREFIX##_COMPLETE) \ |
| return APR_SUCCESS; \ |
| else if (bb == NULL) \ |
| return APR_INCOMPLETE; \ |
| } while (0); |
| |
| |
| APREQ_DECLARE(apreq_parser_t *) |
| apreq_make_parser(apr_pool_t *pool, |
| const char *enctype, |
| apr_status_t (*parser) (APREQ_PARSER_ARGS), |
| apreq_hook_t *hook, |
| void *ctx) |
| { |
| apreq_parser_t *p = apr_palloc(pool, sizeof *p); |
| p->enctype = enctype; |
| p->parser = parser; |
| p->hook = hook; |
| p->ctx = ctx; |
| return p; |
| } |
| |
| APREQ_DECLARE(apreq_hook_t *) |
| apreq_make_hook(apr_pool_t *pool, |
| apr_status_t (*hook) (APREQ_HOOK_ARGS), |
| apreq_hook_t *next, |
| void *ctx) |
| { |
| apreq_hook_t *h = apr_palloc(pool, sizeof *h); |
| h->hook = hook; |
| h->next = next; |
| h->ctx = ctx; |
| return h; |
| } |
| |
| |
| APREQ_DECLARE(void) apreq_add_hook(apreq_parser_t *p, |
| apreq_hook_t *h) |
| { |
| apreq_hook_t *last = h; |
| |
| while (last->next) |
| last = last->next; |
| |
| last->next = p->hook; |
| p->hook = h; |
| } |
| |
| static apr_hash_t *default_parsers; |
| static apr_pool_t *default_parser_pool; |
| |
| void apreq_parser_initialize(void) |
| { |
| if (default_parsers != NULL) |
| return; |
| apr_pool_create(&default_parser_pool, NULL); |
| default_parsers = apr_hash_make(default_parser_pool); |
| |
| apreq_register_parser("application/x-www-form-urlencoded", |
| apreq_parse_urlencoded); |
| apreq_register_parser("multipart/form-data", apreq_parse_multipart); |
| apreq_register_parser("multipart/related", apreq_parse_multipart); |
| } |
| |
| APREQ_DECLARE(void) apreq_register_parser(const char *enctype, |
| apr_status_t (*parser) (APREQ_PARSER_ARGS)) |
| { |
| apr_status_t (**f) (APREQ_PARSER_ARGS) = NULL; |
| apreq_parser_initialize(); |
| if (parser != NULL) { |
| f = apr_palloc(default_parser_pool, sizeof *f); |
| *f = parser; |
| } |
| apr_hash_set(default_parsers, apr_pstrdup(default_parser_pool, enctype), |
| APR_HASH_KEY_STRING, f); |
| |
| } |
| |
| APREQ_DECLARE(apreq_parser_t *)apreq_parser(void *env, apreq_hook_t *hook) |
| { |
| apr_status_t (**f) (APREQ_PARSER_ARGS); |
| apr_pool_t *pool = apreq_env_pool(env); |
| const char *type = apreq_env_content_type(env); |
| apr_ssize_t tlen; |
| |
| if (type == NULL || default_parsers == NULL) |
| return NULL; |
| |
| tlen = 0; |
| while(type[tlen] && type[tlen] != ';') |
| ++tlen; |
| |
| f = apr_hash_get(default_parsers, type, tlen); |
| |
| if (f != NULL) |
| return apreq_make_parser(pool, type, *f, hook, NULL); |
| else |
| return NULL; |
| } |
| |
| |
| |
| /******************** application/x-www-form-urlencoded ********************/ |
| |
| static apr_status_t split_urlword(apr_table_t *t, |
| apr_bucket_brigade *bb, |
| const apr_size_t nlen, |
| const apr_size_t vlen) |
| { |
| apr_pool_t *pool = apr_table_elts(t)->pool; |
| apreq_param_t *param = apr_palloc(pool, nlen + vlen + 1 + sizeof *param); |
| apreq_value_t *v = ¶m->v; |
| apr_bucket *e, *end; |
| apr_status_t s; |
| struct iovec vec[APREQ_NELTS]; |
| apr_array_header_t arr; |
| |
| arr.pool = pool; |
| arr.elt_size = sizeof(struct iovec); |
| arr.nelts = 0; |
| arr.nalloc = APREQ_NELTS; |
| arr.elts = (char *)vec; |
| |
| param->bb = NULL; |
| param->info = NULL; |
| |
| v->name = v->data + vlen + 1; |
| |
| apr_brigade_partition(bb, nlen+1, &end); |
| |
| for (e = APR_BRIGADE_FIRST(bb); e != end; e = APR_BUCKET_NEXT(e)) { |
| apr_size_t dlen; |
| const char *data; |
| struct iovec *iov; |
| s = apr_bucket_read(e, &data, &dlen, APR_BLOCK_READ); |
| if (s != APR_SUCCESS) |
| return s; |
| |
| iov = apr_array_push(&arr); |
| iov->iov_base = (char *)data; |
| iov->iov_len = dlen; |
| } |
| |
| ((struct iovec *)arr.elts)[arr.nelts - 1].iov_len--; /* drop '=' sign */ |
| |
| s = apreq_decodev((char *)v->name, &v->size, |
| (struct iovec *)arr.elts, arr.nelts); |
| if (s != APR_SUCCESS) |
| return s; |
| |
| while ((e = APR_BRIGADE_FIRST(bb)) != end) |
| apr_bucket_delete(e); |
| |
| arr.nelts = 0; |
| apr_brigade_partition(bb, vlen + 1, &end); |
| |
| for (e = APR_BRIGADE_FIRST(bb); e != end; e = APR_BUCKET_NEXT(e)) { |
| apr_size_t dlen; |
| const char *data; |
| struct iovec *vec; |
| s = apr_bucket_read(e, &data, &dlen, APR_BLOCK_READ); |
| if (s != APR_SUCCESS) |
| return s; |
| |
| vec = apr_array_push(&arr); |
| vec->iov_base = (char *)data; |
| vec->iov_len = dlen; |
| } |
| |
| if (end != APR_BRIGADE_SENTINEL(bb)) |
| ((struct iovec *)arr.elts)[arr.nelts - 1].iov_len--; /* drop '=' sign */ |
| |
| s = apreq_decodev(v->data, &v->size, |
| (struct iovec *)arr.elts, arr.nelts); |
| if (s != APR_SUCCESS) |
| return s; |
| |
| while ((e = APR_BRIGADE_FIRST(bb)) != end) |
| apr_bucket_delete(e); |
| |
| apr_table_addn(t, v->name, v->data); |
| return APR_SUCCESS; |
| } |
| |
| struct url_ctx { |
| apr_bucket_brigade *bb; |
| enum { |
| URL_NAME, |
| URL_VALUE, |
| URL_COMPLETE, |
| URL_ERROR |
| } status; |
| }; |
| |
| APREQ_DECLARE_PARSER(apreq_parse_urlencoded) |
| { |
| apr_pool_t *pool = apr_table_elts(t)->pool; |
| apr_ssize_t nlen, vlen; |
| apr_bucket *e; |
| struct url_ctx *ctx; |
| |
| if (parser->ctx == NULL) { |
| apr_bucket_alloc_t *bb_alloc = apreq_env_bucket_alloc(env); |
| ctx = apr_palloc(pool, sizeof *ctx); |
| ctx->bb = apr_brigade_create(pool, bb_alloc); |
| parser->ctx = ctx; |
| ctx->status = URL_NAME; |
| } |
| else |
| ctx = parser->ctx; |
| |
| PARSER_STATUS_CHECK(URL); |
| APR_BRIGADE_CONCAT(ctx->bb, bb); |
| |
| parse_url_brigade: |
| |
| ctx->status = URL_NAME; |
| |
| for (e = APR_BRIGADE_FIRST(ctx->bb), nlen = vlen = 0; |
| e != APR_BRIGADE_SENTINEL(ctx->bb); |
| e = APR_BUCKET_NEXT(e)) |
| { |
| apr_size_t off = 0, dlen; |
| const char *data; |
| apr_status_t s; |
| |
| if (APR_BUCKET_IS_EOS(e)) { |
| s = (ctx->status == URL_NAME) ? APR_SUCCESS : |
| split_urlword(t, ctx->bb, nlen, vlen); |
| APR_BRIGADE_CONCAT(bb, ctx->bb); |
| ctx->status = (s == APR_SUCCESS) ? URL_COMPLETE : URL_ERROR; |
| apreq_log(APREQ_DEBUG s, env, "url parser saw EOS"); |
| return s; |
| } |
| s = apr_bucket_read(e, &data, &dlen, APR_BLOCK_READ); |
| if ( s != APR_SUCCESS ) { |
| ctx->status = URL_ERROR; |
| apreq_log(APREQ_ERROR s, env, "apr_bucket_read failed"); |
| return s; |
| } |
| parse_url_bucket: |
| |
| switch (ctx->status) { |
| |
| case URL_NAME: |
| while (off < dlen) { |
| switch (data[off++]) { |
| case '=': |
| ctx->status = URL_VALUE; |
| goto parse_url_bucket; |
| default: |
| ++nlen; |
| } |
| } |
| break; |
| |
| case URL_VALUE: |
| while (off < dlen) { |
| switch (data[off++]) { |
| case '&': |
| case ';': |
| s = split_urlword(t, ctx->bb, nlen, vlen); |
| if (s != APR_SUCCESS) { |
| ctx->status = URL_ERROR; |
| apreq_log(APREQ_ERROR s, env, "split_urlword failed"); |
| return s; |
| } |
| goto parse_url_brigade; |
| default: |
| ++vlen; |
| } |
| } |
| break; |
| default: |
| ; /* not reached */ |
| } |
| } |
| APREQ_BRIGADE_SETASIDE(ctx->bb, pool); |
| return APR_INCOMPLETE; |
| } |
| |
| |
| /********************* header parsing utils ********************/ |
| |
| |
| static apr_status_t split_header(apr_table_t *t, |
| apr_bucket_brigade *bb, |
| const apr_size_t nlen, |
| const apr_size_t glen, |
| const apr_size_t vlen) |
| { |
| apr_pool_t *pool = apr_table_elts(t)->pool; |
| apreq_value_t *v = apr_palloc(pool, nlen + vlen + sizeof *v); |
| apr_size_t off; |
| |
| v->name = v->data + vlen; |
| |
| /* copy name */ |
| |
| off = 0; |
| while (off < nlen) { |
| apr_size_t dlen; |
| const char *data; |
| apr_bucket *f = APR_BRIGADE_FIRST(bb); |
| apr_status_t s = apr_bucket_read(f, &data, &dlen, APR_BLOCK_READ); |
| |
| if ( s != APR_SUCCESS ) |
| return s; |
| |
| if (dlen > nlen - off) { |
| apr_bucket_split(f, nlen - off); |
| dlen = nlen - off; |
| } |
| |
| memcpy((char *)v->name + off, data, dlen); |
| off += dlen; |
| apr_bucket_delete(f); |
| } |
| |
| /* skip gap */ |
| |
| off = 0; |
| while (off < glen) { |
| apr_size_t dlen; |
| const char *data; |
| apr_bucket *f = APR_BRIGADE_FIRST(bb); |
| apr_status_t s = apr_bucket_read(f, &data, &dlen, APR_BLOCK_READ); |
| |
| if ( s != APR_SUCCESS ) |
| return s; |
| |
| if (dlen > glen - off) { |
| apr_bucket_split(f, glen - off); |
| dlen = glen - off; |
| } |
| |
| off += dlen; |
| apr_bucket_delete(f); |
| } |
| |
| /* copy value */ |
| |
| off = 0; |
| while (off < vlen) { |
| apr_size_t dlen; |
| const char *data; |
| apr_bucket *f = APR_BRIGADE_FIRST(bb); |
| apr_status_t s = apr_bucket_read(f, &data, &dlen, APR_BLOCK_READ); |
| |
| if ( s != APR_SUCCESS ) |
| return s; |
| |
| if (dlen > vlen - off) { |
| apr_bucket_split(f, vlen - off); |
| dlen = vlen - off; |
| } |
| |
| memcpy(v->data + off, data, dlen); |
| off += dlen; |
| apr_bucket_delete(f); |
| } |
| |
| ((char *)v->name)[nlen] = 0; |
| |
| /* remove trailing (CR)LF from value */ |
| v->size = vlen - 1; |
| if ( v->size > 0 && v->data[v->size-1] == '\r') |
| v->size--; |
| |
| v->data[v->size] = 0; |
| |
| apr_table_addn(t, v->name, v->data); |
| return APR_SUCCESS; |
| |
| } |
| |
| struct hdr_ctx { |
| apr_bucket_brigade *bb; |
| enum { |
| HDR_NAME, |
| HDR_GAP, |
| HDR_VALUE, |
| HDR_NEWLINE, |
| HDR_CONTINUE, |
| HDR_COMPLETE, |
| HDR_ERROR |
| } status; |
| }; |
| |
| APREQ_DECLARE_PARSER(apreq_parse_headers) |
| { |
| apr_pool_t *pool = apreq_env_pool(env); |
| apr_ssize_t nlen, glen, vlen; |
| apr_bucket *e; |
| struct hdr_ctx *ctx; |
| |
| if (parser->ctx == NULL) { |
| apr_bucket_alloc_t *bb_alloc = apreq_env_bucket_alloc(env); |
| ctx = apr_pcalloc(pool, sizeof *ctx); |
| ctx->bb = apr_brigade_create(pool, bb_alloc); |
| parser->ctx = ctx; |
| } |
| else |
| ctx = parser->ctx; |
| |
| PARSER_STATUS_CHECK(HDR); |
| APR_BRIGADE_CONCAT(ctx->bb, bb); |
| |
| parse_hdr_brigade: |
| |
| ctx->status = HDR_NAME; |
| |
| /* parse the brigade for CRLF_CRLF-terminated header block, |
| * each time starting from the front of the brigade. |
| */ |
| |
| for (e = APR_BRIGADE_FIRST(ctx->bb), nlen = glen = vlen = 0; |
| e != APR_BRIGADE_SENTINEL(ctx->bb); e = APR_BUCKET_NEXT(e)) |
| { |
| apr_size_t off = 0, dlen; |
| const char *data; |
| apr_status_t s; |
| 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) { |
| switch (data[off++]) { |
| |
| case '\n': |
| 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); |
| ctx->status = HDR_COMPLETE; |
| return APR_SUCCESS; |
| |
| case ':': |
| ++glen; |
| ctx->status = HDR_GAP; |
| goto parse_hdr_bucket; |
| |
| default: |
| ++nlen; |
| } |
| |
| } |
| |
| break; |
| |
| |
| case HDR_GAP: |
| |
| while (off < dlen) { |
| switch (data[off++]) { |
| case ' ': |
| case '\t': |
| ++glen; |
| break; |
| |
| case '\n': |
| ctx->status = HDR_NEWLINE; |
| goto parse_hdr_bucket; |
| |
| default: |
| ctx->status = HDR_VALUE; |
| ++vlen; |
| goto parse_hdr_bucket; |
| } |
| } |
| break; |
| |
| |
| case HDR_VALUE: |
| |
| while (off < dlen) { |
| ++vlen; |
| if (data[off++] == '\n') { |
| ctx->status = HDR_NEWLINE; |
| goto parse_hdr_bucket; |
| } |
| } |
| break; |
| |
| |
| case HDR_NEWLINE: |
| |
| if (off == dlen) |
| break; |
| else { |
| switch (data[off]) { |
| |
| case ' ': |
| case '\t': |
| ctx->status = HDR_CONTINUE; |
| ++off; |
| vlen += 2; |
| break; |
| |
| default: |
| /* can parse brigade now */ |
| if (off > 0) |
| apr_bucket_split(e, off); |
| s = split_header(t, ctx->bb, nlen, glen, vlen); |
| if (s != APR_SUCCESS) { |
| ctx->status = HDR_ERROR; |
| return s; |
| } |
| goto parse_hdr_brigade; |
| } |
| |
| /* cases ' ', '\t' fall through to HDR_CONTINUE */ |
| } |
| |
| |
| case HDR_CONTINUE: |
| |
| while (off < dlen) { |
| switch (data[off++]) { |
| case ' ': |
| case '\t': |
| ++vlen; |
| break; |
| |
| case '\n': |
| ctx->status = HDR_NEWLINE; |
| goto parse_hdr_bucket; |
| |
| default: |
| ctx->status = HDR_VALUE; |
| ++vlen; |
| goto parse_hdr_bucket; |
| } |
| } |
| break; |
| |
| default: |
| ; /* not reached */ |
| } |
| } |
| APREQ_BRIGADE_SETASIDE(ctx->bb,pool); |
| return APR_INCOMPLETE; |
| } |
| |
| |
| /********************* multipart/form-data *********************/ |
| |
| APR_INLINE |
| static apr_status_t brigade_start_string(apr_bucket_brigade *bb, |
| const char *start_string) |
| { |
| apr_bucket *e; |
| apr_size_t slen = strlen(start_string); |
| |
| for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); |
| e = APR_BUCKET_NEXT(e)) |
| { |
| const char *buf; |
| apr_status_t s, bytes_to_check; |
| apr_size_t blen; |
| |
| if (slen == 0) |
| return APR_SUCCESS; |
| |
| if (APR_BUCKET_IS_EOS(e)) |
| return APR_EOF; |
| |
| s = apr_bucket_read(e, &buf, &blen, APR_BLOCK_READ); |
| |
| if (s != APR_SUCCESS) |
| return s; |
| |
| if (blen == 0) |
| continue; |
| |
| bytes_to_check = MIN(slen,blen); |
| |
| if (strncmp(buf,start_string,bytes_to_check) != 0) |
| return APR_EGENERAL; |
| |
| slen -= bytes_to_check; |
| start_string += bytes_to_check; |
| } |
| |
| /* slen > 0, so brigade isn't large enough yet */ |
| return APR_INCOMPLETE; |
| } |
| |
| struct mfd_ctx { |
| apr_table_t *info; |
| apr_bucket_brigade *in; |
| apr_bucket_brigade *bb; |
| apreq_parser_t *hdr_parser; |
| apreq_parser_t *mix_parser; |
| const apr_strmatch_pattern *pattern; |
| char *bdry; |
| enum { |
| MFD_INIT, |
| MFD_NEXTLINE, |
| MFD_HEADER, |
| MFD_POST_HEADER, |
| MFD_PARAM, |
| MFD_UPLOAD, |
| MFD_MIXED, |
| MFD_COMPLETE, |
| MFD_ERROR |
| } status; |
| apr_bucket *eos; |
| const char *param_name; |
| apreq_param_t *upload; |
| }; |
| |
| |
| static apr_status_t split_on_bdry(apr_bucket_brigade *out, |
| apr_bucket_brigade *in, |
| const apr_strmatch_pattern *pattern, |
| const char *bdry) |
| { |
| apr_bucket *e = APR_BRIGADE_FIRST(in); |
| apr_size_t blen = strlen(bdry), off = 0; |
| |
| while ( e != APR_BRIGADE_SENTINEL(in) ) { |
| apr_ssize_t idx; |
| apr_size_t len; |
| const char *buf; |
| apr_status_t s; |
| |
| if (APR_BUCKET_IS_EOS(e)) |
| return APR_EOF; |
| |
| s = apr_bucket_read(e, &buf, &len, APR_BLOCK_READ); |
| if (s != APR_SUCCESS) |
| return s; |
| |
| if (len == 0) { |
| apr_bucket *f = e; |
| e = APR_BUCKET_NEXT(e); |
| apr_bucket_delete(f); |
| continue; |
| } |
| |
| look_for_boundary_up_front: |
| if (strncmp(bdry + off, buf, MIN(len, blen - off)) == 0) { |
| if ( len >= blen - off ) { |
| /* complete match */ |
| if (len > blen - off) |
| apr_bucket_split(e, blen - off); |
| e = APR_BUCKET_NEXT(e); |
| |
| do { |
| apr_bucket *f = APR_BRIGADE_FIRST(in); |
| apr_bucket_delete(f); |
| } while (APR_BRIGADE_FIRST(in) != e); |
| |
| return APR_SUCCESS; |
| } |
| /* partial match */ |
| off += len; |
| e = APR_BUCKET_NEXT(e); |
| continue; |
| } |
| else if (off > 0) { |
| /* prior (partial) strncmp failed, |
| * so we can move previous buckets across |
| * and retest buf against the full bdry. |
| */ |
| do { |
| apr_bucket *f = APR_BRIGADE_FIRST(in); |
| APR_BUCKET_REMOVE(f); |
| APR_BRIGADE_INSERT_TAIL(out, f); |
| } while (e != APR_BRIGADE_FIRST(in)); |
| off = 0; |
| goto look_for_boundary_up_front; |
| } |
| |
| if (pattern != NULL && len >= blen) { |
| const char *match = apr_strmatch(pattern, buf, len); |
| if (match != NULL) |
| idx = match - buf; |
| else { |
| idx = apreq_index(buf + len-blen, blen, bdry, blen, |
| APREQ_MATCH_PARTIAL); |
| if (idx >= 0) |
| idx += len-blen; |
| } |
| } |
| else |
| idx = apreq_index(buf, len, bdry, blen, APREQ_MATCH_PARTIAL); |
| |
| /* Theoretically idx should never be 0 here, because we |
| * already tested the front of the brigade for a potential match. |
| * However, it doesn't hurt to allow for the possibility, |
| * since this will just start the whole loop over again. |
| */ |
| if (idx >= 0) |
| apr_bucket_split(e, idx); |
| |
| APR_BUCKET_REMOVE(e); |
| APR_BRIGADE_INSERT_TAIL(out, e); |
| e = APR_BRIGADE_FIRST(in); |
| } |
| |
| return APR_INCOMPLETE; |
| } |
| |
| #define MAX_FILE_BUCKET_LENGTH ((apr_off_t) 1 << (6 * sizeof(apr_size_t))) |
| |
| APREQ_DECLARE(apr_status_t) apreq_brigade_concat(void *env, |
| apr_bucket_brigade *out, |
| apr_bucket_brigade *in) |
| { |
| apr_bucket *last = APR_BRIGADE_LAST(out); |
| apr_status_t s; |
| apr_bucket_file *f; |
| apr_bucket *e; |
| apr_off_t wlen; |
| |
| if (APR_BUCKET_IS_EOS(last)) |
| return APR_EOF; |
| |
| if (! APR_BUCKET_IS_FILE(last)) { |
| apr_file_t *file; |
| apr_off_t len; |
| apr_ssize_t max_brigade = apreq_env_max_brigade(env,-1); |
| |
| s = apr_brigade_length(out, 1, &len); |
| if (s != APR_SUCCESS) |
| return s; |
| |
| if (max_brigade < 0 || len < max_brigade) { |
| APR_BRIGADE_CONCAT(out, in); |
| return APR_SUCCESS; |
| } |
| |
| s = apreq_file_mktemp(&file, apreq_env_pool(env), |
| apreq_env_temp_dir(env,NULL)); |
| if (s != APR_SUCCESS) |
| return s; |
| |
| s = apreq_brigade_fwrite(file, &wlen, out); |
| |
| if (s != APR_SUCCESS) |
| return s; |
| |
| last = apr_bucket_file_create(file, wlen, 0, out->p, out->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(out, last); |
| } |
| |
| f = last->data; |
| |
| if (last->length > MAX_FILE_BUCKET_LENGTH) { |
| apr_bucket_copy(last, &e); |
| APR_BRIGADE_INSERT_TAIL(out, e); |
| e->length = 0; |
| e->start = last->length + 1; |
| last = e; |
| } |
| |
| e = APR_BRIGADE_LAST(in); |
| if (APR_BUCKET_IS_EOS(e)) { |
| APR_BUCKET_REMOVE(e); |
| APR_BRIGADE_INSERT_TAIL(out, e); |
| } |
| |
| /* Write the remaining buckets to disk, then delete them. */ |
| s = apreq_brigade_fwrite(f->fd, &wlen, in); |
| apr_brigade_cleanup(in); |
| |
| if (s == APR_SUCCESS) |
| last->length += wlen; |
| return s; |
| } |
| |
| static struct mfd_ctx *create_multipart_context(void *env, |
| char *enctype) |
| { |
| apr_status_t s; |
| char *ct; |
| apr_size_t blen; |
| apr_pool_t *pool = apreq_env_pool(env); |
| apr_bucket_alloc_t *bucket_alloc = apreq_env_bucket_alloc(env); |
| struct mfd_ctx *ctx = apr_palloc(pool, sizeof *ctx); |
| |
| ctx->status = MFD_INIT; |
| |
| ct = strchr(enctype, ';'); |
| if (ct == NULL) { |
| ctx->status = MFD_ERROR; |
| apreq_log(APREQ_ERROR APR_EGENERAL, env, |
| "mfd parser cannot find required " |
| "semicolon in supplied Content-Type header"); |
| return NULL; |
| } |
| *ct++ = 0; |
| |
| s = apreq_header_attribute(ct, "boundary", 8, |
| (const char **)&ctx->bdry, &blen); |
| if (s != APR_SUCCESS) { |
| ctx->status = MFD_ERROR; |
| apreq_log(APREQ_ERROR APR_EGENERAL, env, |
| "mfd parser cannot find boundary " |
| "attribute in supplied Content-Type header"); |
| return NULL; |
| } |
| ctx->bdry[blen] = 0; |
| |
| *--ctx->bdry = '-'; |
| *--ctx->bdry = '-'; |
| *--ctx->bdry = '\n'; |
| *--ctx->bdry = '\r'; |
| |
| ctx->pattern = apr_strmatch_precompile(pool,ctx->bdry,1); |
| ctx->hdr_parser = apreq_make_parser(pool, "", apreq_parse_headers, |
| NULL,NULL); |
| ctx->info = NULL; |
| ctx->bb = apr_brigade_create(pool, bucket_alloc); |
| ctx->in = apr_brigade_create(pool, bucket_alloc); |
| ctx->eos = apr_bucket_eos_create(bucket_alloc); |
| ctx->mix_parser = NULL; |
| ctx->param_name = NULL; |
| ctx->upload = NULL; |
| return ctx; |
| } |
| |
| APREQ_DECLARE_PARSER(apreq_parse_multipart) |
| { |
| apr_pool_t *pool = apreq_env_pool(env); |
| struct mfd_ctx *ctx = parser->ctx; |
| apr_status_t s; |
| |
| if (ctx == NULL) { |
| ctx = create_multipart_context(env, apr_pstrdup(pool, |
| parser->enctype)); |
| if (ctx == NULL) |
| return APR_EGENERAL; |
| |
| ctx->mix_parser = apreq_make_parser(pool, "", apreq_parse_multipart, |
| NULL, parser->hook); |
| parser->ctx = ctx; |
| } |
| else if (ctx->status == MFD_COMPLETE) { |
| apreq_log(APREQ_DEBUG APR_SUCCESS, env, |
| "mfd parser is already complete- " |
| "all further input brigades will be ignored."); |
| return APR_SUCCESS; |
| } |
| |
| PARSER_STATUS_CHECK(MFD); |
| APR_BRIGADE_CONCAT(ctx->in, bb); |
| |
| mfd_parse_brigade: |
| |
| switch (ctx->status) { |
| |
| case MFD_INIT: |
| { |
| s = split_on_bdry(ctx->bb, ctx->in, NULL, ctx->bdry + 2); |
| if (s != APR_SUCCESS) { |
| APREQ_BRIGADE_SETASIDE(ctx->in, pool); |
| APREQ_BRIGADE_SETASIDE(ctx->bb, pool); |
| return s; |
| } |
| ctx->status = MFD_NEXTLINE; |
| /* Be polite and return any preamble text to the caller. */ |
| APR_BRIGADE_CONCAT(bb, ctx->bb); |
| } |
| |
| /* fall through */ |
| |
| case MFD_NEXTLINE: |
| { |
| s = split_on_bdry(ctx->bb, ctx->in, NULL, CRLF); |
| if (s != APR_SUCCESS) { |
| APREQ_BRIGADE_SETASIDE(ctx->in, pool); |
| APREQ_BRIGADE_SETASIDE(ctx->bb, pool); |
| return s; |
| } |
| if (!APR_BRIGADE_EMPTY(ctx->bb)) { |
| /* ctx->bb probably contains "--", but we'll stop here |
| * without bothering to check, and just |
| * return any postamble text to caller. |
| */ |
| APR_BRIGADE_CONCAT(bb, ctx->in); |
| ctx->status = MFD_COMPLETE; |
| return APR_SUCCESS; |
| } |
| |
| ctx->status = MFD_HEADER; |
| ctx->info = NULL; |
| } |
| /* fall through */ |
| |
| case MFD_HEADER: |
| { |
| if (ctx->info == NULL) { |
| ctx->info = apr_table_make(pool, APREQ_NELTS); |
| /* flush out old header parser internal structs for reuse */ |
| ctx->hdr_parser->ctx = NULL; |
| } |
| s = APREQ_RUN_PARSER(ctx->hdr_parser, env, ctx->info, ctx->in); |
| switch (s) { |
| case APR_SUCCESS: |
| ctx->status = MFD_POST_HEADER; |
| break; |
| case APR_INCOMPLETE: |
| APREQ_BRIGADE_SETASIDE(ctx->in, pool); |
| return APR_INCOMPLETE; |
| default: |
| ctx->status = MFD_ERROR; |
| return s; |
| } |
| } |
| /* fall through */ |
| |
| case MFD_POST_HEADER: |
| { |
| /* Must handle special case of missing CRLF (mainly |
| coming from empty file uploads). See RFC2065 S5.1.1: |
| |
| body-part = MIME-part-header [CRLF *OCTET] |
| |
| So the CRLF we already matched in MFD_HEADER may have been |
| part of the boundary string! Both Konqueror (v??) and |
| Mozilla-0.97 are known to emit such blocks. |
| |
| Here we first check for this condition with |
| brigade_start_string, and prefix the brigade with |
| an additional CRLF bucket if necessary. |
| */ |
| |
| const char *cd, *name, *filename; |
| apr_size_t nlen, flen; |
| apr_bucket *e; |
| |
| switch (brigade_start_string(ctx->in, ctx->bdry + 2)) { |
| |
| case APR_INCOMPLETE: |
| APREQ_BRIGADE_SETASIDE(ctx->in, pool); |
| return APR_INCOMPLETE; |
| |
| case APR_SUCCESS: |
| /* part has no body- return CRLF to front */ |
| e = apr_bucket_immortal_create(CRLF, 2, |
| ctx->bb->bucket_alloc); |
| APR_BRIGADE_INSERT_HEAD(ctx->in,e); |
| break; |
| |
| default: |
| ; /* has body, ok */ |
| } |
| |
| cd = apr_table_get(ctx->info, "Content-Disposition"); |
| |
| if (cd != NULL) { |
| |
| if (ctx->mix_parser != NULL) { |
| /* multipart/form-data */ |
| |
| s = apreq_header_attribute(cd, "name", 4, &name, &nlen); |
| if (s != APR_SUCCESS) { |
| ctx->status = MFD_ERROR; |
| return APR_EGENERAL; |
| } |
| |
| s = apreq_header_attribute(cd, "filename", |
| 8, &filename, &flen); |
| if (s != APR_SUCCESS) { |
| const char *ct = apr_table_get(ctx->info, |
| "Content-Type"); |
| if (ct != NULL |
| && strncmp(ct, "multipart/mixed", 15) == 0) |
| { |
| struct mfd_ctx *mix_ctx; |
| mix_ctx = create_multipart_context(env, |
| apr_pstrdup(pool, ct)); |
| if (mix_ctx == NULL) { |
| ctx->status = MFD_ERROR; |
| return APR_EGENERAL; |
| } |
| |
| mix_ctx->param_name = apr_pstrmemdup(pool, |
| name, nlen); |
| ctx->mix_parser->ctx = mix_ctx; |
| ctx->status = MFD_MIXED; |
| goto mfd_parse_brigade; |
| } |
| ctx->param_name = apr_pstrmemdup(pool, name, nlen); |
| ctx->status = MFD_PARAM; |
| } |
| else { |
| apreq_param_t *param; |
| param = apreq_make_param(pool, name, nlen, |
| filename, flen); |
| param->info = ctx->info; |
| param->bb = apr_brigade_create(pool, |
| ctx->bb->bucket_alloc); |
| ctx->upload = param; |
| ctx->status = MFD_UPLOAD; |
| goto mfd_parse_brigade; |
| |
| } |
| } |
| else { |
| /* multipart/mixed */ |
| s = apreq_header_attribute(cd, "filename", |
| 8, &filename, &flen); |
| if (s != APR_SUCCESS) { |
| ctx->status = MFD_PARAM; |
| } |
| else { |
| apreq_param_t *param; |
| name = ctx->param_name; |
| nlen = strlen(name); |
| param = apreq_make_param(pool, name, nlen, |
| filename, flen); |
| param->info = ctx->info; |
| param->bb = apr_brigade_create(pool, |
| ctx->bb->bucket_alloc); |
| ctx->upload = param; |
| ctx->status = MFD_UPLOAD; |
| goto mfd_parse_brigade; |
| } |
| } |
| } |
| else { |
| /* multipart/related */ |
| apreq_param_t *param; |
| cd = apr_table_get(ctx->info, "Content-ID"); |
| if (cd == NULL) { |
| ctx->status = MFD_ERROR; |
| return APR_EGENERAL; |
| } |
| name = cd; |
| nlen = strlen(name); |
| filename = ""; |
| flen = 0; |
| param = apreq_make_param(pool, name, nlen, |
| filename, flen); |
| param->info = ctx->info; |
| param->bb = apr_brigade_create(pool, |
| ctx->bb->bucket_alloc); |
| ctx->upload = param; |
| ctx->status = MFD_UPLOAD; |
| goto mfd_parse_brigade; |
| } |
| |
| } |
| /* fall through */ |
| |
| case MFD_PARAM: |
| { |
| apreq_param_t *param; |
| apreq_value_t *v; |
| apr_size_t len; |
| apr_off_t off; |
| |
| s = split_on_bdry(ctx->bb, ctx->in, ctx->pattern, ctx->bdry); |
| |
| switch (s) { |
| |
| case APR_INCOMPLETE: |
| APREQ_BRIGADE_SETASIDE(ctx->in, pool); |
| APREQ_BRIGADE_SETASIDE(ctx->bb,pool); |
| return s; |
| |
| case APR_SUCCESS: |
| s = apr_brigade_length(ctx->bb, 1, &off); |
| if (s != APR_SUCCESS) { |
| ctx->status = MFD_ERROR; |
| return s; |
| } |
| len = off; |
| param = apr_palloc(pool, len + sizeof *param); |
| param->bb = NULL; |
| param->info = ctx->info; |
| |
| v = ¶m->v; |
| v->name = ctx->param_name; |
| apr_brigade_flatten(ctx->bb, v->data, &len); |
| v->size = len; |
| v->data[v->size] = 0; |
| apr_table_addn(t, v->name, v->data); |
| ctx->status = MFD_NEXTLINE; |
| ctx->param_name = NULL; |
| apr_brigade_cleanup(ctx->bb); |
| goto mfd_parse_brigade; |
| |
| default: |
| ctx->status = MFD_ERROR; |
| return s; |
| } |
| |
| |
| } |
| break; /* not reached */ |
| |
| case MFD_UPLOAD: |
| { |
| apreq_param_t *param = ctx->upload; |
| |
| s = split_on_bdry(ctx->bb, ctx->in, ctx->pattern, ctx->bdry); |
| switch (s) { |
| |
| case APR_INCOMPLETE: |
| if (parser->hook) { |
| s = APREQ_RUN_HOOK(parser->hook, env, param, ctx->bb); |
| if (s != APR_SUCCESS) { |
| ctx->status = MFD_ERROR; |
| return s; |
| } |
| } |
| APREQ_BRIGADE_SETASIDE(ctx->bb, pool); |
| APREQ_BRIGADE_SETASIDE(ctx->in, pool); |
| s = apreq_brigade_concat(env, param->bb, ctx->bb); |
| return (s == APR_SUCCESS) ? APR_INCOMPLETE : s; |
| |
| case APR_SUCCESS: |
| if (parser->hook) { |
| APR_BRIGADE_INSERT_TAIL(ctx->bb, ctx->eos); |
| s = APREQ_RUN_HOOK(parser->hook, env, param, ctx->bb); |
| APR_BUCKET_REMOVE(ctx->eos); |
| if (s != APR_SUCCESS) { |
| ctx->status = MFD_ERROR; |
| return s; |
| } |
| } |
| apr_table_addn(t, param->v.name, param->v.data); |
| APREQ_BRIGADE_SETASIDE(ctx->bb, pool); |
| s = apreq_brigade_concat(env, param->bb, ctx->bb); |
| |
| if (s != APR_SUCCESS) |
| return s; |
| |
| ctx->status = MFD_NEXTLINE; |
| goto mfd_parse_brigade; |
| |
| default: |
| ctx->status = MFD_ERROR; |
| return s; |
| } |
| |
| } |
| break; /* not reached */ |
| |
| |
| case MFD_MIXED: |
| { |
| s = APREQ_RUN_PARSER(ctx->mix_parser, env, t, ctx->in); |
| switch (s) { |
| case APR_SUCCESS: |
| ctx->status = MFD_INIT; |
| goto mfd_parse_brigade; |
| case APR_INCOMPLETE: |
| apr_brigade_cleanup(ctx->in); |
| return APR_INCOMPLETE; |
| default: |
| ctx->status = MFD_ERROR; |
| return s; |
| } |
| |
| } |
| break; /* not reached */ |
| |
| default: |
| return APR_EGENERAL; |
| } |
| |
| return APR_INCOMPLETE; |
| } |
| |
| APREQ_DECLARE_HOOK(apreq_hook_disable_uploads) |
| { |
| apreq_log(APREQ_ERROR APR_EGENERAL, env, |
| "Uploads are disabled for this request."); |
| return APR_EGENERAL; |
| } |
| |
| APREQ_DECLARE_HOOK(apreq_hook_discard_brigade) |
| { |
| apr_status_t s = APR_SUCCESS; |
| if (hook->next) |
| s = APREQ_RUN_HOOK(hook->next, env, param, bb); |
| apr_brigade_cleanup(bb); |
| return s; |
| } |
| |
| |
| /* generic parser */ |
| |
| struct gen_ctx { |
| apreq_param_t *param; |
| enum { |
| GEN_INCOMPLETE, |
| GEN_COMPLETE, |
| GEN_ERROR |
| } status; |
| }; |
| |
| |
| APREQ_DECLARE_PARSER(apreq_parse_generic) |
| { |
| struct gen_ctx *ctx = parser->ctx; |
| apr_pool_t *pool = apreq_env_pool(env); |
| apr_status_t s = APR_SUCCESS; |
| apr_bucket *e = APR_BRIGADE_LAST(bb); |
| unsigned saw_eos = 0; |
| |
| if (ctx == NULL) { |
| parser->ctx = ctx = apr_palloc(pool, sizeof *ctx); |
| ctx->status = GEN_INCOMPLETE; |
| ctx->param = apreq_make_param(pool, |
| "_dummy_", strlen("_dummy_"), "", 0); |
| ctx->param->bb = apr_brigade_create(pool, apreq_env_bucket_alloc(env)); |
| ctx->param->info = apr_table_make(pool, APREQ_NELTS); |
| } |
| |
| |
| PARSER_STATUS_CHECK(GEN); |
| |
| while (e != APR_BRIGADE_SENTINEL(bb)) { |
| if (APR_BUCKET_IS_EOS(e)) { |
| saw_eos = 1; |
| break; |
| } |
| e = APR_BUCKET_PREV(e); |
| } |
| |
| if (parser->hook) { |
| s = APREQ_RUN_HOOK(parser->hook, env, ctx->param, bb); |
| if (s != APR_SUCCESS) { |
| ctx->status = GEN_ERROR; |
| return s; |
| } |
| } |
| |
| APREQ_BRIGADE_SETASIDE(bb, pool); |
| s = apreq_brigade_concat(env, ctx->param->bb, bb); |
| |
| if (s != APR_SUCCESS) { |
| ctx->status = GEN_ERROR; |
| return s; |
| } |
| |
| if (saw_eos) { |
| ctx->status = GEN_COMPLETE; |
| return APR_SUCCESS; |
| } |
| else |
| return APR_INCOMPLETE; |
| } |
| |
| |
| struct xml_ctx { |
| apr_xml_doc *doc; |
| apr_xml_parser *xml_parser; |
| enum { |
| XML_INCOMPLETE, |
| XML_COMPLETE, |
| XML_ERROR |
| } status; |
| }; |
| |
| |
| APREQ_DECLARE_HOOK(apreq_hook_apr_xml_parser) |
| { |
| apr_pool_t *pool = apreq_env_pool(env); |
| struct xml_ctx *ctx = hook->ctx; |
| apr_status_t s = APR_SUCCESS; |
| apr_bucket *e; |
| |
| if (ctx == NULL) { |
| hook->ctx = ctx = apr_palloc(pool, sizeof *ctx); |
| ctx->doc = NULL; |
| ctx->xml_parser = apr_xml_parser_create(pool); |
| ctx->status = XML_INCOMPLETE; |
| } |
| |
| PARSER_STATUS_CHECK(XML); |
| |
| for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); |
| e = APR_BUCKET_NEXT(e)) |
| { |
| const char *data; |
| apr_size_t dlen; |
| |
| if (APR_BUCKET_IS_EOS(e)) { |
| s = apr_xml_parser_done(ctx->xml_parser, &ctx->doc); |
| if (s == APR_SUCCESS) { |
| ctx->status = XML_COMPLETE; |
| if (hook->next) |
| s = APREQ_RUN_HOOK(hook->next, env, param, bb); |
| } |
| else { |
| ctx->status = XML_ERROR; |
| apreq_log(APREQ_ERROR s, env, "apreq_parse_xml: " |
| "apr_xml_parser_done failed"); |
| } |
| return s; |
| } |
| else if (APR_BUCKET_IS_METADATA(e)) { |
| continue; |
| } |
| |
| s = apr_bucket_read(e, &data, &dlen, APR_BLOCK_READ); |
| |
| if (s != APR_SUCCESS) { |
| ctx->status = XML_ERROR; |
| apreq_log(APREQ_ERROR s, env, "apreq_parse_xml: " |
| "apr_bucket_read failed"); |
| return s; |
| } |
| |
| s = apr_xml_parser_feed(ctx->xml_parser, data, dlen); |
| |
| if (s != APR_SUCCESS) { |
| ctx->status = XML_ERROR; |
| apreq_log(APREQ_ERROR s, env, "apreq_parse_xml: " |
| "apr_xml_parser_feed failed"); |
| return s; |
| } |
| |
| } |
| |
| if (hook->next) |
| return APREQ_RUN_HOOK(hook->next, env, param, bb); |
| |
| return APR_SUCCESS; |
| } |
| |