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 <assert.h>
25 
26 #include <apr_optional.h>
27 #include <apr_optional_hooks.h>
28 #include <apr_strings.h>
29 #include <apr_cstr.h>
30 #include <apr_time.h>
31 #include <apr_want.h>
32 
33 #include <httpd.h>
34 #include <http_protocol.h>
35 #include <http_request.h>
36 #include <http_log.h>
37 
38 static void curltest_hooks(apr_pool_t *pool);
39 static int curltest_echo_handler(request_rec *r);
40 static int curltest_put_handler(request_rec *r);
41 static int curltest_tweak_handler(request_rec *r);
42 static int curltest_1_1_required(request_rec *r);
43 static int curltest_sslinfo_handler(request_rec *r);
44 
45 AP_DECLARE_MODULE(curltest) =
46 {
47   STANDARD20_MODULE_STUFF,
48   NULL, /* func to create per dir config */
49   NULL,  /* func to merge per dir config */
50   NULL, /* func to create per server config */
51   NULL,  /* func to merge per server config */
52   NULL,              /* command handlers */
53   curltest_hooks,
54 #if defined(AP_MODULE_FLAG_NONE)
55   AP_MODULE_FLAG_ALWAYS_MERGE
56 #endif
57 };
58 
curltest_post_config(apr_pool_t * p,apr_pool_t * plog,apr_pool_t * ptemp,server_rec * s)59 static int curltest_post_config(apr_pool_t *p, apr_pool_t *plog,
60                                 apr_pool_t *ptemp, server_rec *s)
61 {
62   void *data = NULL;
63   const char *key = "mod_curltest_init_counter";
64 
65   (void)plog;(void)ptemp;
66 
67   apr_pool_userdata_get(&data, key, s->process->pool);
68   if(!data) {
69     /* dry run */
70     apr_pool_userdata_set((const void *)1, key,
71                           apr_pool_cleanup_null, s->process->pool);
72     return APR_SUCCESS;
73   }
74 
75   /* mess with the overall server here */
76 
77   return APR_SUCCESS;
78 }
79 
curltest_hooks(apr_pool_t * pool)80 static void curltest_hooks(apr_pool_t *pool)
81 {
82   ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks");
83 
84   /* Run once after configuration is set, but before mpm children initialize.
85    */
86   ap_hook_post_config(curltest_post_config, NULL, NULL, APR_HOOK_MIDDLE);
87 
88   /* curl test handlers */
89   ap_hook_handler(curltest_echo_handler, NULL, NULL, APR_HOOK_MIDDLE);
90   ap_hook_handler(curltest_put_handler, NULL, NULL, APR_HOOK_MIDDLE);
91   ap_hook_handler(curltest_tweak_handler, NULL, NULL, APR_HOOK_MIDDLE);
92   ap_hook_handler(curltest_1_1_required, NULL, NULL, APR_HOOK_MIDDLE);
93   ap_hook_handler(curltest_sslinfo_handler, NULL, NULL, APR_HOOK_MIDDLE);
94 }
95 
96 #define SECS_PER_HOUR      (60*60)
97 #define SECS_PER_DAY       (24*SECS_PER_HOUR)
98 
duration_parse(apr_interval_time_t * ptimeout,const char * value,const char * def_unit)99 static apr_status_t duration_parse(apr_interval_time_t *ptimeout,
100                                    const char *value, const char *def_unit)
101 {
102   char *endp;
103   apr_int64_t n;
104 
105   n = apr_strtoi64(value, &endp, 10);
106   if(errno) {
107     return errno;
108   }
109   if(!endp || !*endp) {
110     if(!def_unit)
111       def_unit = "s";
112   }
113   else if(endp == value) {
114     return APR_EINVAL;
115   }
116   else {
117     def_unit = endp;
118   }
119 
120   switch(*def_unit) {
121   case 'D':
122   case 'd':
123     *ptimeout = apr_time_from_sec(n * SECS_PER_DAY);
124     break;
125   case 's':
126   case 'S':
127     *ptimeout = (apr_interval_time_t) apr_time_from_sec(n);
128     break;
129   case 'h':
130   case 'H':
131     /* Time is in hours */
132     *ptimeout = (apr_interval_time_t) apr_time_from_sec(n * SECS_PER_HOUR);
133     break;
134   case 'm':
135   case 'M':
136     switch(*(++def_unit)) {
137     /* Time is in milliseconds */
138     case 's':
139     case 'S':
140       *ptimeout = (apr_interval_time_t) n * 1000;
141       break;
142     /* Time is in minutes */
143     case 'i':
144     case 'I':
145       *ptimeout = (apr_interval_time_t) apr_time_from_sec(n * 60);
146       break;
147     default:
148       return APR_EGENERAL;
149     }
150     break;
151   case 'u':
152   case 'U':
153     switch(*(++def_unit)) {
154     /* Time is in microseconds */
155     case 's':
156     case 'S':
157       *ptimeout = (apr_interval_time_t) n;
158       break;
159     default:
160       return APR_EGENERAL;
161     }
162     break;
163   default:
164     return APR_EGENERAL;
165   }
166   return APR_SUCCESS;
167 }
168 
status_from_str(const char * s,apr_status_t * pstatus)169 static int status_from_str(const char *s, apr_status_t *pstatus)
170 {
171   if(!strcmp("timeout", s)) {
172     *pstatus = APR_TIMEUP;
173     return 1;
174   }
175   else if(!strcmp("reset", s)) {
176     *pstatus = APR_ECONNRESET;
177     return 1;
178   }
179   return 0;
180 }
181 
curltest_echo_handler(request_rec * r)182 static int curltest_echo_handler(request_rec *r)
183 {
184   conn_rec *c = r->connection;
185   apr_bucket_brigade *bb;
186   apr_bucket *b;
187   apr_status_t rv;
188   char buffer[8192];
189   const char *ct;
190   apr_off_t die_after_len = -1, total_read_len = 0;
191   int just_die = 0, die_after_100 = 0;
192   long l;
193 
194   if(strcmp(r->handler, "curltest-echo")) {
195     return DECLINED;
196   }
197   if(r->method_number != M_GET && r->method_number != M_POST) {
198     return DECLINED;
199   }
200 
201   ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "echo_handler: processing");
202   if(r->args) {
203     apr_array_header_t *args = NULL;
204     int i;
205     args = apr_cstr_split(r->args, "&", 1, r->pool);
206     for(i = 0; i < args->nelts; ++i) {
207       char *s, *val, *arg = APR_ARRAY_IDX(args, i, char *);
208       s = strchr(arg, '=');
209       if(s) {
210         *s = '\0';
211         val = s + 1;
212         if(!strcmp("die_after", arg)) {
213           die_after_len = (apr_off_t)apr_atoi64(val);
214           continue;
215         }
216         else if(!strcmp("just_die", arg)) {
217           just_die = 1;
218           continue;
219         }
220         else if(!strcmp("die_after_100", arg)) {
221           die_after_100 = 1;
222           continue;
223         }
224       }
225     }
226   }
227 
228   if(just_die) {
229     ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
230                   "echo_handler: dying right away");
231     /* Generate no HTTP response at all. */
232     ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
233     r->connection->keepalive = AP_CONN_CLOSE;
234     return AP_FILTER_ERROR;
235   }
236 
237   r->status = 200;
238   if(die_after_len >= 0) {
239     r->clength = die_after_len + 1;
240     r->chunked = 0;
241     apr_table_set(r->headers_out, "Content-Length",
242                   apr_ltoa(r->pool, (long)r->clength));
243   }
244   else {
245     r->clength = -1;
246     r->chunked = 1;
247     apr_table_unset(r->headers_out, "Content-Length");
248   }
249   /* Discourage content-encodings */
250   apr_table_unset(r->headers_out, "Content-Encoding");
251   apr_table_setn(r->subprocess_env, "no-brotli", "1");
252   apr_table_setn(r->subprocess_env, "no-gzip", "1");
253 
254   ct = apr_table_get(r->headers_in, "content-type");
255   ap_set_content_type(r, ct ? ct : "application/octet-stream");
256 
257   bb = apr_brigade_create(r->pool, c->bucket_alloc);
258   /* copy any request body into the response */
259   rv = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK);
260   if(rv)
261     goto cleanup;
262   if(die_after_100) {
263     ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
264                   "echo_handler: dying after 100-continue");
265     /* Generate no HTTP response at all. */
266     ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
267     r->connection->keepalive = AP_CONN_CLOSE;
268     return AP_FILTER_ERROR;
269   }
270   if(ap_should_client_block(r)) {
271     while(0 < (l = ap_get_client_block(r, &buffer[0], sizeof(buffer)))) {
272       total_read_len += l;
273       if(die_after_len >= 0 && total_read_len >= die_after_len) {
274         ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
275                       "echo_handler: dying after %ld bytes as requested",
276                       (long)total_read_len);
277         ap_pass_brigade(r->output_filters, bb);
278         ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
279         r->connection->keepalive = AP_CONN_CLOSE;
280         return DONE;
281       }
282       ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
283                     "echo_handler: copying %ld bytes from request body", l);
284       rv = apr_brigade_write(bb, NULL, NULL, buffer, l);
285       if(APR_SUCCESS != rv)
286         goto cleanup;
287       rv = ap_pass_brigade(r->output_filters, bb);
288       if(APR_SUCCESS != rv)
289         goto cleanup;
290       ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
291                     "echo_handler: passed %ld bytes from request body", l);
292     }
293   }
294   /* we are done */
295   b = apr_bucket_eos_create(c->bucket_alloc);
296   APR_BRIGADE_INSERT_TAIL(bb, b);
297   ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "echo_handler: request read");
298 
299   if(r->trailers_in && !apr_is_empty_table(r->trailers_in)) {
300     ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
301                   "echo_handler: seeing incoming trailers");
302     apr_table_setn(r->trailers_out, "h2test-trailers-in",
303                    apr_itoa(r->pool, 1));
304   }
305 
306   rv = ap_pass_brigade(r->output_filters, bb);
307 
308 cleanup:
309   if(rv == APR_SUCCESS ||
310      r->status != HTTP_OK ||
311      c->aborted) {
312     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "echo_handler: done");
313     return OK;
314   }
315   else {
316     /* no way to know what type of error occurred */
317     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "echo_handler failed");
318     return AP_FILTER_ERROR;
319   }
320   return DECLINED;
321 }
322 
curltest_tweak_handler(request_rec * r)323 static int curltest_tweak_handler(request_rec *r)
324 {
325   conn_rec *c = r->connection;
326   apr_bucket_brigade *bb;
327   apr_bucket *b;
328   apr_status_t rv;
329   char buffer[16*1024];
330   int i, chunks = 3, error_bucket = 1;
331   size_t chunk_size = sizeof(buffer);
332   const char *request_id = "none";
333   apr_time_t delay = 0, chunk_delay = 0, close_delay = 0;
334   apr_array_header_t *args = NULL;
335   int http_status = 200;
336   apr_status_t error = APR_SUCCESS, body_error = APR_SUCCESS;
337   int close_conn = 0, with_cl = 0;
338 
339   if(strcmp(r->handler, "curltest-tweak")) {
340     return DECLINED;
341   }
342   if(r->method_number == M_DELETE) {
343     http_status = 204;
344     chunks = 0;
345   }
346   else if(r->method_number != M_GET && r->method_number != M_POST) {
347     return DECLINED;
348   }
349 
350   if(r->args) {
351     args = apr_cstr_split(r->args, "&", 1, r->pool);
352     for(i = 0; i < args->nelts; ++i) {
353       char *s, *val, *arg = APR_ARRAY_IDX(args, i, char *);
354       s = strchr(arg, '=');
355       if(s) {
356         *s = '\0';
357         val = s + 1;
358         if(!strcmp("status", arg)) {
359           http_status = (int)apr_atoi64(val);
360           if(http_status > 0) {
361             continue;
362           }
363         }
364         else if(!strcmp("chunks", arg)) {
365           chunks = (int)apr_atoi64(val);
366           if(chunks >= 0) {
367             continue;
368           }
369         }
370         else if(!strcmp("chunk_size", arg)) {
371           chunk_size = (int)apr_atoi64(val);
372           if(chunk_size >= 0) {
373             if(chunk_size > sizeof(buffer)) {
374               ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
375                             "chunk_size %zu too large", chunk_size);
376               ap_die(HTTP_BAD_REQUEST, r);
377               return OK;
378             }
379             continue;
380           }
381         }
382         else if(!strcmp("id", arg)) {
383           /* just an id for repeated requests with curl's url globbing */
384           request_id = val;
385           continue;
386         }
387         else if(!strcmp("error", arg)) {
388           if(status_from_str(val, &error)) {
389             continue;
390           }
391         }
392         else if(!strcmp("error_bucket", arg)) {
393           error_bucket = (int)apr_atoi64(val);
394           if(error_bucket >= 0) {
395             continue;
396           }
397         }
398         else if(!strcmp("body_error", arg)) {
399           if(status_from_str(val, &body_error)) {
400             continue;
401           }
402         }
403         else if(!strcmp("delay", arg)) {
404           rv = duration_parse(&delay, val, "s");
405           if(APR_SUCCESS == rv) {
406             continue;
407           }
408         }
409         else if(!strcmp("chunk_delay", arg)) {
410           rv = duration_parse(&chunk_delay, val, "s");
411           if(APR_SUCCESS == rv) {
412             continue;
413           }
414         }
415         else if(!strcmp("close_delay", arg)) {
416           rv = duration_parse(&close_delay, val, "s");
417           if(APR_SUCCESS == rv) {
418             continue;
419           }
420         }
421       }
422       else if(!strcmp("close", arg)) {
423         /* we are asked to close the connection */
424         close_conn = 1;
425         continue;
426       }
427       else if(!strcmp("with_cl", arg)) {
428         with_cl = 1;
429         continue;
430       }
431       ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "query parameter not "
432                     "understood: '%s' in %s",
433                     arg, r->args);
434       ap_die(HTTP_BAD_REQUEST, r);
435       return OK;
436     }
437   }
438 
439   ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "error_handler: processing "
440                 "request, %s", r->args? r->args : "(no args)");
441   r->status = http_status;
442   r->clength = with_cl ? (chunks * chunk_size) : -1;
443   r->chunked = (r->proto_num >= HTTP_VERSION(1, 1)) && !with_cl;
444   apr_table_setn(r->headers_out, "request-id", request_id);
445   if(r->clength >= 0) {
446     apr_table_set(r->headers_out, "Content-Length",
447                   apr_ltoa(r->pool, (long)r->clength));
448   }
449   else
450     apr_table_unset(r->headers_out, "Content-Length");
451   /* Discourage content-encodings */
452   apr_table_unset(r->headers_out, "Content-Encoding");
453   apr_table_setn(r->subprocess_env, "no-brotli", "1");
454   apr_table_setn(r->subprocess_env, "no-gzip", "1");
455 
456   ap_set_content_type(r, "application/octet-stream");
457   bb = apr_brigade_create(r->pool, c->bucket_alloc);
458 
459   if(delay) {
460     apr_sleep(delay);
461   }
462   if(error != APR_SUCCESS) {
463     return ap_map_http_request_error(error, HTTP_BAD_REQUEST);
464   }
465   /* flush response */
466   b = apr_bucket_flush_create(c->bucket_alloc);
467   APR_BRIGADE_INSERT_TAIL(bb, b);
468   rv = ap_pass_brigade(r->output_filters, bb);
469   if(APR_SUCCESS != rv)
470     goto cleanup;
471 
472   memset(buffer, 'X', sizeof(buffer));
473   for(i = 0; i < chunks; ++i) {
474     if(chunk_delay) {
475       apr_sleep(chunk_delay);
476     }
477     rv = apr_brigade_write(bb, NULL, NULL, buffer, chunk_size);
478     if(APR_SUCCESS != rv)
479       goto cleanup;
480     rv = ap_pass_brigade(r->output_filters, bb);
481     if(APR_SUCCESS != rv)
482       goto cleanup;
483     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
484                   "error_handler: passed %lu bytes as response body",
485                   (unsigned long)chunk_size);
486     if(body_error != APR_SUCCESS) {
487       rv = body_error;
488       goto cleanup;
489     }
490   }
491   /* we are done */
492   b = apr_bucket_eos_create(c->bucket_alloc);
493   APR_BRIGADE_INSERT_TAIL(bb, b);
494   rv = ap_pass_brigade(r->output_filters, bb);
495   apr_brigade_cleanup(bb);
496   ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r,
497                 "error_handler: response passed");
498 
499 cleanup:
500   if(close_conn) {
501     if(close_delay) {
502       b = apr_bucket_flush_create(c->bucket_alloc);
503       APR_BRIGADE_INSERT_TAIL(bb, b);
504       rv = ap_pass_brigade(r->output_filters, bb);
505       apr_brigade_cleanup(bb);
506       apr_sleep(close_delay);
507     }
508     r->connection->keepalive = AP_CONN_CLOSE;
509   }
510   ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r,
511                 "error_handler: request cleanup, r->status=%d, aborted=%d, "
512                 "close=%d", r->status, c->aborted, close_conn);
513   if(rv == APR_SUCCESS) {
514     return OK;
515   }
516   if(error_bucket) {
517     http_status = ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
518     b = ap_bucket_error_create(http_status, NULL, r->pool, c->bucket_alloc);
519     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r,
520                   "error_handler: passing error bucket, status=%d",
521                   http_status);
522     APR_BRIGADE_INSERT_TAIL(bb, b);
523     ap_pass_brigade(r->output_filters, bb);
524   }
525   return AP_FILTER_ERROR;
526 }
527 
curltest_put_handler(request_rec * r)528 static int curltest_put_handler(request_rec *r)
529 {
530   conn_rec *c = r->connection;
531   apr_bucket_brigade *bb;
532   apr_bucket *b;
533   apr_status_t rv;
534   char buffer[128*1024];
535   const char *ct;
536   apr_off_t rbody_len = 0;
537   apr_off_t rbody_max_len = -1;
538   const char *s_rbody_len;
539   const char *request_id = "none";
540   apr_time_t read_delay = 0, chunk_delay = 0;
541   apr_array_header_t *args = NULL;
542   long l;
543   int i;
544 
545   if(strcmp(r->handler, "curltest-put")) {
546     return DECLINED;
547   }
548   if(r->method_number != M_PUT) {
549     return DECLINED;
550   }
551 
552   if(r->args) {
553     args = apr_cstr_split(r->args, "&", 1, r->pool);
554     for(i = 0; i < args->nelts; ++i) {
555       char *s, *val, *arg = APR_ARRAY_IDX(args, i, char *);
556       s = strchr(arg, '=');
557       if(s) {
558         *s = '\0';
559         val = s + 1;
560         if(!strcmp("id", arg)) {
561           /* just an id for repeated requests with curl's url globbing */
562           request_id = val;
563           continue;
564         }
565         else if(!strcmp("read_delay", arg)) {
566           rv = duration_parse(&read_delay, val, "s");
567           if(APR_SUCCESS == rv) {
568             continue;
569           }
570         }
571         else if(!strcmp("chunk_delay", arg)) {
572           rv = duration_parse(&chunk_delay, val, "s");
573           if(APR_SUCCESS == rv) {
574             continue;
575           }
576         }
577         else if(!strcmp("max_upload", arg)) {
578           rbody_max_len = (int)apr_atoi64(val);
579           continue;
580         }
581       }
582       ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "query parameter not "
583                     "understood: '%s' in %s",
584                     arg, r->args);
585       ap_die(HTTP_BAD_REQUEST, r);
586       return OK;
587     }
588   }
589 
590   ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "put_handler: processing");
591   r->status = 200;
592   r->clength = -1;
593   r->chunked = 1;
594   apr_table_unset(r->headers_out, "Content-Length");
595   /* Discourage content-encodings */
596   apr_table_unset(r->headers_out, "Content-Encoding");
597   apr_table_setn(r->subprocess_env, "no-brotli", "1");
598   apr_table_setn(r->subprocess_env, "no-gzip", "1");
599 
600   ct = apr_table_get(r->headers_in, "content-type");
601   ap_set_content_type(r, ct ? ct : "text/plain");
602 
603   if(read_delay) {
604     apr_sleep(read_delay);
605   }
606   bb = apr_brigade_create(r->pool, c->bucket_alloc);
607   /* copy any request body into the response */
608   rv = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK);
609   if(rv)
610     goto cleanup;
611   if(ap_should_client_block(r)) {
612     while(0 < (l = ap_get_client_block(r, &buffer[0], sizeof(buffer)))) {
613       ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
614                     "put_handler: read %ld bytes from request body", l);
615       if(chunk_delay) {
616         apr_sleep(chunk_delay);
617       }
618       rbody_len += l;
619       if((rbody_max_len > 0) && (rbody_len > rbody_max_len)) {
620         r->status = 413;
621         break;
622       }
623     }
624   }
625   /* we are done */
626   s_rbody_len = apr_psprintf(r->pool, "%"APR_OFF_T_FMT, rbody_len);
627   apr_table_setn(r->headers_out, "Received-Length", s_rbody_len);
628   rv = apr_brigade_puts(bb, NULL, NULL, s_rbody_len);
629   if(APR_SUCCESS != rv)
630     goto cleanup;
631   b = apr_bucket_eos_create(c->bucket_alloc);
632   APR_BRIGADE_INSERT_TAIL(bb, b);
633   ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "put_handler: request read");
634 
635   rv = ap_pass_brigade(r->output_filters, bb);
636 
637   if(r->status == 413) {
638     apr_sleep(apr_time_from_sec(1));
639   }
640 
641 cleanup:
642   if(rv == APR_SUCCESS
643      || r->status != HTTP_OK
644      || c->aborted) {
645     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "put_handler: done");
646     return OK;
647   }
648   else {
649     /* no way to know what type of error occurred */
650     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "put_handler failed");
651     return AP_FILTER_ERROR;
652   }
653   return DECLINED;
654 }
655 
curltest_1_1_required(request_rec * r)656 static int curltest_1_1_required(request_rec *r)
657 {
658   conn_rec *c = r->connection;
659   apr_bucket_brigade *bb;
660   apr_bucket *b;
661   apr_status_t rv;
662   char buffer[16*1024];
663   const char *ct;
664   const char *request_id = "none";
665   apr_time_t chunk_delay = 0;
666   apr_array_header_t *args = NULL;
667   long l;
668   int i;
669 
670   if(strcmp(r->handler, "curltest-1_1-required")) {
671     return DECLINED;
672   }
673 
674   if(HTTP_VERSION_MAJOR(r->proto_num) > 1) {
675     apr_table_setn(r->notes, "ssl-renegotiate-forbidden", "1");
676     ap_die(HTTP_FORBIDDEN, r);
677     return OK;
678   }
679 
680   ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "1_1_handler: processing");
681   r->status = 200;
682   r->clength = -1;
683   r->chunked = 1;
684   apr_table_unset(r->headers_out, "Content-Length");
685   /* Discourage content-encodings */
686   apr_table_unset(r->headers_out, "Content-Encoding");
687   apr_table_setn(r->subprocess_env, "no-brotli", "1");
688   apr_table_setn(r->subprocess_env, "no-gzip", "1");
689 
690   ct = apr_table_get(r->headers_in, "content-type");
691   ap_set_content_type(r, ct ? ct : "text/plain");
692 
693   bb = apr_brigade_create(r->pool, c->bucket_alloc);
694   /* flush response */
695   b = apr_bucket_flush_create(c->bucket_alloc);
696   APR_BRIGADE_INSERT_TAIL(bb, b);
697   rv = ap_pass_brigade(r->output_filters, bb);
698   if(APR_SUCCESS != rv)
699     goto cleanup;
700 
701   /* we are done */
702   rv = apr_brigade_printf(bb, NULL, NULL, "well done!");
703   if(APR_SUCCESS != rv)
704     goto cleanup;
705   b = apr_bucket_eos_create(c->bucket_alloc);
706   APR_BRIGADE_INSERT_TAIL(bb, b);
707   ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "1_1_handler: request read");
708 
709   rv = ap_pass_brigade(r->output_filters, bb);
710 
711 cleanup:
712   if(rv == APR_SUCCESS
713      || r->status != HTTP_OK
714      || c->aborted) {
715     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "1_1_handler: done");
716     return OK;
717   }
718   else {
719     /* no way to know what type of error occurred */
720     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "1_1_handler failed");
721     return AP_FILTER_ERROR;
722   }
723   return DECLINED;
724 }
725 
brigade_env_var(request_rec * r,apr_bucket_brigade * bb,const char * name)726 static int brigade_env_var(request_rec *r, apr_bucket_brigade *bb,
727                            const char *name)
728 {
729   const char *s;
730   s = apr_table_get(r->subprocess_env, name);
731   if(s)
732     return apr_brigade_printf(bb, NULL, NULL, ",\n  \"%s\": \"%s\"", name, s);
733   return 0;
734 }
735 
curltest_sslinfo_handler(request_rec * r)736 static int curltest_sslinfo_handler(request_rec *r)
737 {
738   conn_rec *c = r->connection;
739   apr_bucket_brigade *bb;
740   apr_bucket *b;
741   apr_status_t rv;
742   apr_array_header_t *args = NULL;
743   const char *request_id = NULL;
744   int close_conn = 0;
745   long l;
746   int i;
747 
748   if(strcmp(r->handler, "curltest-sslinfo")) {
749     return DECLINED;
750   }
751   if(r->method_number != M_GET) {
752     return DECLINED;
753   }
754 
755   if(r->args) {
756     apr_array_header_t *args = apr_cstr_split(r->args, "&", 1, r->pool);
757     for(i = 0; i < args->nelts; ++i) {
758       char *s, *val, *arg = APR_ARRAY_IDX(args, i, char *);
759       s = strchr(arg, '=');
760       if(s) {
761         *s = '\0';
762         val = s + 1;
763         if(!strcmp("id", arg)) {
764           /* just an id for repeated requests with curl's url globbing */
765           request_id = val;
766           continue;
767         }
768       }
769       else if(!strcmp("close", arg)) {
770         /* we are asked to close the connection */
771         close_conn = 1;
772         continue;
773       }
774       ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "query parameter not "
775                     "understood: '%s' in %s",
776                     arg, r->args);
777       ap_die(HTTP_BAD_REQUEST, r);
778       return OK;
779     }
780   }
781 
782   ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "sslinfo: processing");
783   r->status = 200;
784   r->clength = -1;
785   r->chunked = 1;
786   apr_table_unset(r->headers_out, "Content-Length");
787   /* Discourage content-encodings */
788   apr_table_unset(r->headers_out, "Content-Encoding");
789   apr_table_setn(r->subprocess_env, "no-brotli", "1");
790   apr_table_setn(r->subprocess_env, "no-gzip", "1");
791 
792   ap_set_content_type(r, "application/json");
793 
794   bb = apr_brigade_create(r->pool, c->bucket_alloc);
795 
796   apr_brigade_puts(bb, NULL, NULL, "{\n  \"Name\": \"SSL-Information\"");
797   brigade_env_var(r, bb, "HTTPS");
798   brigade_env_var(r, bb, "SSL_PROTOCOL");
799   brigade_env_var(r, bb, "SSL_CIPHER");
800   brigade_env_var(r, bb, "SSL_SESSION_ID");
801   brigade_env_var(r, bb, "SSL_SESSION_RESUMED");
802   brigade_env_var(r, bb, "SSL_SRP_USER");
803   brigade_env_var(r, bb, "SSL_SRP_USERINFO");
804   brigade_env_var(r, bb, "SSL_TLS_SNI");
805   apr_brigade_puts(bb, NULL, NULL, "}\n");
806 
807   /* flush response */
808   b = apr_bucket_flush_create(c->bucket_alloc);
809   APR_BRIGADE_INSERT_TAIL(bb, b);
810   rv = ap_pass_brigade(r->output_filters, bb);
811   if(APR_SUCCESS != rv)
812     goto cleanup;
813 
814   /* we are done */
815   b = apr_bucket_eos_create(c->bucket_alloc);
816   APR_BRIGADE_INSERT_TAIL(bb, b);
817   ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "1_1_handler: request read");
818 
819   rv = ap_pass_brigade(r->output_filters, bb);
820 
821 cleanup:
822   if(close_conn)
823     r->connection->keepalive = AP_CONN_CLOSE;
824   if(rv == APR_SUCCESS
825      || r->status != HTTP_OK
826      || c->aborted) {
827     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "1_1_handler: done");
828     return OK;
829   }
830   else {
831     /* no way to know what type of error occurred */
832     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "1_1_handler failed");
833     return AP_FILTER_ERROR;
834   }
835   return DECLINED;
836 }
837