mod_curltest.c 17 KB

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