/** * Copyright 2007 Paul Querna * * 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. * */ /*** DANGER DANGER DANGER DO NOT RUN THIS MODULE ON PRODUCTION SERVERS IT IS INSECURE IT COULD CRASH IT COULD ALLOW EVIL EXPLOTS THE CODE ITSELF IS EVIL IFF you know what you are doing, and you want to try out this prototype, you can copmile it with `apxs`. That is all. ***/ #include "httpd.h" #include "http_config.h" #include "http_log.h" #include "util_filter.h" #include "apr_buckets.h" typedef struct mg_dir_cfg { int enabled; int max_requests; } mg_dir_cfg; extern module AP_MODULE_DECLARE_DATA multiget_module; #define MG_CAPTURE_FILTER_NAME "multiget-internal-capture-filter" #define MG_MAX_URI_SIZE (8190) static void *mg_config_dir_create(apr_pool_t * p, char *x) { mg_dir_cfg *conf = apr_pcalloc(p, sizeof(mg_dir_cfg)); conf->enabled = 0; conf->max_requests = 10; return conf; } static const char *mg_config_enable(cmd_parms * cmd, void *cfg, int on) { mg_dir_cfg *conf = (mg_dir_cfg *) cfg; conf->enabled = on; return NULL; } static void mg_build_string_table(request_rec *r, char *input, apr_table_t *parms) { char *key; char *value; char *strtok_state = NULL; key = apr_strtok(input, "&", &strtok_state); while (key) { value = strchr(key, '='); if (value) { *value = '\0'; /* Split the string in two */ value++; /* Skip passed the = */ } else { value = "1"; } ap_unescape_url(key); ap_unescape_url(value); apr_table_set(parms, key, value); /* ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Found query arg: %s = %s", key, value); */ key = apr_strtok(NULL, "&", &strtok_state); } } static int mg_buffer_body(request_rec *r, apr_pool_t *pbuf, char **buf, apr_size_t *buflen, apr_size_t limit) { apr_status_t arv = APR_SUCCESS; long rv = 0; apr_bucket_brigade *bb = apr_brigade_create(pbuf, r->connection->bucket_alloc); do { char iobuf[AP_IOBUFSIZE]; apr_size_t len = sizeof(iobuf); apr_off_t bblen = 0; rv = ap_get_client_block(r, iobuf, len); if (rv > 0) { apr_bucket* e = apr_bucket_heap_create(iobuf, rv, NULL, r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, e); } apr_brigade_length(bb, 1, &bblen); if (bblen > limit) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "multiget: client sent POST that is too large!" " (limit=%"APR_SIZE_T_FMT")", limit); return HTTP_INTERNAL_SERVER_ERROR; } } while (rv > 0); if (rv < 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "multiget: error reading client POST data."); return HTTP_INTERNAL_SERVER_ERROR; } { /* make the POST data NULL terminated. yes, this is quite eviiile. */ char tbuf[1]; tbuf[0] = '\0'; apr_bucket* e = apr_bucket_heap_create(tbuf, 1, NULL, r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, e); } arv = apr_brigade_pflatten(bb, buf, buflen, pbuf); if (arv) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "multiget: error reading client POST data. (pflatten failed)"); return HTTP_INTERNAL_SERVER_ERROR; } apr_brigade_destroy(bb); return OK; } static int mg_parse_body(request_rec *r, mg_dir_cfg *conf, apr_table_t *uris) { int rv = OK; char *buf = NULL; apr_size_t len; apr_size_t limit = (MG_MAX_URI_SIZE * conf->max_requests); rv = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK); if (rv != OK) { return rv; } rv = ap_should_client_block(r); if (rv == 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "multiget: client didn't set POST data?"); return HTTP_EXPECTATION_FAILED; } rv = mg_buffer_body(r, r->pool, &buf, &len, limit); if (rv != OK) { return rv; } mg_build_string_table(r, buf, uris); return rv; } typedef struct mg_baton { mg_dir_cfg *conf; request_rec *r; apr_bucket_brigade *rout; int first; } mg_baton; typedef struct mg_sub_req { apr_pool_t *p; const char *uri; int http_status; request_rec *rr; apr_bucket_brigade *output; } mg_sub_req; static int mg_run_subrequest(mg_baton *baton, const char *value) { apr_pool_t *subpool = NULL; do { /* run the subrequest on the value parameter. */ mg_sub_req *sreq; int rr_status; apr_pool_create(&subpool, baton->r->pool); sreq = apr_palloc(subpool, sizeof(mg_sub_req)); sreq->p = subpool; sreq->output = apr_brigade_create(subpool, baton->r->connection->bucket_alloc); sreq->rr = ap_sub_req_lookup_uri(value, baton->r, NULL); if (sreq->rr->status != HTTP_OK) { ap_destroy_sub_req(sreq->rr); break; } ap_add_output_filter(MG_CAPTURE_FILTER_NAME, sreq, sreq->rr, baton->r->connection); rr_status = ap_run_sub_req(sreq->rr); if (rr_status != OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, baton->r, "multiget: Subrequest for '%s' failed with '%d'", value, rr_status); /* XXXXX: bleh, this is a bug in the core on inconsistent * use of request_rec->status. */ sreq->rr->status = rr_status; } /* XXXXX: Support alternative content types. */ /* XXXXX: Assuming everything is UTF-8 Encoded.*/ /* XXXXX: For non-json types, you MUST escape them */ if (sreq->rr->content_type == NULL || strstr(sreq->rr->content_type, "application/json") == NULL) { apr_brigade_destroy(sreq->output); sreq->output = NULL; } if (baton->first) { baton->first = 0; } else { apr_brigade_putc(baton->rout, NULL, NULL, ','); } apr_brigade_printf(baton->rout, NULL, NULL, "{\"uri\":\"%s\"," "\"status\":%d," "\"body\":", sreq->rr->uri, sreq->rr->status); if (sreq->output) { APR_BRIGADE_CONCAT(baton->rout, sreq->output); } else { apr_brigade_puts(baton->rout, NULL, NULL, "{}"); } apr_brigade_putc(baton->rout, NULL, NULL, '}'); } while(0); if (subpool) { apr_pool_destroy(subpool); } return 1; } static int mg_param_cb(void *rec, const char *key, const char *value) { mg_baton *baton = (mg_baton *)rec; if (strncmp("uri_", key, 4) != 0) { return 1; } return mg_run_subrequest(baton, value); } static int mg_handler_main(request_rec *r) { mg_sub_req *head = NULL; int rv = 0; int http_ret = OK; mg_dir_cfg *conf = ap_get_module_config(r->per_dir_config, &multiget_module); if (conf->enabled != 1) { return DECLINED; } { /* Parse the request body for subrequests */ apr_bucket *b; apr_table_t *uri_table; mg_baton baton; uri_table = apr_table_make(r->pool, conf->max_requests); http_ret = mg_parse_body(r, conf, uri_table); if (http_ret != OK) { return http_ret; } baton.conf = conf; baton.r = r; baton.first = 1; baton.rout = apr_brigade_create(r->pool, r->connection->bucket_alloc); apr_brigade_printf(baton.rout, NULL, NULL, "{\"requests\":["); apr_table_do(mg_param_cb, &baton, uri_table, NULL); apr_brigade_printf(baton.rout, NULL, NULL, "]}"); /* XXXXX: do any browsers break with this HTTP return code?? */ r->status = HTTP_MULTI_STATUS; ap_set_content_type(r, "application/json; charset=utf-8"); b = apr_bucket_eos_create(r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(baton.rout, b); /* XXXX: rv ignored. */ ap_pass_brigade(r->output_filters, baton.rout); return OK; } return OK; } static int mg_handler(request_rec *r) { if (ap_is_initial_req(r) != 1) { /* prevernt recursion. you can't call multiget on a multiget URI. */ return DECLINED; } /* XXXX: We should also allow a custom HTTP method, like 'MULTIGET', * Because in theory, multiple GET requests should still * All be cachable. */ r->allowed |= (AP_METHOD_BIT << M_POST); if (r->method_number != M_POST) { return DECLINED; } return mg_handler_main(r); } static apr_status_t mg_capture_filter(ap_filter_t *f, apr_bucket_brigade *bb) { apr_bucket *b; apr_status_t rv; mg_sub_req *ctxt = f->ctx; apr_bucket_brigade *data = ctxt->output; for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) { if (APR_BUCKET_IS_EOS(b)) { /* xxxxx multiple EOS buckets possible? */ APR_BUCKET_REMOVE(b); break; } } /* save all of the data coming down. wooooo. */ rv = ap_save_brigade(f, &data, &bb, f->r->pool); ctxt->output = data; return rv; } static void mg_hooks(apr_pool_t * p) { ap_hook_handler(mg_handler, NULL, NULL, APR_HOOK_REALLY_FIRST); ap_register_output_filter(MG_CAPTURE_FILTER_NAME, mg_capture_filter, NULL, AP_FTYPE_RESOURCE); } static const command_rec mg_config_cmds[] = { AP_INIT_FLAG("MultiGet", mg_config_enable, NULL, OR_ALL, "Enable multiget support"), {NULL} }; module AP_MODULE_DECLARE_DATA multiget_module = { STANDARD20_MODULE_STUFF, mg_config_dir_create, NULL, NULL, NULL, mg_config_cmds, mg_hooks };