1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24 #include <apr_optional.h>
25 #include <apr_optional_hooks.h>
26 #include <apr_strings.h>
27 #include <apr_cstr.h>
28 #include <apr_time.h>
29 #include <apr_want.h>
30
31 #include <httpd.h>
32 #include <http_protocol.h>
33 #include <http_request.h>
34 #include <http_log.h>
35
36 static void curltest_hooks(apr_pool_t *pool);
37 static int curltest_echo_handler(request_rec *r);
38 static int curltest_put_handler(request_rec *r);
39 static int curltest_tweak_handler(request_rec *r);
40
41 AP_DECLARE_MODULE(curltest) = {
42 STANDARD20_MODULE_STUFF,
43 NULL, /* func to create per dir config */
44 NULL, /* func to merge per dir config */
45 NULL, /* func to create per server config */
46 NULL, /* func to merge per server config */
47 NULL, /* command handlers */
48 curltest_hooks,
49 #if defined(AP_MODULE_FLAG_NONE)
50 AP_MODULE_FLAG_ALWAYS_MERGE
51 #endif
52 };
53
curltest_post_config(apr_pool_t * p,apr_pool_t * plog,apr_pool_t * ptemp,server_rec * s)54 static int curltest_post_config(apr_pool_t *p, apr_pool_t *plog,
55 apr_pool_t *ptemp, server_rec *s)
56 {
57 void *data = NULL;
58 const char *key = "mod_curltest_init_counter";
59
60 (void)plog;(void)ptemp;
61
62 apr_pool_userdata_get(&data, key, s->process->pool);
63 if(!data) {
64 /* dry run */
65 apr_pool_userdata_set((const void *)1, key,
66 apr_pool_cleanup_null, s->process->pool);
67 return APR_SUCCESS;
68 }
69
70 /* mess with the overall server here */
71
72 return APR_SUCCESS;
73 }
74
curltest_hooks(apr_pool_t * pool)75 static void curltest_hooks(apr_pool_t *pool)
76 {
77 ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks");
78
79 /* Run once after configuration is set, but before mpm children initialize.
80 */
81 ap_hook_post_config(curltest_post_config, NULL, NULL, APR_HOOK_MIDDLE);
82
83 /* curl test handlers */
84 ap_hook_handler(curltest_echo_handler, NULL, NULL, APR_HOOK_MIDDLE);
85 ap_hook_handler(curltest_put_handler, NULL, NULL, APR_HOOK_MIDDLE);
86 ap_hook_handler(curltest_tweak_handler, NULL, NULL, APR_HOOK_MIDDLE);
87 }
88
89 #define SECS_PER_HOUR (60*60)
90 #define SECS_PER_DAY (24*SECS_PER_HOUR)
91
duration_parse(apr_interval_time_t * ptimeout,const char * value,const char * def_unit)92 static apr_status_t duration_parse(apr_interval_time_t *ptimeout, const char *value,
93 const char *def_unit)
94 {
95 char *endp;
96 apr_int64_t n;
97
98 n = apr_strtoi64(value, &endp, 10);
99 if(errno) {
100 return errno;
101 }
102 if(!endp || !*endp) {
103 if (!def_unit) def_unit = "s";
104 }
105 else if(endp == value) {
106 return APR_EINVAL;
107 }
108 else {
109 def_unit = endp;
110 }
111
112 switch(*def_unit) {
113 case 'D':
114 case 'd':
115 *ptimeout = apr_time_from_sec(n * SECS_PER_DAY);
116 break;
117 case 's':
118 case 'S':
119 *ptimeout = (apr_interval_time_t) apr_time_from_sec(n);
120 break;
121 case 'h':
122 case 'H':
123 /* Time is in hours */
124 *ptimeout = (apr_interval_time_t) apr_time_from_sec(n * SECS_PER_HOUR);
125 break;
126 case 'm':
127 case 'M':
128 switch(*(++def_unit)) {
129 /* Time is in milliseconds */
130 case 's':
131 case 'S':
132 *ptimeout = (apr_interval_time_t) n * 1000;
133 break;
134 /* Time is in minutes */
135 case 'i':
136 case 'I':
137 *ptimeout = (apr_interval_time_t) apr_time_from_sec(n * 60);
138 break;
139 default:
140 return APR_EGENERAL;
141 }
142 break;
143 case 'u':
144 case 'U':
145 switch(*(++def_unit)) {
146 /* Time is in microseconds */
147 case 's':
148 case 'S':
149 *ptimeout = (apr_interval_time_t) n;
150 break;
151 default:
152 return APR_EGENERAL;
153 }
154 break;
155 default:
156 return APR_EGENERAL;
157 }
158 return APR_SUCCESS;
159 }
160
status_from_str(const char * s,apr_status_t * pstatus)161 static int status_from_str(const char *s, apr_status_t *pstatus)
162 {
163 if(!strcmp("timeout", s)) {
164 *pstatus = APR_TIMEUP;
165 return 1;
166 }
167 else if(!strcmp("reset", s)) {
168 *pstatus = APR_ECONNRESET;
169 return 1;
170 }
171 return 0;
172 }
173
curltest_echo_handler(request_rec * r)174 static int curltest_echo_handler(request_rec *r)
175 {
176 conn_rec *c = r->connection;
177 apr_bucket_brigade *bb;
178 apr_bucket *b;
179 apr_status_t rv;
180 char buffer[8192];
181 const char *ct;
182 long l;
183
184 if(strcmp(r->handler, "curltest-echo")) {
185 return DECLINED;
186 }
187 if(r->method_number != M_GET && r->method_number != M_POST) {
188 return DECLINED;
189 }
190
191 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "echo_handler: processing");
192 r->status = 200;
193 r->clength = -1;
194 r->chunked = 1;
195 apr_table_unset(r->headers_out, "Content-Length");
196 /* Discourage content-encodings */
197 apr_table_unset(r->headers_out, "Content-Encoding");
198 apr_table_setn(r->subprocess_env, "no-brotli", "1");
199 apr_table_setn(r->subprocess_env, "no-gzip", "1");
200
201 ct = apr_table_get(r->headers_in, "content-type");
202 ap_set_content_type(r, ct? ct : "application/octet-stream");
203
204 bb = apr_brigade_create(r->pool, c->bucket_alloc);
205 /* copy any request body into the response */
206 if((rv = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK))) goto cleanup;
207 if(ap_should_client_block(r)) {
208 while(0 < (l = ap_get_client_block(r, &buffer[0], sizeof(buffer)))) {
209 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
210 "echo_handler: copying %ld bytes from request body", l);
211 rv = apr_brigade_write(bb, NULL, NULL, buffer, l);
212 if (APR_SUCCESS != rv) goto cleanup;
213 rv = ap_pass_brigade(r->output_filters, bb);
214 if (APR_SUCCESS != rv) goto cleanup;
215 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
216 "echo_handler: passed %ld bytes from request body", l);
217 }
218 }
219 /* we are done */
220 b = apr_bucket_eos_create(c->bucket_alloc);
221 APR_BRIGADE_INSERT_TAIL(bb, b);
222 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "echo_handler: request read");
223
224 if(r->trailers_in && !apr_is_empty_table(r->trailers_in)) {
225 ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
226 "echo_handler: seeing incoming trailers");
227 apr_table_setn(r->trailers_out, "h2test-trailers-in",
228 apr_itoa(r->pool, 1));
229 }
230
231 rv = ap_pass_brigade(r->output_filters, bb);
232
233 cleanup:
234 if(rv == APR_SUCCESS ||
235 r->status != HTTP_OK ||
236 c->aborted) {
237 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "echo_handler: done");
238 return OK;
239 }
240 else {
241 /* no way to know what type of error occurred */
242 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "echo_handler failed");
243 return AP_FILTER_ERROR;
244 }
245 return DECLINED;
246 }
247
curltest_tweak_handler(request_rec * r)248 static int curltest_tweak_handler(request_rec *r)
249 {
250 conn_rec *c = r->connection;
251 apr_bucket_brigade *bb;
252 apr_bucket *b;
253 apr_status_t rv;
254 char buffer[16*1024];
255 int i, chunks = 3, error_bucket = 1;
256 size_t chunk_size = sizeof(buffer);
257 const char *request_id = "none";
258 apr_time_t delay = 0, chunk_delay = 0;
259 apr_array_header_t *args = NULL;
260 int http_status = 200;
261 apr_status_t error = APR_SUCCESS, body_error = APR_SUCCESS;
262
263 if(strcmp(r->handler, "curltest-tweak")) {
264 return DECLINED;
265 }
266 if(r->method_number != M_GET && r->method_number != M_POST) {
267 return DECLINED;
268 }
269
270 if(r->args) {
271 args = apr_cstr_split(r->args, "&", 1, r->pool);
272 for(i = 0; i < args->nelts; ++i) {
273 char *s, *val, *arg = APR_ARRAY_IDX(args, i, char*);
274 s = strchr(arg, '=');
275 if(s) {
276 *s = '\0';
277 val = s + 1;
278 if(!strcmp("status", arg)) {
279 http_status = (int)apr_atoi64(val);
280 if(http_status > 0) {
281 continue;
282 }
283 }
284 else if(!strcmp("chunks", arg)) {
285 chunks = (int)apr_atoi64(val);
286 if(chunks >= 0) {
287 continue;
288 }
289 }
290 else if(!strcmp("chunk_size", arg)) {
291 chunk_size = (int)apr_atoi64(val);
292 if(chunk_size >= 0) {
293 if(chunk_size > sizeof(buffer)) {
294 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
295 "chunk_size %zu too large", chunk_size);
296 ap_die(HTTP_BAD_REQUEST, r);
297 return OK;
298 }
299 continue;
300 }
301 }
302 else if(!strcmp("id", arg)) {
303 /* just an id for repeated requests with curl's url globbing */
304 request_id = val;
305 continue;
306 }
307 else if(!strcmp("error", arg)) {
308 if(status_from_str(val, &error)) {
309 continue;
310 }
311 }
312 else if(!strcmp("error_bucket", arg)) {
313 error_bucket = (int)apr_atoi64(val);
314 if(error_bucket >= 0) {
315 continue;
316 }
317 }
318 else if(!strcmp("body_error", arg)) {
319 if(status_from_str(val, &body_error)) {
320 continue;
321 }
322 }
323 else if(!strcmp("delay", arg)) {
324 rv = duration_parse(&delay, val, "s");
325 if(APR_SUCCESS == rv) {
326 continue;
327 }
328 }
329 else if(!strcmp("chunk_delay", arg)) {
330 rv = duration_parse(&chunk_delay, val, "s");
331 if(APR_SUCCESS == rv) {
332 continue;
333 }
334 }
335 }
336 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "query parameter not "
337 "understood: '%s' in %s",
338 arg, r->args);
339 ap_die(HTTP_BAD_REQUEST, r);
340 return OK;
341 }
342 }
343
344 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "error_handler: processing "
345 "request, %s", r->args? r->args : "(no args)");
346 r->status = http_status;
347 r->clength = -1;
348 r->chunked = 1;
349 apr_table_setn(r->headers_out, "request-id", request_id);
350 apr_table_unset(r->headers_out, "Content-Length");
351 /* Discourage content-encodings */
352 apr_table_unset(r->headers_out, "Content-Encoding");
353 apr_table_setn(r->subprocess_env, "no-brotli", "1");
354 apr_table_setn(r->subprocess_env, "no-gzip", "1");
355
356 ap_set_content_type(r, "application/octet-stream");
357 bb = apr_brigade_create(r->pool, c->bucket_alloc);
358
359 if(delay) {
360 apr_sleep(delay);
361 }
362 if(error != APR_SUCCESS) {
363 return ap_map_http_request_error(error, HTTP_BAD_REQUEST);
364 }
365 /* flush response */
366 b = apr_bucket_flush_create(c->bucket_alloc);
367 APR_BRIGADE_INSERT_TAIL(bb, b);
368 rv = ap_pass_brigade(r->output_filters, bb);
369 if (APR_SUCCESS != rv) goto cleanup;
370
371 memset(buffer, 'X', sizeof(buffer));
372 for(i = 0; i < chunks; ++i) {
373 if(chunk_delay) {
374 apr_sleep(chunk_delay);
375 }
376 rv = apr_brigade_write(bb, NULL, NULL, buffer, chunk_size);
377 if(APR_SUCCESS != rv) goto cleanup;
378 rv = ap_pass_brigade(r->output_filters, bb);
379 if(APR_SUCCESS != rv) goto cleanup;
380 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
381 "error_handler: passed %lu bytes as response body",
382 (unsigned long)chunk_size);
383 if(body_error != APR_SUCCESS) {
384 rv = body_error;
385 goto cleanup;
386 }
387 }
388 /* we are done */
389 b = apr_bucket_eos_create(c->bucket_alloc);
390 APR_BRIGADE_INSERT_TAIL(bb, b);
391 rv = ap_pass_brigade(r->output_filters, bb);
392 apr_brigade_cleanup(bb);
393 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r,
394 "error_handler: response passed");
395
396 cleanup:
397 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r,
398 "error_handler: request cleanup, r->status=%d, aborted=%d",
399 r->status, c->aborted);
400 if(rv == APR_SUCCESS) {
401 return OK;
402 }
403 if(error_bucket && 0) {
404 http_status = ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
405 b = ap_bucket_error_create(http_status, NULL, r->pool, c->bucket_alloc);
406 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r,
407 "error_handler: passing error bucket, status=%d",
408 http_status);
409 APR_BRIGADE_INSERT_TAIL(bb, b);
410 ap_pass_brigade(r->output_filters, bb);
411 }
412 return AP_FILTER_ERROR;
413 }
414
curltest_put_handler(request_rec * r)415 static int curltest_put_handler(request_rec *r)
416 {
417 conn_rec *c = r->connection;
418 apr_bucket_brigade *bb;
419 apr_bucket *b;
420 apr_status_t rv;
421 char buffer[16*1024];
422 const char *ct;
423 apr_off_t rbody_len = 0;
424 const char *request_id = "none";
425 apr_time_t chunk_delay = 0;
426 apr_array_header_t *args = NULL;
427 long l;
428 int i;
429
430 if(strcmp(r->handler, "curltest-put")) {
431 return DECLINED;
432 }
433 if(r->method_number != M_PUT) {
434 return DECLINED;
435 }
436
437 if(r->args) {
438 args = apr_cstr_split(r->args, "&", 1, r->pool);
439 for(i = 0; i < args->nelts; ++i) {
440 char *s, *val, *arg = APR_ARRAY_IDX(args, i, char*);
441 s = strchr(arg, '=');
442 if(s) {
443 *s = '\0';
444 val = s + 1;
445 if(!strcmp("id", arg)) {
446 /* just an id for repeated requests with curl's url globbing */
447 request_id = val;
448 continue;
449 }
450 else if(!strcmp("chunk_delay", arg)) {
451 rv = duration_parse(&chunk_delay, val, "s");
452 if(APR_SUCCESS == rv) {
453 continue;
454 }
455 }
456 }
457 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "query parameter not "
458 "understood: '%s' in %s",
459 arg, r->args);
460 ap_die(HTTP_BAD_REQUEST, r);
461 return OK;
462 }
463 }
464
465 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "put_handler: processing");
466 r->status = 200;
467 r->clength = -1;
468 r->chunked = 1;
469 apr_table_unset(r->headers_out, "Content-Length");
470 /* Discourage content-encodings */
471 apr_table_unset(r->headers_out, "Content-Encoding");
472 apr_table_setn(r->subprocess_env, "no-brotli", "1");
473 apr_table_setn(r->subprocess_env, "no-gzip", "1");
474
475 ct = apr_table_get(r->headers_in, "content-type");
476 ap_set_content_type(r, ct? ct : "text/plain");
477
478 bb = apr_brigade_create(r->pool, c->bucket_alloc);
479 /* copy any request body into the response */
480 if((rv = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK))) goto cleanup;
481 if(ap_should_client_block(r)) {
482 while(0 < (l = ap_get_client_block(r, &buffer[0], sizeof(buffer)))) {
483 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
484 "put_handler: read %ld bytes from request body", l);
485 if(chunk_delay) {
486 apr_sleep(chunk_delay);
487 }
488 rbody_len += l;
489 }
490 }
491 /* we are done */
492 rv = apr_brigade_printf(bb, NULL, NULL, "%"APR_OFF_T_FMT, rbody_len);
493 if(APR_SUCCESS != rv) goto cleanup;
494 b = apr_bucket_eos_create(c->bucket_alloc);
495 APR_BRIGADE_INSERT_TAIL(bb, b);
496 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "put_handler: request read");
497
498 rv = ap_pass_brigade(r->output_filters, bb);
499
500 cleanup:
501 if(rv == APR_SUCCESS
502 || r->status != HTTP_OK
503 || c->aborted) {
504 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "put_handler: done");
505 return OK;
506 }
507 else {
508 /* no way to know what type of error occurred */
509 ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "put_handler failed");
510 return AP_FILTER_ERROR;
511 }
512 return DECLINED;
513 }
514
515