• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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