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   const char *s_rbody_len;
538   const char *request_id = "none";
539   apr_time_t read_delay = 0, chunk_delay = 0;
540   apr_array_header_t *args = NULL;
541   long l;
542   int i;
543 
544   if(strcmp(r->handler, "curltest-put")) {
545     return DECLINED;
546   }
547   if(r->method_number != M_PUT) {
548     return DECLINED;
549   }
550 
551   if(r->args) {
552     args = apr_cstr_split(r->args, "&", 1, r->pool);
553     for(i = 0; i < args->nelts; ++i) {
554       char *s, *val, *arg = APR_ARRAY_IDX(args, i, char *);
555       s = strchr(arg, '=');
556       if(s) {
557         *s = '\0';
558         val = s + 1;
559         if(!strcmp("id", arg)) {
560           /* just an id for repeated requests with curl's url globbing */
561           request_id = val;
562           continue;
563         }
564         else if(!strcmp("read_delay", arg)) {
565           rv = duration_parse(&read_delay, val, "s");
566           if(APR_SUCCESS == rv) {
567             continue;
568           }
569         }
570         else if(!strcmp("chunk_delay", arg)) {
571           rv = duration_parse(&chunk_delay, val, "s");
572           if(APR_SUCCESS == rv) {
573             continue;
574           }
575         }
576       }
577       ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "query parameter not "
578                     "understood: '%s' in %s",
579                     arg, r->args);
580       ap_die(HTTP_BAD_REQUEST, r);
581       return OK;
582     }
583   }
584 
585   ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "put_handler: processing");
586   r->status = 200;
587   r->clength = -1;
588   r->chunked = 1;
589   apr_table_unset(r->headers_out, "Content-Length");
590   /* Discourage content-encodings */
591   apr_table_unset(r->headers_out, "Content-Encoding");
592   apr_table_setn(r->subprocess_env, "no-brotli", "1");
593   apr_table_setn(r->subprocess_env, "no-gzip", "1");
594 
595   ct = apr_table_get(r->headers_in, "content-type");
596   ap_set_content_type(r, ct ? ct : "text/plain");
597 
598   if(read_delay) {
599     apr_sleep(read_delay);
600   }
601   bb = apr_brigade_create(r->pool, c->bucket_alloc);
602   /* copy any request body into the response */
603   rv = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK);
604   if(rv)
605     goto cleanup;
606   if(ap_should_client_block(r)) {
607     while(0 < (l = ap_get_client_block(r, &buffer[0], sizeof(buffer)))) {
608       ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
609                     "put_handler: read %ld bytes from request body", l);
610       if(chunk_delay) {
611         apr_sleep(chunk_delay);
612       }
613       rbody_len += l;
614     }
615   }
616   /* we are done */
617   s_rbody_len = apr_psprintf(r->pool, "%"APR_OFF_T_FMT, rbody_len);
618   apr_table_setn(r->headers_out, "Received-Length", s_rbody_len);
619   rv = apr_brigade_puts(bb, NULL, NULL, s_rbody_len);
620   if(APR_SUCCESS != rv)
621     goto cleanup;
622   b = apr_bucket_eos_create(c->bucket_alloc);
623   APR_BRIGADE_INSERT_TAIL(bb, b);
624   ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "put_handler: request read");
625 
626   rv = ap_pass_brigade(r->output_filters, bb);
627 
628 cleanup:
629   if(rv == APR_SUCCESS
630      || r->status != HTTP_OK
631      || c->aborted) {
632     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "put_handler: done");
633     return OK;
634   }
635   else {
636     /* no way to know what type of error occurred */
637     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "put_handler failed");
638     return AP_FILTER_ERROR;
639   }
640   return DECLINED;
641 }
642 
curltest_1_1_required(request_rec * r)643 static int curltest_1_1_required(request_rec *r)
644 {
645   conn_rec *c = r->connection;
646   apr_bucket_brigade *bb;
647   apr_bucket *b;
648   apr_status_t rv;
649   char buffer[16*1024];
650   const char *ct;
651   const char *request_id = "none";
652   apr_time_t chunk_delay = 0;
653   apr_array_header_t *args = NULL;
654   long l;
655   int i;
656 
657   if(strcmp(r->handler, "curltest-1_1-required")) {
658     return DECLINED;
659   }
660 
661   if(HTTP_VERSION_MAJOR(r->proto_num) > 1) {
662     apr_table_setn(r->notes, "ssl-renegotiate-forbidden", "1");
663     ap_die(HTTP_FORBIDDEN, r);
664     return OK;
665   }
666 
667   ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "1_1_handler: processing");
668   r->status = 200;
669   r->clength = -1;
670   r->chunked = 1;
671   apr_table_unset(r->headers_out, "Content-Length");
672   /* Discourage content-encodings */
673   apr_table_unset(r->headers_out, "Content-Encoding");
674   apr_table_setn(r->subprocess_env, "no-brotli", "1");
675   apr_table_setn(r->subprocess_env, "no-gzip", "1");
676 
677   ct = apr_table_get(r->headers_in, "content-type");
678   ap_set_content_type(r, ct ? ct : "text/plain");
679 
680   bb = apr_brigade_create(r->pool, c->bucket_alloc);
681   /* flush response */
682   b = apr_bucket_flush_create(c->bucket_alloc);
683   APR_BRIGADE_INSERT_TAIL(bb, b);
684   rv = ap_pass_brigade(r->output_filters, bb);
685   if(APR_SUCCESS != rv)
686     goto cleanup;
687 
688   /* we are done */
689   rv = apr_brigade_printf(bb, NULL, NULL, "well done!");
690   if(APR_SUCCESS != rv)
691     goto cleanup;
692   b = apr_bucket_eos_create(c->bucket_alloc);
693   APR_BRIGADE_INSERT_TAIL(bb, b);
694   ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "1_1_handler: request read");
695 
696   rv = ap_pass_brigade(r->output_filters, bb);
697 
698 cleanup:
699   if(rv == APR_SUCCESS
700      || r->status != HTTP_OK
701      || c->aborted) {
702     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "1_1_handler: done");
703     return OK;
704   }
705   else {
706     /* no way to know what type of error occurred */
707     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "1_1_handler failed");
708     return AP_FILTER_ERROR;
709   }
710   return DECLINED;
711 }
712 
brigade_env_var(request_rec * r,apr_bucket_brigade * bb,const char * name)713 static int brigade_env_var(request_rec *r, apr_bucket_brigade *bb,
714                            const char *name)
715 {
716   const char *s;
717   s = apr_table_get(r->subprocess_env, name);
718   if(s)
719     return apr_brigade_printf(bb, NULL, NULL, ",\n  \"%s\": \"%s\"", name, s);
720   return 0;
721 }
722 
curltest_sslinfo_handler(request_rec * r)723 static int curltest_sslinfo_handler(request_rec *r)
724 {
725   conn_rec *c = r->connection;
726   apr_bucket_brigade *bb;
727   apr_bucket *b;
728   apr_status_t rv;
729   apr_array_header_t *args = NULL;
730   const char *request_id = NULL;
731   int close_conn = 0;
732   long l;
733   int i;
734 
735   if(strcmp(r->handler, "curltest-sslinfo")) {
736     return DECLINED;
737   }
738   if(r->method_number != M_GET) {
739     return DECLINED;
740   }
741 
742   if(r->args) {
743     apr_array_header_t *args = apr_cstr_split(r->args, "&", 1, r->pool);
744     for(i = 0; i < args->nelts; ++i) {
745       char *s, *val, *arg = APR_ARRAY_IDX(args, i, char *);
746       s = strchr(arg, '=');
747       if(s) {
748         *s = '\0';
749         val = s + 1;
750         if(!strcmp("id", arg)) {
751           /* just an id for repeated requests with curl's url globbing */
752           request_id = val;
753           continue;
754         }
755       }
756       else if(!strcmp("close", arg)) {
757         /* we are asked to close the connection */
758         close_conn = 1;
759         continue;
760       }
761       ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "query parameter not "
762                     "understood: '%s' in %s",
763                     arg, r->args);
764       ap_die(HTTP_BAD_REQUEST, r);
765       return OK;
766     }
767   }
768 
769   ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "sslinfo: processing");
770   r->status = 200;
771   r->clength = -1;
772   r->chunked = 1;
773   apr_table_unset(r->headers_out, "Content-Length");
774   /* Discourage content-encodings */
775   apr_table_unset(r->headers_out, "Content-Encoding");
776   apr_table_setn(r->subprocess_env, "no-brotli", "1");
777   apr_table_setn(r->subprocess_env, "no-gzip", "1");
778 
779   ap_set_content_type(r, "application/json");
780 
781   bb = apr_brigade_create(r->pool, c->bucket_alloc);
782 
783   apr_brigade_puts(bb, NULL, NULL, "{\n  \"Name\": \"SSL-Information\"");
784   brigade_env_var(r, bb, "HTTPS");
785   brigade_env_var(r, bb, "SSL_PROTOCOL");
786   brigade_env_var(r, bb, "SSL_CIPHER");
787   brigade_env_var(r, bb, "SSL_SESSION_ID");
788   brigade_env_var(r, bb, "SSL_SESSION_RESUMED");
789   brigade_env_var(r, bb, "SSL_SRP_USER");
790   brigade_env_var(r, bb, "SSL_SRP_USERINFO");
791   brigade_env_var(r, bb, "SSL_TLS_SNI");
792   apr_brigade_puts(bb, NULL, NULL, "}\n");
793 
794   /* flush response */
795   b = apr_bucket_flush_create(c->bucket_alloc);
796   APR_BRIGADE_INSERT_TAIL(bb, b);
797   rv = ap_pass_brigade(r->output_filters, bb);
798   if(APR_SUCCESS != rv)
799     goto cleanup;
800 
801   /* we are done */
802   b = apr_bucket_eos_create(c->bucket_alloc);
803   APR_BRIGADE_INSERT_TAIL(bb, b);
804   ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "1_1_handler: request read");
805 
806   rv = ap_pass_brigade(r->output_filters, bb);
807 
808 cleanup:
809   if(close_conn)
810     r->connection->keepalive = AP_CONN_CLOSE;
811   if(rv == APR_SUCCESS
812      || r->status != HTTP_OK
813      || c->aborted) {
814     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "1_1_handler: done");
815     return OK;
816   }
817   else {
818     /* no way to know what type of error occurred */
819     ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "1_1_handler failed");
820     return AP_FILTER_ERROR;
821   }
822   return DECLINED;
823 }
824