123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583 |
- /***************************************************************************
- * _ _ ____ _
- * Project ___| | | | _ \| |
- * / __| | | | |_) | |
- * | (__| |_| | _ <| |___
- * \___|\___/|_| \_\_____|
- *
- * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * are also available at https://curl.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- * SPDX-License-Identifier: curl
- *
- ***************************************************************************/
- #include <apr_optional.h>
- #include <apr_optional_hooks.h>
- #include <apr_strings.h>
- #include <apr_cstr.h>
- #include <apr_time.h>
- #include <apr_want.h>
- #include <httpd.h>
- #include <http_protocol.h>
- #include <http_request.h>
- #include <http_log.h>
- static void curltest_hooks(apr_pool_t *pool);
- static int curltest_echo_handler(request_rec *r);
- static int curltest_put_handler(request_rec *r);
- static int curltest_tweak_handler(request_rec *r);
- static int curltest_1_1_required(request_rec *r);
- AP_DECLARE_MODULE(curltest) = {
- STANDARD20_MODULE_STUFF,
- NULL, /* func to create per dir config */
- NULL, /* func to merge per dir config */
- NULL, /* func to create per server config */
- NULL, /* func to merge per server config */
- NULL, /* command handlers */
- curltest_hooks,
- #if defined(AP_MODULE_FLAG_NONE)
- AP_MODULE_FLAG_ALWAYS_MERGE
- #endif
- };
- static int curltest_post_config(apr_pool_t *p, apr_pool_t *plog,
- apr_pool_t *ptemp, server_rec *s)
- {
- void *data = NULL;
- const char *key = "mod_curltest_init_counter";
- (void)plog;(void)ptemp;
- apr_pool_userdata_get(&data, key, s->process->pool);
- if(!data) {
- /* dry run */
- apr_pool_userdata_set((const void *)1, key,
- apr_pool_cleanup_null, s->process->pool);
- return APR_SUCCESS;
- }
- /* mess with the overall server here */
- return APR_SUCCESS;
- }
- static void curltest_hooks(apr_pool_t *pool)
- {
- ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks");
- /* Run once after configuration is set, but before mpm children initialize.
- */
- ap_hook_post_config(curltest_post_config, NULL, NULL, APR_HOOK_MIDDLE);
- /* curl test handlers */
- ap_hook_handler(curltest_echo_handler, NULL, NULL, APR_HOOK_MIDDLE);
- ap_hook_handler(curltest_put_handler, NULL, NULL, APR_HOOK_MIDDLE);
- ap_hook_handler(curltest_tweak_handler, NULL, NULL, APR_HOOK_MIDDLE);
- ap_hook_handler(curltest_1_1_required, NULL, NULL, APR_HOOK_MIDDLE);
- }
- #define SECS_PER_HOUR (60*60)
- #define SECS_PER_DAY (24*SECS_PER_HOUR)
- static apr_status_t duration_parse(apr_interval_time_t *ptimeout, const char *value,
- const char *def_unit)
- {
- char *endp;
- apr_int64_t n;
- n = apr_strtoi64(value, &endp, 10);
- if(errno) {
- return errno;
- }
- if(!endp || !*endp) {
- if (!def_unit) def_unit = "s";
- }
- else if(endp == value) {
- return APR_EINVAL;
- }
- else {
- def_unit = endp;
- }
- switch(*def_unit) {
- case 'D':
- case 'd':
- *ptimeout = apr_time_from_sec(n * SECS_PER_DAY);
- break;
- case 's':
- case 'S':
- *ptimeout = (apr_interval_time_t) apr_time_from_sec(n);
- break;
- case 'h':
- case 'H':
- /* Time is in hours */
- *ptimeout = (apr_interval_time_t) apr_time_from_sec(n * SECS_PER_HOUR);
- break;
- case 'm':
- case 'M':
- switch(*(++def_unit)) {
- /* Time is in milliseconds */
- case 's':
- case 'S':
- *ptimeout = (apr_interval_time_t) n * 1000;
- break;
- /* Time is in minutes */
- case 'i':
- case 'I':
- *ptimeout = (apr_interval_time_t) apr_time_from_sec(n * 60);
- break;
- default:
- return APR_EGENERAL;
- }
- break;
- case 'u':
- case 'U':
- switch(*(++def_unit)) {
- /* Time is in microseconds */
- case 's':
- case 'S':
- *ptimeout = (apr_interval_time_t) n;
- break;
- default:
- return APR_EGENERAL;
- }
- break;
- default:
- return APR_EGENERAL;
- }
- return APR_SUCCESS;
- }
- static int status_from_str(const char *s, apr_status_t *pstatus)
- {
- if(!strcmp("timeout", s)) {
- *pstatus = APR_TIMEUP;
- return 1;
- }
- else if(!strcmp("reset", s)) {
- *pstatus = APR_ECONNRESET;
- return 1;
- }
- return 0;
- }
- static int curltest_echo_handler(request_rec *r)
- {
- conn_rec *c = r->connection;
- apr_bucket_brigade *bb;
- apr_bucket *b;
- apr_status_t rv;
- char buffer[8192];
- const char *ct;
- long l;
- if(strcmp(r->handler, "curltest-echo")) {
- return DECLINED;
- }
- if(r->method_number != M_GET && r->method_number != M_POST) {
- return DECLINED;
- }
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "echo_handler: processing");
- r->status = 200;
- r->clength = -1;
- r->chunked = 1;
- apr_table_unset(r->headers_out, "Content-Length");
- /* Discourage content-encodings */
- apr_table_unset(r->headers_out, "Content-Encoding");
- apr_table_setn(r->subprocess_env, "no-brotli", "1");
- apr_table_setn(r->subprocess_env, "no-gzip", "1");
- ct = apr_table_get(r->headers_in, "content-type");
- ap_set_content_type(r, ct? ct : "application/octet-stream");
- bb = apr_brigade_create(r->pool, c->bucket_alloc);
- /* copy any request body into the response */
- if((rv = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK))) goto cleanup;
- if(ap_should_client_block(r)) {
- while(0 < (l = ap_get_client_block(r, &buffer[0], sizeof(buffer)))) {
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
- "echo_handler: copying %ld bytes from request body", l);
- rv = apr_brigade_write(bb, NULL, NULL, buffer, l);
- if (APR_SUCCESS != rv) goto cleanup;
- rv = ap_pass_brigade(r->output_filters, bb);
- if (APR_SUCCESS != rv) goto cleanup;
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
- "echo_handler: passed %ld bytes from request body", l);
- }
- }
- /* we are done */
- b = apr_bucket_eos_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, b);
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "echo_handler: request read");
- if(r->trailers_in && !apr_is_empty_table(r->trailers_in)) {
- ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
- "echo_handler: seeing incoming trailers");
- apr_table_setn(r->trailers_out, "h2test-trailers-in",
- apr_itoa(r->pool, 1));
- }
- rv = ap_pass_brigade(r->output_filters, bb);
- cleanup:
- if(rv == APR_SUCCESS ||
- r->status != HTTP_OK ||
- c->aborted) {
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "echo_handler: done");
- return OK;
- }
- else {
- /* no way to know what type of error occurred */
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "echo_handler failed");
- return AP_FILTER_ERROR;
- }
- return DECLINED;
- }
- static int curltest_tweak_handler(request_rec *r)
- {
- conn_rec *c = r->connection;
- apr_bucket_brigade *bb;
- apr_bucket *b;
- apr_status_t rv;
- char buffer[16*1024];
- int i, chunks = 3, error_bucket = 1;
- size_t chunk_size = sizeof(buffer);
- const char *request_id = "none";
- apr_time_t delay = 0, chunk_delay = 0;
- apr_array_header_t *args = NULL;
- int http_status = 200;
- apr_status_t error = APR_SUCCESS, body_error = APR_SUCCESS;
- if(strcmp(r->handler, "curltest-tweak")) {
- return DECLINED;
- }
- if(r->method_number != M_GET && r->method_number != M_POST) {
- return DECLINED;
- }
- if(r->args) {
- args = apr_cstr_split(r->args, "&", 1, r->pool);
- for(i = 0; i < args->nelts; ++i) {
- char *s, *val, *arg = APR_ARRAY_IDX(args, i, char*);
- s = strchr(arg, '=');
- if(s) {
- *s = '\0';
- val = s + 1;
- if(!strcmp("status", arg)) {
- http_status = (int)apr_atoi64(val);
- if(http_status > 0) {
- continue;
- }
- }
- else if(!strcmp("chunks", arg)) {
- chunks = (int)apr_atoi64(val);
- if(chunks >= 0) {
- continue;
- }
- }
- else if(!strcmp("chunk_size", arg)) {
- chunk_size = (int)apr_atoi64(val);
- if(chunk_size >= 0) {
- if(chunk_size > sizeof(buffer)) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
- "chunk_size %zu too large", chunk_size);
- ap_die(HTTP_BAD_REQUEST, r);
- return OK;
- }
- continue;
- }
- }
- else if(!strcmp("id", arg)) {
- /* just an id for repeated requests with curl's url globbing */
- request_id = val;
- continue;
- }
- else if(!strcmp("error", arg)) {
- if(status_from_str(val, &error)) {
- continue;
- }
- }
- else if(!strcmp("error_bucket", arg)) {
- error_bucket = (int)apr_atoi64(val);
- if(error_bucket >= 0) {
- continue;
- }
- }
- else if(!strcmp("body_error", arg)) {
- if(status_from_str(val, &body_error)) {
- continue;
- }
- }
- else if(!strcmp("delay", arg)) {
- rv = duration_parse(&delay, val, "s");
- if(APR_SUCCESS == rv) {
- continue;
- }
- }
- else if(!strcmp("chunk_delay", arg)) {
- rv = duration_parse(&chunk_delay, val, "s");
- if(APR_SUCCESS == rv) {
- continue;
- }
- }
- }
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "query parameter not "
- "understood: '%s' in %s",
- arg, r->args);
- ap_die(HTTP_BAD_REQUEST, r);
- return OK;
- }
- }
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "error_handler: processing "
- "request, %s", r->args? r->args : "(no args)");
- r->status = http_status;
- r->clength = -1;
- r->chunked = 1;
- apr_table_setn(r->headers_out, "request-id", request_id);
- apr_table_unset(r->headers_out, "Content-Length");
- /* Discourage content-encodings */
- apr_table_unset(r->headers_out, "Content-Encoding");
- apr_table_setn(r->subprocess_env, "no-brotli", "1");
- apr_table_setn(r->subprocess_env, "no-gzip", "1");
- ap_set_content_type(r, "application/octet-stream");
- bb = apr_brigade_create(r->pool, c->bucket_alloc);
- if(delay) {
- apr_sleep(delay);
- }
- if(error != APR_SUCCESS) {
- return ap_map_http_request_error(error, HTTP_BAD_REQUEST);
- }
- /* flush response */
- b = apr_bucket_flush_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, b);
- rv = ap_pass_brigade(r->output_filters, bb);
- if (APR_SUCCESS != rv) goto cleanup;
- memset(buffer, 'X', sizeof(buffer));
- for(i = 0; i < chunks; ++i) {
- if(chunk_delay) {
- apr_sleep(chunk_delay);
- }
- rv = apr_brigade_write(bb, NULL, NULL, buffer, chunk_size);
- if(APR_SUCCESS != rv) goto cleanup;
- rv = ap_pass_brigade(r->output_filters, bb);
- if(APR_SUCCESS != rv) goto cleanup;
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
- "error_handler: passed %lu bytes as response body",
- (unsigned long)chunk_size);
- if(body_error != APR_SUCCESS) {
- rv = body_error;
- goto cleanup;
- }
- }
- /* we are done */
- b = apr_bucket_eos_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, b);
- rv = ap_pass_brigade(r->output_filters, bb);
- apr_brigade_cleanup(bb);
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r,
- "error_handler: response passed");
- cleanup:
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r,
- "error_handler: request cleanup, r->status=%d, aborted=%d",
- r->status, c->aborted);
- if(rv == APR_SUCCESS) {
- return OK;
- }
- if(error_bucket) {
- http_status = ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
- b = ap_bucket_error_create(http_status, NULL, r->pool, c->bucket_alloc);
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r,
- "error_handler: passing error bucket, status=%d",
- http_status);
- APR_BRIGADE_INSERT_TAIL(bb, b);
- ap_pass_brigade(r->output_filters, bb);
- }
- return AP_FILTER_ERROR;
- }
- static int curltest_put_handler(request_rec *r)
- {
- conn_rec *c = r->connection;
- apr_bucket_brigade *bb;
- apr_bucket *b;
- apr_status_t rv;
- char buffer[16*1024];
- const char *ct;
- apr_off_t rbody_len = 0;
- const char *request_id = "none";
- apr_time_t chunk_delay = 0;
- apr_array_header_t *args = NULL;
- long l;
- int i;
- if(strcmp(r->handler, "curltest-put")) {
- return DECLINED;
- }
- if(r->method_number != M_PUT) {
- return DECLINED;
- }
- if(r->args) {
- args = apr_cstr_split(r->args, "&", 1, r->pool);
- for(i = 0; i < args->nelts; ++i) {
- char *s, *val, *arg = APR_ARRAY_IDX(args, i, char*);
- s = strchr(arg, '=');
- if(s) {
- *s = '\0';
- val = s + 1;
- if(!strcmp("id", arg)) {
- /* just an id for repeated requests with curl's url globbing */
- request_id = val;
- continue;
- }
- else if(!strcmp("chunk_delay", arg)) {
- rv = duration_parse(&chunk_delay, val, "s");
- if(APR_SUCCESS == rv) {
- continue;
- }
- }
- }
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "query parameter not "
- "understood: '%s' in %s",
- arg, r->args);
- ap_die(HTTP_BAD_REQUEST, r);
- return OK;
- }
- }
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "put_handler: processing");
- r->status = 200;
- r->clength = -1;
- r->chunked = 1;
- apr_table_unset(r->headers_out, "Content-Length");
- /* Discourage content-encodings */
- apr_table_unset(r->headers_out, "Content-Encoding");
- apr_table_setn(r->subprocess_env, "no-brotli", "1");
- apr_table_setn(r->subprocess_env, "no-gzip", "1");
- ct = apr_table_get(r->headers_in, "content-type");
- ap_set_content_type(r, ct? ct : "text/plain");
- bb = apr_brigade_create(r->pool, c->bucket_alloc);
- /* copy any request body into the response */
- if((rv = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK))) goto cleanup;
- if(ap_should_client_block(r)) {
- while(0 < (l = ap_get_client_block(r, &buffer[0], sizeof(buffer)))) {
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
- "put_handler: read %ld bytes from request body", l);
- if(chunk_delay) {
- apr_sleep(chunk_delay);
- }
- rbody_len += l;
- }
- }
- /* we are done */
- rv = apr_brigade_printf(bb, NULL, NULL, "%"APR_OFF_T_FMT, rbody_len);
- if(APR_SUCCESS != rv) goto cleanup;
- b = apr_bucket_eos_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, b);
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "put_handler: request read");
- rv = ap_pass_brigade(r->output_filters, bb);
- cleanup:
- if(rv == APR_SUCCESS
- || r->status != HTTP_OK
- || c->aborted) {
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "put_handler: done");
- return OK;
- }
- else {
- /* no way to know what type of error occurred */
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "put_handler failed");
- return AP_FILTER_ERROR;
- }
- return DECLINED;
- }
- static int curltest_1_1_required(request_rec *r)
- {
- conn_rec *c = r->connection;
- apr_bucket_brigade *bb;
- apr_bucket *b;
- apr_status_t rv;
- char buffer[16*1024];
- const char *ct;
- const char *request_id = "none";
- apr_time_t chunk_delay = 0;
- apr_array_header_t *args = NULL;
- long l;
- int i;
- if(strcmp(r->handler, "curltest-1_1-required")) {
- return DECLINED;
- }
- if (HTTP_VERSION_MAJOR(r->proto_num) > 1) {
- apr_table_setn(r->notes, "ssl-renegotiate-forbidden", "1");
- ap_die(HTTP_FORBIDDEN, r);
- return OK;
- }
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "1_1_handler: processing");
- r->status = 200;
- r->clength = -1;
- r->chunked = 1;
- apr_table_unset(r->headers_out, "Content-Length");
- /* Discourage content-encodings */
- apr_table_unset(r->headers_out, "Content-Encoding");
- apr_table_setn(r->subprocess_env, "no-brotli", "1");
- apr_table_setn(r->subprocess_env, "no-gzip", "1");
- ct = apr_table_get(r->headers_in, "content-type");
- ap_set_content_type(r, ct? ct : "text/plain");
- bb = apr_brigade_create(r->pool, c->bucket_alloc);
- /* flush response */
- b = apr_bucket_flush_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, b);
- rv = ap_pass_brigade(r->output_filters, bb);
- if (APR_SUCCESS != rv) goto cleanup;
- /* we are done */
- rv = apr_brigade_printf(bb, NULL, NULL, "well done!");
- if(APR_SUCCESS != rv) goto cleanup;
- b = apr_bucket_eos_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, b);
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "1_1_handler: request read");
- rv = ap_pass_brigade(r->output_filters, bb);
- cleanup:
- if(rv == APR_SUCCESS
- || r->status != HTTP_OK
- || c->aborted) {
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "1_1_handler: done");
- return OK;
- }
- else {
- /* no way to know what type of error occurred */
- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "1_1_handler failed");
- return AP_FILTER_ERROR;
- }
- return DECLINED;
- }
|