| /* 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 "mod_perl.h" |
| |
| /* helper funcs */ |
| |
| #define MP_FILTER_NAME_FORMAT " %s\n\n\t" |
| |
| #define MP_FILTER_NAME(f) \ |
| (is_modperl_filter(f) \ |
| ? modperl_handler_name( \ |
| ((modperl_filter_ctx_t *)(f)->ctx)->handler) \ |
| : (f)->frec->name) |
| |
| #define MP_FILTER_TYPE(filter) \ |
| (is_modperl_filter(filter->f) \ |
| ? ((modperl_filter_ctx_t *)(filter)->f->ctx)->handler->attrs & \ |
| MP_FILTER_CONNECTION_HANDLER ? "connection" : "request" \ |
| : "unknown") |
| |
| #define MP_FILTER_MODE(filter) \ |
| (filter->mode == MP_INPUT_FILTER_MODE ? "input" : "output") |
| |
| #define MP_FILTER_POOL(f) f->r ? f->r->pool : f->c->pool |
| |
| /* allocate wbucket memory using a sub-pool and not a ap_filter_t |
| * pool, since we may need many of these if the filter is invoked |
| * multiple times */ |
| #define WBUCKET_INIT(filter) \ |
| if (!filter->wbucket) { \ |
| modperl_wbucket_t *wb = \ |
| (modperl_wbucket_t *)apr_pcalloc(filter->temp_pool, \ |
| sizeof(*wb)); \ |
| wb->pool = filter->pool; \ |
| wb->filters = &(filter->f->next); \ |
| wb->outcnt = 0; \ |
| wb->r = NULL; \ |
| wb->header_parse = 0; \ |
| filter->wbucket = wb; \ |
| } |
| |
| #define FILTER_FREE(filter) \ |
| apr_pool_destroy(filter->temp_pool); |
| |
| /* Save the value of $@ if it was set */ |
| #define MP_FILTER_SAVE_ERRSV(tmpsv) \ |
| if (SvTRUE(ERRSV)) { \ |
| tmpsv = newSVsv(ERRSV); \ |
| MP_TRACE_f(MP_FUNC, MP_FILTER_NAME_FORMAT \ |
| "Saving $@='%s'", \ |
| MP_FILTER_NAME(filter->f), \ |
| SvPVX(tmpsv) \ |
| ); \ |
| } |
| |
| /* Restore previously saved value of $@. if there was a filter error |
| * it'd have been logged by modperl_errsv call following |
| * modperl_callback */ |
| #define MP_FILTER_RESTORE_ERRSV(tmpsv) \ |
| if (tmpsv) { \ |
| sv_setsv(ERRSV, tmpsv); \ |
| MP_TRACE_f(MP_FUNC, MP_FILTER_NAME_FORMAT \ |
| "Restoring $@='%s'", \ |
| MP_FILTER_NAME(filter->f), \ |
| SvPVX(tmpsv) \ |
| ); \ |
| } |
| |
| /* this function is for tracing only, it's not optimized for performance */ |
| static int is_modperl_filter(ap_filter_t *f) |
| { |
| const char *name = f->frec->name; |
| |
| /* frec->name is always lowercased */ |
| if (!strcasecmp(name, MP_FILTER_CONNECTION_INPUT_NAME) || |
| !strcasecmp(name, MP_FILTER_CONNECTION_OUTPUT_NAME) || |
| !strcasecmp(name, MP_FILTER_REQUEST_INPUT_NAME) || |
| !strcasecmp(name, MP_FILTER_REQUEST_OUTPUT_NAME) ) { |
| return 1; |
| } |
| else { |
| return 0; |
| } |
| } |
| |
| |
| MP_INLINE static apr_status_t send_input_eos(modperl_filter_t *filter) |
| { |
| apr_bucket_alloc_t *ba = filter->f->c->bucket_alloc; |
| apr_bucket *b = apr_bucket_eos_create(ba); |
| APR_BRIGADE_INSERT_TAIL(filter->bb_out, b); |
| ((modperl_filter_ctx_t *)filter->f->ctx)->sent_eos = 1; |
| MP_TRACE_f(MP_FUNC, MP_FILTER_NAME_FORMAT |
| "write out: EOS bucket", MP_FILTER_NAME(filter->f)); |
| return APR_SUCCESS; |
| } |
| |
| MP_INLINE static apr_status_t send_input_flush(modperl_filter_t *filter) |
| { |
| apr_bucket_alloc_t *ba = filter->f->c->bucket_alloc; |
| apr_bucket *b = apr_bucket_flush_create(ba); |
| APR_BRIGADE_INSERT_TAIL(filter->bb_out, b); |
| MP_TRACE_f(MP_FUNC, MP_FILTER_NAME_FORMAT |
| "write out: FLUSH bucket", MP_FILTER_NAME(filter->f)); |
| return APR_SUCCESS; |
| } |
| |
| MP_INLINE static apr_status_t send_output_eos(ap_filter_t *f) |
| { |
| apr_bucket_alloc_t *ba = f->c->bucket_alloc; |
| apr_bucket_brigade *bb = apr_brigade_create(MP_FILTER_POOL(f), |
| ba); |
| apr_bucket *b = apr_bucket_eos_create(ba); |
| APR_BRIGADE_INSERT_TAIL(bb, b); |
| ((modperl_filter_ctx_t *)f->ctx)->sent_eos = 1; |
| MP_TRACE_f(MP_FUNC, MP_FILTER_NAME_FORMAT |
| "write out: EOS bucket in separate bb", MP_FILTER_NAME(f)); |
| return ap_pass_brigade(f->next, bb); |
| } |
| |
| MP_INLINE static apr_status_t send_output_flush(ap_filter_t *f) |
| { |
| apr_bucket_alloc_t *ba = f->c->bucket_alloc; |
| apr_bucket_brigade *bb = apr_brigade_create(MP_FILTER_POOL(f), |
| ba); |
| apr_bucket *b = apr_bucket_flush_create(ba); |
| APR_BRIGADE_INSERT_TAIL(bb, b); |
| MP_TRACE_f(MP_FUNC, MP_FILTER_NAME_FORMAT |
| "write out: FLUSH bucket in separate bb", MP_FILTER_NAME(f)); |
| return ap_pass_brigade(f, bb); |
| } |
| |
| /* simple buffer api */ |
| |
| MP_INLINE apr_status_t modperl_wbucket_pass(modperl_wbucket_t *wb, |
| const char *buf, apr_size_t len, |
| int add_flush_bucket) |
| { |
| apr_bucket_alloc_t *ba = (*wb->filters)->c->bucket_alloc; |
| apr_bucket_brigade *bb; |
| apr_bucket *bucket; |
| |
| /* reset the counter to 0 as early as possible and in one place, |
| * since this function will always either pass the data out (and |
| * it has 'len' already) or return an error. |
| */ |
| wb->outcnt = 0; |
| |
| if (wb->header_parse) { |
| request_rec *r = wb->r; |
| const char *body; |
| int status; |
| |
| MP_TRACE_f(MP_FUNC, "parsing headers: %db [%s]", len, |
| MP_TRACE_STR_TRUNC(wb->pool, buf, len)); |
| |
| status = modperl_cgi_header_parse(r, (char *)buf, &len, &body); |
| |
| wb->header_parse = 0; /* only once per-request */ |
| |
| if (status == HTTP_MOVED_TEMPORARILY) { |
| return APR_SUCCESS; /* XXX: HTTP_MOVED_TEMPORARILY ? */ |
| } |
| else if (status != OK) { |
| ap_log_error(APLOG_MARK, APLOG_WARNING, |
| 0, r->server, "%s did not send an HTTP header", |
| r->uri); |
| r->status = status; |
| /* XXX: body == NULL here */ |
| return APR_SUCCESS; |
| } |
| else if (!len) { |
| return APR_SUCCESS; |
| } |
| |
| buf = body; |
| } |
| |
| /* this is a note for filter writers who may decide that there is |
| * a bug in mod_perl. We send a transient bucket. That means that |
| * this bucket can't be stored inside a filter without copying the |
| * data in it. This is done automatically by apr_bucket_setaside, |
| * which is written exactly for the purpose to make setaside |
| * operation transparent to the kind of bucket. |
| */ |
| bucket = apr_bucket_transient_create(buf, len, ba); |
| bb = apr_brigade_create(wb->pool, ba); |
| APR_BRIGADE_INSERT_TAIL(bb, bucket); |
| |
| if (add_flush_bucket) { |
| /* append the flush bucket rather then calling ap_rflush, to |
| * prevent a creation of yet another bb, which will cause an |
| * extra call for each filter in the chain */ |
| apr_bucket *bucket = apr_bucket_flush_create(ba); |
| APR_BRIGADE_INSERT_TAIL(bb, bucket); |
| } |
| |
| MP_TRACE_f(MP_FUNC, "\n\n\twrite out: %db [%s]" |
| "\t\tfrom %s\n\t\tto %s filter handler", |
| len, |
| MP_TRACE_STR_TRUNC(wb->pool, buf, len), |
| ((wb->r && wb->filters == &wb->r->output_filters) |
| ? "response handler" : "current filter handler"), |
| MP_FILTER_NAME(*(wb->filters))); |
| |
| return ap_pass_brigade(*(wb->filters), bb); |
| } |
| |
| /* flush data if any, |
| * if add_flush_bucket is TRUE |
| * if there is data to flush |
| * a flush bucket is added to the tail of bb with data |
| * else |
| * a flush bucket is sent in its own bb |
| * else |
| * nothing is sent |
| */ |
| MP_INLINE apr_status_t modperl_wbucket_flush(modperl_wbucket_t *wb, |
| int add_flush_bucket) |
| { |
| apr_status_t rv = APR_SUCCESS; |
| |
| if (wb->outcnt) { |
| rv = modperl_wbucket_pass(wb, wb->outbuf, wb->outcnt, |
| add_flush_bucket); |
| } |
| else if (add_flush_bucket) { |
| rv = send_output_flush(*(wb->filters)); |
| } |
| |
| return rv; |
| } |
| |
| MP_INLINE apr_status_t modperl_wbucket_write(pTHX_ modperl_wbucket_t *wb, |
| const char *buf, |
| apr_size_t *wlen) |
| { |
| apr_size_t len = *wlen; |
| *wlen = 0; |
| |
| if ((len + wb->outcnt) > sizeof(wb->outbuf)) { |
| apr_status_t rv; |
| if ((rv = modperl_wbucket_flush(wb, FALSE)) != APR_SUCCESS) { |
| return rv; |
| } |
| } |
| |
| if (len >= sizeof(wb->outbuf)) { |
| *wlen = len; |
| return modperl_wbucket_pass(wb, buf, len, FALSE); |
| } |
| else { |
| memcpy(&wb->outbuf[wb->outcnt], buf, len); |
| wb->outcnt += len; |
| *wlen = len; |
| return APR_SUCCESS; |
| } |
| } |
| |
| /* generic filter routines */ |
| |
| /* all ap_filter_t filter cleanups should go here */ |
| static apr_status_t modperl_filter_f_cleanup(void *data) |
| { |
| ap_filter_t *f = (ap_filter_t *)data; |
| modperl_filter_ctx_t *ctx = (modperl_filter_ctx_t *)(f->ctx); |
| |
| /* mod_perl filter ctx cleanup */ |
| if (ctx->data){ |
| #ifdef USE_ITHREADS |
| dTHXa(ctx->interp->perl); |
| // MP_ASSERT_CONTEXT(aTHX); |
| #endif |
| if (SvOK(ctx->data) && SvREFCNT(ctx->data)) { |
| SvREFCNT_dec(ctx->data); |
| ctx->data = NULL; |
| } |
| MP_INTERP_PUTBACK(ctx->interp, aTHX); |
| } |
| |
| return APR_SUCCESS; |
| } |
| |
| modperl_filter_t *modperl_filter_new(ap_filter_t *f, |
| apr_bucket_brigade *bb, |
| modperl_filter_mode_e mode, |
| ap_input_mode_t input_mode, |
| apr_read_type_e block, |
| apr_off_t readbytes) |
| { |
| apr_pool_t *p = MP_FILTER_POOL(f); |
| apr_pool_t *temp_pool; |
| modperl_filter_t *filter; |
| |
| /* we can't allocate memory from the pool here, since potentially |
| * a filter can be called hundreds of times during the same |
| * request/connection resulting in enormous memory demands |
| * (sizeof(*filter)*number of invocations). so we use a sub-pool |
| * which will get destroyed at the end of each modperl_filter |
| * invocation. |
| */ |
| apr_status_t rv = apr_pool_create(&temp_pool, p); |
| if (rv != APR_SUCCESS) { |
| /* XXX: how do we handle the error? assert? */ |
| return NULL; |
| } |
| filter = (modperl_filter_t *)apr_pcalloc(temp_pool, sizeof(*filter)); |
| |
| #ifdef MP_DEBUG |
| apr_pool_tag(temp_pool, "mod_perl temp filter"); |
| #endif |
| |
| filter->temp_pool = temp_pool; |
| filter->mode = mode; |
| filter->f = f; |
| filter->pool = p; |
| filter->wbucket = NULL; |
| |
| if (mode == MP_INPUT_FILTER_MODE) { |
| filter->bb_in = NULL; |
| filter->bb_out = bb; |
| filter->input_mode = input_mode; |
| filter->block = block; |
| filter->readbytes = readbytes; |
| } |
| else { |
| filter->bb_in = bb; |
| filter->bb_out = NULL; |
| } |
| |
| MP_TRACE_f(MP_FUNC, MP_FILTER_NAME_FORMAT |
| "new: %s %s filter (modperl_filter_t *0x%lx), " |
| "f (ap_filter_t *0x%lx)", |
| MP_FILTER_NAME(f), |
| MP_FILTER_TYPE(filter), |
| MP_FILTER_MODE(filter), |
| (unsigned long)filter, |
| (unsigned long)filter->f); |
| |
| return filter; |
| } |
| |
| static void modperl_filter_mg_set(pTHX_ SV *obj, modperl_filter_t *filter) |
| { |
| sv_magic(SvRV(obj), (SV *)NULL, PERL_MAGIC_ext, NULL, -1); |
| SvMAGIC(SvRV(obj))->mg_ptr = (char *)filter; |
| } |
| |
| modperl_filter_t *modperl_filter_mg_get(pTHX_ SV *obj) |
| { |
| MAGIC *mg = mg_find(SvRV(obj), PERL_MAGIC_ext); |
| return mg ? (modperl_filter_t *)mg->mg_ptr : NULL; |
| } |
| |
| /* eval "package Foo; \&init_handler" */ |
| int modperl_filter_resolve_init_handler(pTHX_ modperl_handler_t *handler, |
| apr_pool_t *p) |
| { |
| char *init_handler_pv_code = NULL; |
| |
| if (handler->mgv_cv) { |
| GV *gv = modperl_mgv_lookup(aTHX_ handler->mgv_cv); |
| if (gv) { |
| CV *cv = modperl_mgv_cv(gv); |
| if (cv && SvMAGICAL(cv)) { |
| MAGIC *mg = mg_find((SV*)(cv), PERL_MAGIC_ext); |
| init_handler_pv_code = mg ? mg->mg_ptr : NULL; |
| } |
| else { |
| /* XXX: should we complain in such a case? */ |
| return 0; |
| } |
| } |
| } |
| |
| if (init_handler_pv_code) { |
| char *package_name = |
| modperl_mgv_as_string(aTHX_ handler->mgv_cv, p, 1); |
| /* fprintf(stderr, "PACKAGE: %s\n", package_name ); */ |
| |
| /* eval the code in the parent handler's package's context */ |
| char *code = apr_pstrcat(p, "package ", package_name, ";", |
| init_handler_pv_code, NULL); |
| SV *sv; |
| modperl_handler_t *init_handler; |
| |
| ENTER;SAVETMPS; |
| sv = eval_pv(code, TRUE); |
| /* fprintf(stderr, "code: %s\n", code); */ |
| init_handler = modperl_handler_new_from_sv(aTHX_ p, sv); |
| FREETMPS;LEAVE; |
| |
| if (init_handler) { |
| modperl_mgv_resolve(aTHX_ init_handler, p, init_handler->name, 1); |
| |
| MP_TRACE_h(MP_FUNC, "found init handler %s", |
| modperl_handler_name(init_handler)); |
| |
| if (!(init_handler->attrs & MP_FILTER_INIT_HANDLER)) { |
| Perl_croak(aTHX_ "handler %s doesn't have " |
| "the FilterInitHandler attribute set", |
| modperl_handler_name(init_handler)); |
| } |
| |
| handler->next = init_handler; |
| return 1; |
| } |
| else { |
| Perl_croak(aTHX_ "failed to eval code: %s", code); |
| |
| } |
| } |
| |
| return 1; |
| } |
| |
| static int modperl_run_filter_init(ap_filter_t *f, |
| modperl_filter_mode_e mode, |
| modperl_handler_t *handler) |
| { |
| AV *args = (AV *)NULL; |
| int status; |
| |
| request_rec *r = f->r; |
| conn_rec *c = f->c; |
| server_rec *s = r ? r->server : c->base_server; |
| apr_pool_t *p = r ? r->pool : c->pool; |
| modperl_filter_t *filter = modperl_filter_new(f, NULL, mode, 0, 0, 0); |
| |
| MP_dINTERPa(r, c, s); |
| |
| MP_TRACE_h(MP_FUNC, "running filter init handler %s", |
| modperl_handler_name(handler)); |
| |
| modperl_handler_make_args(aTHX_ &args, |
| "Apache2::Filter", f, |
| NULL); |
| |
| modperl_filter_mg_set(aTHX_ AvARRAY(args)[0], filter); |
| |
| /* XXX filter_init return status is propagated back to Apache over |
| * in C land, making it possible to use filter_init to return, say, |
| * BAD_REQUEST. this implementation, however, ignores the return status |
| * even though we're trapping it here - modperl_filter_add_request sees |
| * the error and propagates it, but modperl_output_filter_add_request |
| * is void so the error is lost */ |
| if ((status = modperl_callback(aTHX_ handler, p, r, s, args)) != OK) { |
| status = modperl_errsv(aTHX_ status, r, s); |
| } |
| |
| FILTER_FREE(filter); |
| SvREFCNT_dec((SV*)args); |
| |
| MP_INTERP_PUTBACK(interp, aTHX); |
| |
| MP_TRACE_f(MP_FUNC, MP_FILTER_NAME_FORMAT |
| "return: %d", modperl_handler_name(handler), status); |
| |
| return status; |
| } |
| |
| int modperl_run_filter(modperl_filter_t *filter) |
| { |
| AV *args = (AV *)NULL; |
| SV *errsv = (SV *)NULL; |
| int status; |
| modperl_handler_t *handler = |
| ((modperl_filter_ctx_t *)filter->f->ctx)->handler; |
| |
| request_rec *r = filter->f->r; |
| conn_rec *c = filter->f->c; |
| server_rec *s = r ? r->server : c->base_server; |
| apr_pool_t *p = r ? r->pool : c->pool; |
| |
| MP_dINTERPa(r, c, s); |
| |
| MP_FILTER_SAVE_ERRSV(errsv); |
| |
| modperl_handler_make_args(aTHX_ &args, |
| "Apache2::Filter", filter->f, |
| "APR::Brigade", |
| (filter->mode == MP_INPUT_FILTER_MODE |
| ? filter->bb_out |
| : filter->bb_in), |
| NULL); |
| |
| modperl_filter_mg_set(aTHX_ AvARRAY(args)[0], filter); |
| |
| if (filter->mode == MP_INPUT_FILTER_MODE) { |
| av_push(args, newSViv(filter->input_mode)); |
| av_push(args, newSViv(filter->block)); |
| av_push(args, newSViv(filter->readbytes)); |
| } |
| |
| /* while filters are VOID handlers, we need to log any errors, |
| * because most perl coders will forget to check the return errors |
| * from read() and print() calls. and if the caller is not a perl |
| * program they won't make any sense of ERRSV or $! |
| */ |
| if ((status = modperl_callback(aTHX_ handler, p, r, s, args)) != OK) { |
| status = modperl_errsv(aTHX_ status, r, s); |
| } |
| |
| SvREFCNT_dec((SV*)args); |
| |
| /* when the streaming filter is invoked it should be able to send |
| * extra data, after the read in a while() loop is finished. |
| * Therefore we need to postpone propogating the EOS bucket, up |
| * until the filter handler is returned and only then send the EOS |
| * bucket if the stream had one. |
| */ |
| if (filter->seen_eos) { |
| filter->eos = 1; |
| filter->seen_eos = 0; |
| } |
| |
| if (filter->mode == MP_INPUT_FILTER_MODE) { |
| if (filter->bb_in) { |
| if (status == DECLINED) { |
| /* make sure the filter doesn't try to make mod_perl |
| * pass the bucket brigade through after it called |
| * $f->read(), since it causes a pre-fetch of the |
| * bb */ |
| MP_CROAK_PUTBACK(MODPERL_FILTER_ERROR, |
| "a filter calling $f->read " |
| "must return OK and not DECLINED"); |
| } |
| /* in the streaming mode filter->bb_in is populated on the |
| * first modperl_input_filter_read, so it must be |
| * destroyed at the end of the filter invocation |
| */ |
| apr_brigade_destroy(filter->bb_in); |
| filter->bb_in = NULL; |
| } |
| MP_RUN_CROAK_RESET_OK_PUTBACK(s, modperl_input_filter_flush(filter), |
| "Apache2::Filter internal flush"); |
| } |
| else { |
| MP_RUN_CROAK_RESET_OK_PUTBACK(s, modperl_output_filter_flush(filter), |
| "Apache2::Filter internal flush"); |
| } |
| |
| MP_FILTER_RESTORE_ERRSV(errsv); |
| |
| MP_INTERP_PUTBACK(interp, aTHX); |
| |
| MP_TRACE_f(MP_FUNC, MP_FILTER_NAME_FORMAT |
| "return: %d", modperl_handler_name(handler), status); |
| |
| return status; |
| } |
| |
| /* unrolled APR_BRIGADE_FOREACH loop */ |
| |
| #define MP_FILTER_EMPTY(filter) \ |
| APR_BRIGADE_EMPTY(filter->bb_in) |
| |
| #define MP_FILTER_SENTINEL(filter) \ |
| APR_BRIGADE_SENTINEL(filter->bb_in) |
| |
| #define MP_FILTER_FIRST(filter) \ |
| APR_BRIGADE_FIRST(filter->bb_in) |
| |
| #define MP_FILTER_NEXT(filter) \ |
| APR_BUCKET_NEXT(filter->bucket) |
| |
| #define MP_FILTER_IS_EOS(filter) \ |
| APR_BUCKET_IS_EOS(filter->bucket) |
| |
| #define MP_FILTER_IS_FLUSH(filter) \ |
| APR_BUCKET_IS_FLUSH(filter->bucket) |
| |
| MP_INLINE static int get_bucket(modperl_filter_t *filter) |
| { |
| if (!filter->bb_in || MP_FILTER_EMPTY(filter)) { |
| MP_TRACE_f(MP_FUNC, MP_FILTER_NAME_FORMAT |
| "read in: bucket brigade is empty", |
| MP_FILTER_NAME(filter->f)); |
| return 0; |
| } |
| |
| if (!filter->bucket) { |
| filter->bucket = MP_FILTER_FIRST(filter); |
| } |
| else if (filter->bucket != MP_FILTER_SENTINEL(filter)) { |
| filter->bucket = MP_FILTER_NEXT(filter); |
| } |
| |
| if (filter->bucket == MP_FILTER_SENTINEL(filter)) { |
| filter->bucket = NULL; |
| /* can't destroy bb_in since the next read will need a brigade |
| * to try to read from */ |
| apr_brigade_cleanup(filter->bb_in); |
| return 0; |
| } |
| |
| if (MP_FILTER_IS_EOS(filter)) { |
| MP_TRACE_f(MP_FUNC, MP_FILTER_NAME_FORMAT |
| "read in: EOS bucket", |
| MP_FILTER_NAME(filter->f)); |
| |
| filter->seen_eos = 1; |
| /* there should be only one EOS sent, modperl_filter_read will |
| * not come here, since filter->seen_eos is set |
| */ |
| return 0; |
| } |
| else if (MP_FILTER_IS_FLUSH(filter)) { |
| MP_TRACE_f(MP_FUNC, MP_FILTER_NAME_FORMAT |
| "read in: FLUSH bucket", |
| MP_FILTER_NAME(filter->f)); |
| filter->flush = 1; |
| return 0; |
| } |
| else { |
| return 1; |
| } |
| } |
| |
| |
| MP_INLINE static apr_size_t modperl_filter_read(pTHX_ |
| modperl_filter_t *filter, |
| SV *buffer, |
| apr_size_t wanted) |
| { |
| int num_buckets = 0; |
| apr_size_t len = 0; |
| |
| (void)SvUPGRADE(buffer, SVt_PV); |
| SvCUR(buffer) = 0; |
| |
| /* calling SvPOK_only here may leave buffer an invalid state since |
| * SvPVX may be NULL. But it's very likely that something is copied. |
| * So, we turn the POK flag on here. Later we check if SvPVX is NULL |
| * and turn the flag off again if so. */ |
| SvPOK_only(buffer); |
| |
| /* sometimes the EOS bucket arrives in the same brigade with other |
| * buckets, so that particular read() will not return 0 and will |
| * be called again if called in the while ($filter->read(...)) |
| * loop. In that case we return 0. |
| */ |
| if (filter->seen_eos) { |
| return 0; |
| } |
| |
| /* modperl_brigade_dump(filter->bb_in, NULL); */ |
| |
| MP_TRACE_f(MP_FUNC, MP_FILTER_NAME_FORMAT |
| "wanted: %db", |
| MP_FILTER_NAME(filter->f), |
| wanted); |
| |
| if (filter->remaining) { |
| if (filter->remaining >= wanted) { |
| MP_TRACE_f(MP_FUNC, MP_FILTER_NAME_FORMAT |
| "eating and returning %d [%s]\n\tof " |
| "remaining %db", |
| MP_FILTER_NAME(filter->f), |
| wanted, |
| MP_TRACE_STR_TRUNC(filter->pool, filter->leftover, wanted), |
| filter->remaining); |
| SvGROW(buffer, wanted+1); |
| sv_catpvn(buffer, filter->leftover, wanted); |
| filter->leftover += wanted; |
| filter->remaining -= wanted; |
| return wanted; |
| } |
| else { |
| MP_TRACE_f(MP_FUNC, MP_FILTER_NAME_FORMAT |
| "eating remaining %db", |
| MP_FILTER_NAME(filter->f), |
| filter->remaining); |
| SvGROW(buffer, filter->remaining+1); |
| sv_catpvn(buffer, filter->leftover, filter->remaining); |
| len = filter->remaining; |
| filter->remaining = 0; |
| filter->leftover = NULL; |
| } |
| } |
| |
| while (1) { |
| const char *buf; |
| apr_size_t buf_len; |
| |
| if (!get_bucket(filter)) { |
| break; |
| } |
| |
| num_buckets++; |
| |
| filter->rc = apr_bucket_read(filter->bucket, &buf, &buf_len, 0); |
| |
| if (filter->rc == APR_SUCCESS) { |
| MP_TRACE_f(MP_FUNC, |
| MP_FILTER_NAME_FORMAT |
| "read in: %s bucket with %db (0x%lx)", |
| MP_FILTER_NAME(filter->f), |
| filter->bucket->type->name, |
| buf_len, |
| (unsigned long)filter->bucket); |
| } |
| else { |
| SvREFCNT_dec(buffer); |
| modperl_croak(aTHX_ filter->rc, "Apache2::Filter::read"); |
| } |
| |
| if (buf_len) { |
| if ((SvCUR(buffer) + buf_len) >= wanted) { |
| int nibble = wanted - SvCUR(buffer); |
| SvGROW(buffer, SvCUR(buffer)+nibble+1); |
| sv_catpvn(buffer, buf, nibble); |
| filter->leftover = (char *)buf+nibble; |
| filter->remaining = buf_len - nibble; |
| len += nibble; |
| break; |
| } |
| else { |
| len += buf_len; |
| SvGROW(buffer, SvCUR(buffer)+buf_len+1); |
| sv_catpvn(buffer, buf, buf_len); |
| } |
| } |
| } |
| |
| if (!SvPVX(buffer)) { |
| SvPOK_off(buffer); |
| } |
| |
| MP_TRACE_f(MP_FUNC, |
| MP_FILTER_NAME_FORMAT |
| "return: %db from %d bucket%s [%s]\n\t(%db leftover)", |
| MP_FILTER_NAME(filter->f), |
| len, num_buckets, ((num_buckets == 1) ? "" : "s"), |
| MP_TRACE_STR_TRUNC(filter->pool, SvPVX(buffer), len), |
| filter->remaining); |
| |
| return len; |
| } |
| |
| MP_INLINE apr_size_t modperl_input_filter_read(pTHX_ |
| modperl_filter_t *filter, |
| SV *buffer, |
| apr_size_t wanted) |
| { |
| apr_size_t len = 0; |
| |
| if (!filter->bb_in) { |
| /* This should be read only once per handler invocation! */ |
| filter->bb_in = apr_brigade_create(filter->pool, |
| filter->f->c->bucket_alloc); |
| MP_TRACE_f(MP_FUNC, MP_FILTER_NAME_FORMAT |
| "retrieving bb: 0x%lx", |
| MP_FILTER_NAME(filter->f), |
| (unsigned long)(filter->bb_in)); |
| MP_RUN_CROAK(ap_get_brigade(filter->f->next, filter->bb_in, |
| filter->input_mode, filter->block, |
| filter->readbytes), |
| "Apache2::Filter::read"); |
| } |
| |
| len = modperl_filter_read(aTHX_ filter, buffer, wanted); |
| |
| if (filter->flush && len == 0) { |
| /* if len > 0 then $filter->write will flush */ |
| apr_status_t rc = modperl_input_filter_flush(filter); |
| if (rc != APR_SUCCESS) { |
| SvREFCNT_dec(buffer); |
| modperl_croak(aTHX_ rc, "Apache2::Filter::read"); |
| } |
| } |
| |
| return len; |
| } |
| |
| |
| MP_INLINE apr_size_t modperl_output_filter_read(pTHX_ |
| modperl_filter_t *filter, |
| SV *buffer, |
| apr_size_t wanted) |
| { |
| apr_size_t len = 0; |
| len = modperl_filter_read(aTHX_ filter, buffer, wanted); |
| |
| if (filter->flush && len == 0) { |
| /* if len > 0 then $filter->write will flush */ |
| apr_status_t rc = modperl_output_filter_flush(filter); |
| if (rc != APR_SUCCESS) { |
| SvREFCNT_dec(buffer); |
| modperl_croak(aTHX_ rc, "Apache2::Filter::read"); |
| } |
| } |
| |
| return len; |
| } |
| |
| |
| MP_INLINE apr_status_t modperl_input_filter_flush(modperl_filter_t *filter) |
| { |
| if (((modperl_filter_ctx_t *)filter->f->ctx)->sent_eos) { |
| /* no data should be sent after EOS has been sent */ |
| return filter->rc; |
| } |
| |
| if (filter->flush) { |
| filter->rc = send_input_flush(filter); |
| filter->flush = 0; |
| } |
| |
| if (filter->eos) { |
| filter->rc = send_input_eos(filter); |
| filter->eos = 0; |
| } |
| |
| return filter->rc; |
| } |
| |
| MP_INLINE apr_status_t modperl_output_filter_flush(modperl_filter_t *filter) |
| { |
| int add_flush_bucket = FALSE; |
| |
| if (((modperl_filter_ctx_t *)filter->f->ctx)->sent_eos) { |
| /* no data should be sent after EOS has been sent */ |
| return filter->rc; |
| } |
| |
| if (filter->flush) { |
| add_flush_bucket = TRUE; |
| filter->flush = 0; |
| } |
| |
| WBUCKET_INIT(filter); |
| filter->rc = modperl_wbucket_flush(filter->wbucket, add_flush_bucket); |
| if (filter->rc != APR_SUCCESS) { |
| return filter->rc; |
| } |
| |
| if (filter->eos) { |
| filter->rc = send_output_eos(filter->f); |
| if (filter->bb_in) { |
| apr_brigade_destroy(filter->bb_in); |
| filter->bb_in = NULL; |
| } |
| filter->eos = 0; |
| } |
| |
| return filter->rc; |
| } |
| |
| MP_INLINE apr_status_t modperl_input_filter_write(pTHX_ |
| modperl_filter_t *filter, |
| const char *buf, |
| apr_size_t *len) |
| { |
| apr_bucket_alloc_t *ba = filter->f->c->bucket_alloc; |
| char *copy = apr_pmemdup(filter->pool, buf, *len); |
| apr_bucket *bucket = apr_bucket_transient_create(copy, *len, ba); |
| MP_TRACE_f(MP_FUNC, MP_FILTER_NAME_FORMAT |
| "write out: %db [%s]:", |
| MP_FILTER_NAME(filter->f), *len, |
| MP_TRACE_STR_TRUNC(filter->pool, copy, *len)); |
| APR_BRIGADE_INSERT_TAIL(filter->bb_out, bucket); |
| /* modperl_brigade_dump(filter->bb_out, NULL); */ |
| return APR_SUCCESS; |
| } |
| |
| MP_INLINE apr_status_t modperl_output_filter_write(pTHX_ |
| modperl_filter_t *filter, |
| const char *buf, |
| apr_size_t *len) |
| { |
| WBUCKET_INIT(filter); |
| |
| return modperl_wbucket_write(aTHX_ filter->wbucket, buf, len); |
| } |
| |
| apr_status_t modperl_output_filter_handler(ap_filter_t *f, |
| apr_bucket_brigade *bb) |
| { |
| modperl_filter_t *filter; |
| int status; |
| |
| if (((modperl_filter_ctx_t *)f->ctx)->sent_eos) { |
| MP_TRACE_f(MP_FUNC, |
| MP_FILTER_NAME_FORMAT |
| "write_out: EOS was already sent, " |
| "passing through the brigade", |
| MP_FILTER_NAME(f)); |
| return ap_pass_brigade(f->next, bb); |
| } |
| else { |
| filter = modperl_filter_new(f, bb, MP_OUTPUT_FILTER_MODE, |
| 0, 0, 0); |
| status = modperl_run_filter(filter); |
| FILTER_FREE(filter); |
| } |
| |
| switch (status) { |
| case OK: |
| return APR_SUCCESS; |
| case DECLINED: |
| return ap_pass_brigade(f->next, bb); |
| default: |
| return status; /*XXX*/ |
| } |
| } |
| |
| apr_status_t modperl_input_filter_handler(ap_filter_t *f, |
| apr_bucket_brigade *bb, |
| ap_input_mode_t input_mode, |
| apr_read_type_e block, |
| apr_off_t readbytes) |
| { |
| modperl_filter_t *filter; |
| int status; |
| |
| if (((modperl_filter_ctx_t *)f->ctx)->sent_eos) { |
| MP_TRACE_f(MP_FUNC, |
| MP_FILTER_NAME_FORMAT |
| "write out: EOS was already sent, " |
| "passing through the brigade", |
| MP_FILTER_NAME(f)); |
| return ap_get_brigade(f->next, bb, input_mode, block, readbytes); |
| } |
| else { |
| filter = modperl_filter_new(f, bb, MP_INPUT_FILTER_MODE, |
| input_mode, block, readbytes); |
| status = modperl_run_filter(filter); |
| FILTER_FREE(filter); |
| } |
| |
| switch (status) { |
| case OK: |
| return APR_SUCCESS; |
| case DECLINED: |
| return ap_get_brigade(f->next, bb, input_mode, block, readbytes); |
| case HTTP_INTERNAL_SERVER_ERROR: |
| /* XXX: later may introduce separate error codes for |
| * modperl_run_filter and modperl_run_filter_init */ |
| return MODPERL_FILTER_ERROR; |
| default: |
| return status; /*XXX*/ |
| } |
| } |
| |
| static int modperl_filter_add_connection(conn_rec *c, |
| int idx, |
| const char *name, |
| modperl_filter_add_t addfunc, |
| const char *type) |
| { |
| modperl_config_dir_t *dcfg = |
| modperl_config_dir_get_defaults(c->base_server); |
| MpAV *av; |
| |
| if ((av = dcfg->handlers_per_dir[idx])) { |
| modperl_handler_t **handlers = (modperl_handler_t **)av->elts; |
| int i; |
| |
| for (i=0; i<av->nelts; i++) { |
| modperl_filter_ctx_t *ctx; |
| ap_filter_t *f; |
| |
| /* process non-mod_perl filter handlers */ |
| if ((handlers[i]->attrs & MP_FILTER_HTTPD_HANDLER)) { |
| |
| /* non-mp2 filters below PROTOCOL level can't be added |
| * at the connection level, so we need to go through |
| * the pain of figuring out the type of the filter */ |
| ap_filter_rec_t *frec; |
| char *normalized_name = apr_pstrdup(c->pool, |
| handlers[i]->name); |
| ap_str_tolower(normalized_name); |
| frec = idx == MP_INPUT_FILTER_HANDLER |
| ? ap_get_input_filter_handle(normalized_name) |
| : ap_get_output_filter_handle(normalized_name); |
| if (frec && frec->ftype < AP_FTYPE_PROTOCOL) { |
| MP_TRACE_f(MP_FUNC, "a non-mod_perl %s handler %s " |
| "skipped (not a connection filter)", |
| type, handlers[i]->name); |
| continue; |
| } |
| |
| addfunc(handlers[i]->name, NULL, NULL, c); |
| MP_TRACE_f(MP_FUNC, |
| "a non-mod_perl %s handler %s configured " |
| "(connection)", type, handlers[i]->name); |
| continue; |
| } |
| |
| /* skip non-connection level filters, e.g. request filters |
| * configured outside the resource container */ |
| if (!(handlers[i]->attrs & MP_FILTER_CONNECTION_HANDLER)) { |
| MP_TRACE_f(MP_FUNC, |
| "%s is not a FilterConnection handler, skipping", |
| handlers[i]->name); |
| continue; |
| } |
| |
| ctx = (modperl_filter_ctx_t *)apr_pcalloc(c->pool, sizeof(*ctx)); |
| ctx->handler = handlers[i]; |
| |
| f = addfunc(name, (void*)ctx, NULL, c); |
| |
| /* ap_filter_t filter cleanup */ |
| apr_pool_cleanup_register(c->pool, (void *)f, |
| modperl_filter_f_cleanup, |
| apr_pool_cleanup_null); |
| |
| if (handlers[i]->attrs & MP_FILTER_HAS_INIT_HANDLER && |
| handlers[i]->next) { |
| int status = modperl_run_filter_init( |
| f, |
| (idx == MP_INPUT_FILTER_HANDLER |
| ? MP_INPUT_FILTER_MODE : MP_OUTPUT_FILTER_MODE), |
| handlers[i]->next); |
| if (status != OK) { |
| return status; |
| } |
| } |
| |
| MP_TRACE_h(MP_FUNC, "%s handler %s configured (connection)", |
| type, handlers[i]->name); |
| } |
| |
| return OK; |
| } |
| |
| MP_TRACE_h(MP_FUNC, "no %s handlers configured (connection)", type); |
| |
| return DECLINED; |
| } |
| |
| static int modperl_filter_add_request(request_rec *r, |
| int idx, |
| const char *name, |
| modperl_filter_add_t addfunc, |
| const char *type, |
| ap_filter_t *filters) |
| { |
| MP_dDCFG; |
| MpAV *av; |
| |
| if ((av = dcfg->handlers_per_dir[idx])) { |
| modperl_handler_t **handlers = (modperl_handler_t **)av->elts; |
| int i; |
| |
| for (i=0; i<av->nelts; i++) { |
| modperl_filter_ctx_t *ctx; |
| int registered = 0; |
| ap_filter_t *f; |
| |
| /* process non-mod_perl filter handlers */ |
| if ((handlers[i]->attrs & MP_FILTER_HTTPD_HANDLER)) { |
| addfunc(handlers[i]->name, NULL, r, r->connection); |
| MP_TRACE_f(MP_FUNC, |
| "a non-mod_perl %s handler %s configured (%s)", |
| type, handlers[i]->name, r->uri); |
| continue; |
| } |
| |
| /* skip non-request level filters, e.g. connection filters |
| * configured outside the resource container, merged into |
| * resource's dcfg->handlers_per_dir[] entry. |
| */ |
| if ((handlers[i]->attrs & MP_FILTER_CONNECTION_HANDLER)) { |
| MP_TRACE_f(MP_FUNC, |
| "%s is not a FilterRequest handler, skipping", |
| handlers[i]->name); |
| continue; |
| } |
| |
| /* XXX: I fail to see where this feature is used, since |
| * modperl_filter_add_connection doesn't register request |
| * filters. may be it'll be still useful when the same |
| * filter handler is configured to run more than once? |
| * e.g. snooping filter [stas] */ |
| f = filters; |
| while (f) { |
| const char *fname = f->frec->name; |
| |
| /* XXX: I think this won't work as f->frec->name gets |
| * lowercased when added to the chain */ |
| if (*fname == 'M' && strEQ(fname, name)) { |
| modperl_handler_t *ctx_handler = |
| ((modperl_filter_ctx_t *)f->ctx)->handler; |
| |
| if (modperl_handler_equal(ctx_handler, handlers[i])) { |
| /* skip if modperl_filter_add_connection |
| * already registered this handler |
| * XXX: set a flag in the modperl_handler_t instead |
| */ |
| registered = 1; |
| break; |
| } |
| } |
| |
| f = f->next; |
| } |
| |
| if (registered) { |
| MP_TRACE_f(MP_FUNC, |
| "%s %s already registered", |
| handlers[i]->name, type); |
| continue; |
| } |
| |
| ctx = (modperl_filter_ctx_t *)apr_pcalloc(r->pool, sizeof(*ctx)); |
| ctx->handler = handlers[i]; |
| |
| f = addfunc(name, (void*)ctx, r, r->connection); |
| |
| /* ap_filter_t filter cleanup */ |
| apr_pool_cleanup_register(r->pool, (void *)f, |
| modperl_filter_f_cleanup, |
| apr_pool_cleanup_null); |
| |
| if (handlers[i]->attrs & MP_FILTER_HAS_INIT_HANDLER && |
| handlers[i]->next) { |
| int status = modperl_run_filter_init( |
| f, |
| (idx == MP_INPUT_FILTER_HANDLER |
| ? MP_INPUT_FILTER_MODE : MP_OUTPUT_FILTER_MODE), |
| handlers[i]->next); |
| if (status != OK) { |
| return status; |
| } |
| } |
| |
| MP_TRACE_h(MP_FUNC, "%s handler %s configured (%s)", |
| type, handlers[i]->name, r->uri); |
| } |
| |
| return OK; |
| } |
| |
| MP_TRACE_h(MP_FUNC, "no %s handlers configured (%s)", |
| type, r->uri); |
| |
| return DECLINED; |
| } |
| |
| void modperl_output_filter_add_connection(conn_rec *c) |
| { |
| modperl_filter_add_connection(c, |
| MP_OUTPUT_FILTER_HANDLER, |
| MP_FILTER_CONNECTION_OUTPUT_NAME, |
| ap_add_output_filter, |
| "OutputFilter"); |
| } |
| |
| void modperl_output_filter_add_request(request_rec *r) |
| { |
| modperl_filter_add_request(r, |
| MP_OUTPUT_FILTER_HANDLER, |
| MP_FILTER_REQUEST_OUTPUT_NAME, |
| ap_add_output_filter, |
| "OutputFilter", |
| r->connection->output_filters); |
| } |
| |
| void modperl_input_filter_add_connection(conn_rec *c) |
| { |
| modperl_filter_add_connection(c, |
| MP_INPUT_FILTER_HANDLER, |
| MP_FILTER_CONNECTION_INPUT_NAME, |
| ap_add_input_filter, |
| "InputFilter"); |
| } |
| |
| void modperl_input_filter_add_request(request_rec *r) |
| { |
| modperl_filter_add_request(r, |
| MP_INPUT_FILTER_HANDLER, |
| MP_FILTER_REQUEST_INPUT_NAME, |
| ap_add_input_filter, |
| "InputFilter", |
| r->connection->input_filters); |
| } |
| |
| void modperl_filter_runtime_add(pTHX_ request_rec *r, conn_rec *c, |
| const char *name, |
| modperl_filter_mode_e mode, |
| modperl_filter_add_t addfunc, |
| SV *callback, const char *type) |
| { |
| apr_pool_t *pool = r ? r->pool : c->pool; |
| modperl_handler_t *handler = |
| modperl_handler_new_from_sv(aTHX_ pool, callback); |
| |
| if (handler) { |
| ap_filter_t *f; |
| modperl_filter_ctx_t *ctx = |
| (modperl_filter_ctx_t *)apr_pcalloc(pool, sizeof(*ctx)); |
| |
| ctx->handler = handler; |
| f = addfunc(name, (void*)ctx, r, c); |
| |
| /* ap_filter_t filter cleanup */ |
| apr_pool_cleanup_register(pool, (void *)f, |
| modperl_filter_f_cleanup, |
| apr_pool_cleanup_null); |
| |
| /* has to resolve early so we can check for init functions */ |
| if (!modperl_mgv_resolve(aTHX_ handler, pool, handler->name, TRUE)) { |
| Perl_croak(aTHX_ "unable to resolve handler %s\n", |
| modperl_handler_name(handler)); |
| } |
| |
| /* verify that the filter handler is of the right kind */ |
| if (r == NULL) { |
| /* needs to have the FilterConnectionHandler attribute */ |
| if (!(handler->attrs & MP_FILTER_CONNECTION_HANDLER)) { |
| Perl_croak(aTHX_ "Can't add connection filter handler '%s' " |
| "since it doesn't have the " |
| "FilterConnectionHandler attribute set", |
| modperl_handler_name(handler)); |
| } |
| } |
| else { |
| /* needs to have the FilterRequestHandler attribute, but |
| * since by default request filters are not required to |
| * have the FilterRequestHandler attribute, croak only if |
| * some other attribute is set, but not |
| * FilterRequestHandler */ |
| if (handler->attrs && |
| !(handler->attrs & MP_FILTER_REQUEST_HANDLER)) { |
| Perl_croak(aTHX_ "Can't add request filter handler '%s' " |
| "since it doesn't have the " |
| "FilterRequestHandler attribute set", |
| modperl_handler_name(handler)); |
| } |
| } |
| |
| if (handler->attrs & MP_FILTER_HAS_INIT_HANDLER && handler->next) { |
| int status = modperl_run_filter_init(f, mode, handler->next); |
| if (status != OK) { |
| modperl_croak(aTHX_ status, strEQ("InputFilter", type) |
| ? "Apache2::Filter::add_input_filter" |
| : "Apache2::Filter::add_output_filter"); |
| } |
| } |
| |
| MP_TRACE_h(MP_FUNC, "%s handler %s configured (connection)", |
| type, name); |
| |
| return; |
| } |
| |
| Perl_croak(aTHX_ "unable to resolve handler 0x%lx\n", |
| (unsigned long)callback); |
| } |
| |
| void modperl_brigade_dump(apr_bucket_brigade *bb, apr_file_t *file) |
| { |
| apr_bucket *bucket; |
| int i = 0; |
| #ifndef WIN32 |
| if (file == NULL) { |
| file = modperl_global_get_server_rec()->error_log; |
| } |
| |
| apr_file_printf(file, "dump of brigade 0x%lx\n", (unsigned long)bb); |
| |
| for (bucket = APR_BRIGADE_FIRST(bb); |
| bucket != APR_BRIGADE_SENTINEL(bb); |
| bucket = APR_BUCKET_NEXT(bucket)) |
| { |
| apr_file_printf(file, |
| " %d: bucket=%s(0x%lx), length=%ld, data=0x%lx\n", |
| i, bucket->type->name, |
| (unsigned long)bucket, |
| (long)bucket->length, |
| (unsigned long)bucket->data); |
| /* apr_file_printf(file, " : %s\n", (char *)bucket->data); */ |
| |
| i++; |
| } |
| #endif |
| } |
| |
| /* |
| * Local Variables: |
| * c-basic-offset: 4 |
| * indent-tabs-mode: nil |
| * End: |
| */ |