libtest_convenience_server_reply.c (27113B)
1 /* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ 2 /* 3 This file is part of GNU libmicrohttpd. 4 Copyright (C) 2024 Christian Grothoff 5 Copyright (C) 2024 Evgeny Grin (Karlson2k) 6 7 GNU libmicrohttpd is free software; you can redistribute it and/or 8 modify it under the terms of the GNU Lesser General Public 9 License as published by the Free Software Foundation; either 10 version 2.1 of the License, or (at your option) any later version. 11 12 GNU libmicrohttpd is distributed in the hope that it will be useful, 13 but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 Lesser General Public License for more details. 16 17 Alternatively, you can redistribute GNU libmicrohttpd and/or 18 modify it under the terms of the GNU General Public License as 19 published by the Free Software Foundation; either version 2 of 20 the License, or (at your option) any later version, together 21 with the eCos exception, as follows: 22 23 As a special exception, if other files instantiate templates or 24 use macros or inline functions from this file, or you compile this 25 file and link it with other works to produce a work based on this 26 file, this file does not by itself cause the resulting work to be 27 covered by the GNU General Public License. However the source code 28 for this file must still be made available in accordance with 29 section (3) of the GNU General Public License v2. 30 31 This exception does not invalidate any other reasons why a work 32 based on this file might be covered by the GNU General Public 33 License. 34 35 You should have received copies of the GNU Lesser General Public 36 License and the GNU General Public License along with this library; 37 if not, see <https://www.gnu.org/licenses/>. 38 */ 39 40 /** 41 * @file libtest_convenience_server_reply.c 42 * @brief convenience functions that generate 43 * replies from the server for libtest users 44 * @author Christian Grothoff 45 */ 46 #include "libtest.h" 47 #include <pthread.h> 48 #include <stdbool.h> 49 #include <fcntl.h> 50 #include <stdio.h> 51 #include <unistd.h> 52 #include <errno.h> 53 #include <curl/curl.h> 54 #include <assert.h> 55 #include "mhdt_tmpfile.h" 56 57 #ifndef CURL_VERSION_BITS 58 # define CURL_VERSION_BITS(x, y, z) ((x) << 16 | (y) << 8 | (z)) 59 #endif 60 #ifndef CURL_AT_LEAST_VERSION 61 # define CURL_AT_LEAST_VERSION(x, y, z) \ 62 (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS (x, y, z)) 63 #endif 64 65 const struct MHD_Action * 66 MHDT_server_reply_text ( 67 void *cls, 68 struct MHD_Request *MHD_RESTRICT request, 69 const struct MHD_String *MHD_RESTRICT path, 70 enum MHD_HTTP_Method method, 71 uint_fast64_t upload_size) 72 { 73 const char *text = cls; 74 75 (void)path; 76 (void)method; 77 (void)upload_size; /* Unused */ 78 79 return MHD_action_from_response ( 80 request, 81 MHD_response_from_buffer_static (MHD_HTTP_STATUS_OK, 82 strlen (text), 83 text)); 84 } 85 86 87 const struct MHD_Action * 88 MHDT_server_reply_file ( 89 void *cls, 90 struct MHD_Request *MHD_RESTRICT request, 91 const struct MHD_String *MHD_RESTRICT path, 92 enum MHD_HTTP_Method method, 93 uint_fast64_t upload_size) 94 { 95 const char *text = cls; 96 size_t written = 0u; 97 size_t tlen = strlen (text); 98 int fd; 99 100 (void)path; 101 (void)method; 102 (void)upload_size; /* Unused */ 103 104 fd = MHDT_create_tmpfile (); 105 if (-1 == fd) 106 { 107 fprintf (stderr, 108 "Failed to create a temporary file\n"); 109 fflush (stderr); 110 exit (99); 111 } 112 113 while (written < tlen) 114 { 115 ssize_t wr_chunk = write (fd, 116 text + written, 117 tlen - written); 118 if (0 == wr_chunk) 119 break; 120 else if (0 > wr_chunk) 121 { 122 if (EINTR == errno) 123 continue; 124 break; 125 } 126 written += (size_t)wr_chunk; 127 } 128 if (written < tlen) 129 { 130 fprintf (stderr, 131 "Failed to write() a temporary file: %s\n", 132 strerror (errno)); 133 (void)close (fd); 134 fflush (stderr); 135 exit (99); 136 } 137 return MHD_action_from_response ( 138 request, 139 MHD_response_from_fd (MHD_HTTP_STATUS_OK, 140 fd, 141 0 /* offset */, 142 tlen)); 143 } 144 145 146 const struct MHD_Action * 147 MHDT_server_reply_with_header ( 148 void *cls, 149 struct MHD_Request *MHD_RESTRICT request, 150 const struct MHD_String *MHD_RESTRICT path, 151 enum MHD_HTTP_Method method, 152 uint_fast64_t upload_size) 153 { 154 const char *header = cls; 155 size_t hlen = strlen (header) + 1; 156 char name[hlen]; 157 const char *colon = strchr (header, ':'); 158 const char *value; 159 struct MHD_Response *resp; 160 161 (void)path; 162 (void)method; 163 (void)upload_size; /* Unused */ 164 165 memcpy (name, 166 header, 167 hlen); 168 name[colon - header] = '\0'; 169 value = &name[colon - header + 1]; 170 171 resp = MHD_response_from_empty (MHD_HTTP_STATUS_NO_CONTENT); 172 if (MHD_SC_OK != 173 MHD_response_add_header (resp, 174 name, 175 value)) 176 { 177 MHD_response_destroy (resp); 178 return MHD_action_abort_request (request); 179 } 180 return MHD_action_from_response ( 181 request, 182 resp); 183 } 184 185 186 const struct MHD_Action * 187 MHDT_server_reply_check_query ( 188 void *cls, 189 struct MHD_Request *MHD_RESTRICT request, 190 const struct MHD_String *MHD_RESTRICT path, 191 enum MHD_HTTP_Method method, 192 uint_fast64_t upload_size) 193 { 194 const char *equery = cls; 195 size_t qlen = strlen (equery) + 1; 196 char qc[qlen]; 197 198 (void)path; 199 (void)method; 200 (void)upload_size; /* Unused */ 201 202 memcpy (qc, 203 equery, 204 qlen); 205 for (const char *tok = strtok (qc, "&"); 206 NULL != tok; 207 tok = strtok (NULL, "&")) 208 { 209 const char *end; 210 struct MHD_StringNullable sn; 211 const char *val; 212 213 end = strchr (tok, '='); 214 if (NULL == end) 215 { 216 end = &tok[strlen (tok)]; 217 val = NULL; 218 } 219 else 220 { 221 val = end + 1; 222 } 223 { 224 size_t alen = (size_t)(end - tok); 225 char arg[alen + 1]; 226 227 memcpy (arg, 228 tok, 229 alen); 230 arg[alen] = '\0'; 231 if (MHD_NO == 232 MHD_request_get_value (request, 233 MHD_VK_URI_QUERY_PARAM, 234 arg, 235 &sn)) 236 { 237 fprintf (stderr, 238 "NULL returned for query key %s\n", 239 arg); 240 return MHD_action_abort_request (request); 241 } 242 if (NULL == val) 243 { 244 if (NULL != sn.cstr) 245 { 246 fprintf (stderr, 247 "NULL expected for value for query key %s, got %s\n", 248 arg, 249 sn.cstr); 250 return MHD_action_abort_request (request); 251 } 252 } 253 else 254 { 255 if (NULL == sn.cstr) 256 { 257 fprintf (stderr, 258 "%s expected for value for query key %s, got NULL\n", 259 val, 260 arg); 261 return MHD_action_abort_request (request); 262 } 263 if (0 != strcmp (val, 264 sn.cstr)) 265 { 266 fprintf (stderr, 267 "%s expected for value for query key %s, got %s\n", 268 val, 269 arg, 270 sn.cstr); 271 return MHD_action_abort_request (request); 272 } 273 } 274 } 275 } 276 277 return MHD_action_from_response ( 278 request, 279 MHD_response_from_empty ( 280 MHD_HTTP_STATUS_NO_CONTENT)); 281 } 282 283 284 const struct MHD_Action * 285 MHDT_server_reply_check_header ( 286 void *cls, 287 struct MHD_Request *MHD_RESTRICT request, 288 const struct MHD_String *MHD_RESTRICT path, 289 enum MHD_HTTP_Method method, 290 uint_fast64_t upload_size) 291 { 292 const char *want = cls; 293 size_t wlen = strlen (want) + 1; 294 char key[wlen]; 295 const char *colon = strchr (want, ':'); 296 struct MHD_StringNullable have; 297 const char *value; 298 299 (void)path; 300 (void)method; 301 (void)upload_size; /* Unused */ 302 303 memcpy (key, 304 want, 305 wlen); 306 if (NULL != colon) 307 { 308 key[colon - want] = '\0'; 309 value = &key[colon - want + 1]; 310 } 311 else 312 { 313 value = NULL; 314 } 315 if (MHD_NO == 316 MHD_request_get_value (request, 317 MHD_VK_HEADER, 318 key, 319 &have)) 320 { 321 fprintf (stderr, 322 "Missing client header `%s'\n", 323 want); 324 return MHD_action_abort_request (request); 325 } 326 if (NULL == value) 327 { 328 if (NULL != have.cstr) 329 { 330 fprintf (stderr, 331 "Have unexpected client header `%s': `%s'\n", 332 key, 333 have.cstr); 334 return MHD_action_abort_request (request); 335 } 336 } 337 else 338 { 339 if (NULL == have.cstr) 340 { 341 fprintf (stderr, 342 "Missing value for client header `%s'\n", 343 want); 344 return MHD_action_abort_request (request); 345 } 346 if (0 != strcmp (have.cstr, 347 value)) 348 { 349 fprintf (stderr, 350 "Client HTTP header `%s' was expected to be `%s' but is `%s'\n", 351 key, 352 value, 353 have.cstr); 354 return MHD_action_abort_request (request); 355 } 356 } 357 return MHD_action_from_response ( 358 request, 359 MHD_response_from_empty ( 360 MHD_HTTP_STATUS_NO_CONTENT)); 361 } 362 363 364 /** 365 * Function to process data uploaded by a client. 366 * 367 * @param cls the payload we expect to be uploaded as a 0-terminated string 368 * @param request the request is being processed 369 * @param content_data_size the size of the @a content_data, 370 * zero when all data have been processed 371 * @param[in] content_data the uploaded content data, 372 * may be modified in the callback, 373 * valid only until return from the callback, 374 * NULL when all data have been processed 375 * @return action specifying how to proceed: 376 * #MHD_upload_action_continue() to continue upload (for incremental 377 * upload processing only), 378 * #MHD_upload_action_suspend() to stop reading the upload until 379 * the request is resumed, 380 * #MHD_upload_action_abort_request() to close the socket, 381 * or a response to discard the rest of the upload and transmit 382 * the response 383 * @ingroup action 384 */ 385 static const struct MHD_UploadAction * 386 check_upload_cb (void *cls, 387 struct MHD_Request *request, 388 size_t content_data_size, 389 void *content_data) 390 { 391 const char *want = cls; 392 size_t wlen = strlen (want); 393 394 if (content_data_size != wlen) 395 { 396 fprintf (stderr, 397 "Invalid body size given to full upload callback\n"); 398 return MHD_upload_action_abort_request (request); 399 } 400 if (0 != memcmp (want, 401 content_data, 402 wlen)) 403 { 404 fprintf (stderr, 405 "Invalid body data given to full upload callback\n"); 406 return MHD_upload_action_abort_request (request); 407 } 408 /* success! */ 409 return MHD_upload_action_from_response ( 410 request, 411 MHD_response_from_empty ( 412 MHD_HTTP_STATUS_NO_CONTENT)); 413 } 414 415 416 const struct MHD_Action * 417 MHDT_server_reply_check_upload ( 418 void *cls, 419 struct MHD_Request *MHD_RESTRICT request, 420 const struct MHD_String *MHD_RESTRICT path, 421 enum MHD_HTTP_Method method, 422 uint_fast64_t upload_size) 423 { 424 const char *want = cls; 425 size_t wlen = strlen (want); 426 427 (void)path; 428 (void)method; 429 (void)upload_size; /* Unused */ 430 431 return MHD_action_process_upload_full (request, 432 wlen, 433 &check_upload_cb, 434 (void *)want); 435 } 436 437 438 /** 439 * Closure for #chunk_return. 440 */ 441 struct ChunkContext 442 { 443 /** 444 * Where we are in the buffer. 445 */ 446 const char *pos; 447 }; 448 449 450 /** 451 * Function that returns a string in chunks. 452 * 453 * @param dyn_cont_cls must be a `struct ChunkContext` 454 * @param ctx the context to produce the action to return, 455 * the pointer is only valid until the callback returns 456 * @param pos position in the datastream to access; 457 * note that if a `struct MHD_Response` object is re-used, 458 * it is possible for the same content reader to 459 * be queried multiple times for the same data; 460 * however, if a `struct MHD_Response` is not re-used, 461 * libmicrohttpd guarantees that "pos" will be 462 * the sum of all data sizes provided by this callback 463 * @param[out] buf where to copy the data 464 * @param max maximum number of bytes to copy to @a buf (size of @a buf) 465 * @return action to use, 466 * NULL in case of any error (the response will be aborted) 467 */ 468 static const struct MHD_DynamicContentCreatorAction * 469 chunk_return (void *cls, 470 struct MHD_DynamicContentCreatorContext *ctx, 471 uint_fast64_t pos, 472 void *buf, 473 size_t max) 474 { 475 struct ChunkContext *cc = cls; 476 size_t imax = strlen (cc->pos); 477 const char *space = strchr (cc->pos, ' '); 478 479 (void)pos; // TODO: add check 480 481 if (0 == imax) 482 return MHD_DCC_action_finish (ctx); 483 if (NULL != space) 484 imax = (size_t)(space - cc->pos) + 1; 485 if (imax > max) 486 imax = max; 487 memcpy (buf, 488 cc->pos, 489 imax); 490 cc->pos += imax; 491 return MHD_DCC_action_continue (ctx, 492 imax); 493 } 494 495 496 const struct MHD_Action * 497 MHDT_server_reply_chunked_text ( 498 void *cls, 499 struct MHD_Request *MHD_RESTRICT request, 500 const struct MHD_String *MHD_RESTRICT path, 501 enum MHD_HTTP_Method method, 502 uint_fast64_t upload_size) 503 { 504 const char *text = cls; 505 struct ChunkContext *cc; 506 507 (void)path; 508 (void)method; 509 (void)upload_size; /* Unused */ 510 511 cc = malloc (sizeof (struct ChunkContext)); 512 if (NULL == cc) 513 return NULL; 514 cc->pos = text; 515 516 return MHD_action_from_response ( 517 request, 518 MHD_response_from_callback (MHD_HTTP_STATUS_OK, 519 MHD_SIZE_UNKNOWN, 520 &chunk_return, 521 cc, 522 &free)); 523 } 524 525 526 /** 527 * Compare two strings, succeed if both are NULL. 528 * 529 * @param wants string we want 530 * @param have string we have 531 * @return true if what we @a want is what we @a have 532 */ 533 static bool 534 nstrcmp (const char *wants, 535 const struct MHD_StringNullable *have) 536 { 537 if ((NULL == wants) 538 && (NULL == have->cstr) 539 && (0 == have->len)) 540 return true; 541 if ((NULL == wants) 542 || (NULL == have->cstr)) 543 return false; 544 return (0 == strcmp (wants, 545 have->cstr)); 546 } 547 548 549 /** 550 * "Stream" reader for POST data. 551 * This callback is called to incrementally process parsed POST data sent by 552 * the client. 553 * 554 * @param req the request 555 * @param cls user-specified closure 556 * @param name the name of the POST field 557 * @param filename the name of the uploaded file, @a cstr member is NULL if not 558 * known / not provided 559 * @param content_type the mime-type of the data, cstr member is NULL if not 560 * known / not provided 561 * @param encoding the encoding of the data, cstr member is NULL if not known / 562 * not provided 563 * @param size the number of bytes in @a data available, may be zero if 564 * the @a final_data is #MHD_YES 565 * @param data the pointer to @a size bytes of data at the specified 566 * @a off offset, NOT zero-terminated 567 * @param off the offset of @a data in the overall value, always equal to 568 * the sum of sizes of previous calls for the same field / file; 569 * client may provide more than one field with the same name and 570 * the same filename, the new filed (or file) is indicated by zero 571 * value of @a off (and the end is indicated by @a final_data) 572 * @param final_data if set to #MHD_YES then full field data is provided, 573 * if set to #MHD_NO then more field data may be provided 574 * @return action specifying how to proceed: 575 * #MHD_upload_action_continue() if all is well, 576 * #MHD_upload_action_suspend() to stop reading the upload until 577 * the request is resumed, 578 * #MHD_upload_action_abort_request() to close the socket, 579 * or a response to discard the rest of the upload and transmit 580 * the response 581 * @ingroup action 582 */ 583 static const struct MHD_UploadAction * 584 post_stream_reader (struct MHD_Request *req, 585 void *cls, 586 const struct MHD_String *name, 587 const struct MHD_StringNullable *filename, 588 const struct MHD_StringNullable *content_type, 589 const struct MHD_StringNullable *encoding, 590 size_t size, 591 const void *data, 592 uint_fast64_t off, 593 enum MHD_Bool final_data) 594 { 595 struct MHDT_PostInstructions *pi = cls; 596 struct MHDT_PostWant *wants = pi->wants; 597 598 (void)encoding; // TODO: add check 599 600 if (NULL != wants) 601 { 602 for (unsigned int i = 0; NULL != wants[i].key; i++) 603 { 604 struct MHDT_PostWant *want = &wants[i]; 605 606 if (want->satisfied) 607 continue; 608 if (0 != strcmp (want->key, 609 name->cstr)) 610 continue; 611 if (!nstrcmp (want->filename, 612 filename)) 613 continue; 614 if (!nstrcmp (want->content_type, 615 content_type)) 616 continue; 617 if (!want->incremental) 618 continue; 619 if (want->value_off != off) 620 continue; 621 if (want->value_size < off + size) 622 continue; 623 if (0 != memcmp (data, 624 want->value + off, 625 size)) 626 continue; 627 want->value_off += size; 628 want->satisfied = (want->value_size == want->value_off) && final_data; 629 } 630 } 631 632 return MHD_upload_action_continue (req); 633 } 634 635 636 /** 637 * Iterator over name-value pairs. This iterator can be used to 638 * iterate over all of the cookies, headers, or POST-data fields of a 639 * request, and also to iterate over the headers that have been added 640 * to a response. 641 * 642 * The pointers to the strings in @a nvt are valid until the response 643 * is queued. If the data is needed beyond this point, it should be copied. 644 * 645 * @param cls closure 646 * @param nvt the name, the value and the kind of the element 647 * @return #MHD_YES to continue iterating, 648 * #MHD_NO to abort the iteration 649 * @ingroup request 650 */ 651 static enum MHD_Bool 652 check_complete_post_value ( 653 void *cls, 654 enum MHD_ValueKind kind, 655 const struct MHD_NameAndValue *nv) 656 { 657 struct MHDT_PostInstructions *pi = cls; 658 struct MHDT_PostWant *wants = pi->wants; 659 660 if (NULL == wants) 661 return MHD_NO; 662 if (MHD_VK_POSTDATA != kind) 663 return MHD_NO; 664 for (unsigned int i = 0; NULL != wants[i].key; i++) 665 { 666 struct MHDT_PostWant *want = &wants[i]; 667 668 if (want->satisfied) 669 continue; 670 if (want->incremental) 671 continue; 672 if (0 != strcmp (want->key, 673 nv->name.cstr)) 674 continue; 675 if (NULL == want->value) 676 { 677 if (NULL == nv->value.cstr) 678 want->satisfied = true; 679 } 680 else if (NULL == nv->value.cstr) 681 continue; 682 else if (0 == want->value_size) 683 { 684 if (0 == strcmp (nv->value.cstr, 685 want->value)) 686 want->satisfied = true; 687 } 688 else 689 { 690 if ((want->value_size == nv->value.len) 691 && (0 == memcmp (nv->value.cstr, 692 want->value, 693 want->value_size))) 694 want->satisfied = true; 695 } 696 } 697 return MHD_YES; 698 } 699 700 701 /** 702 * The callback to be called when finished with processing 703 * of the postprocessor upload data. 704 * @param req the request 705 * @param cls the closure 706 * @param parsing_result the result of POST data parsing 707 * @return the action to proceed 708 */ 709 static const struct MHD_UploadAction * 710 post_stream_done (struct MHD_Request *req, 711 void *cls, 712 enum MHD_PostParseResult parsing_result) 713 { 714 struct MHDT_PostInstructions *pi = cls; 715 struct MHDT_PostWant *wants = pi->wants; 716 717 if (MHD_POST_PARSE_RES_OK != parsing_result) 718 { 719 fprintf (stderr, 720 "POST parsing was not successful. The result: %d\n", 721 (int)parsing_result); 722 return MHD_upload_action_abort_request (req); 723 } 724 725 MHD_request_get_values_cb (req, 726 MHD_VK_POSTDATA, 727 &check_complete_post_value, 728 pi); 729 if (NULL != wants) 730 { 731 for (unsigned int i = 0; NULL != wants[i].key; i++) 732 { 733 struct MHDT_PostWant *want = &wants[i]; 734 735 if (want->satisfied) 736 continue; 737 fprintf (stderr, 738 "Expected key-value pair `%s' missing\n", 739 want->key); 740 return MHD_upload_action_abort_request (req); 741 } 742 } 743 return MHD_upload_action_from_response ( 744 req, 745 MHD_response_from_empty ( 746 MHD_HTTP_STATUS_NO_CONTENT)); 747 } 748 749 750 const struct MHD_Action * 751 MHDT_server_reply_check_post ( 752 void *cls, 753 struct MHD_Request *MHD_RESTRICT request, 754 const struct MHD_String *MHD_RESTRICT path, 755 enum MHD_HTTP_Method method, 756 uint_fast64_t upload_size) 757 { 758 struct MHDT_PostInstructions *pi = cls; 759 760 (void)path; /* Unused */ 761 (void)upload_size; // TODO: add check 762 763 if (MHD_HTTP_METHOD_POST != method) 764 { 765 fprintf (stderr, 766 "Reported HTTP method other then POST. Reported method: %u\n", 767 (unsigned)method); 768 return MHD_action_abort_request (req); 769 } 770 771 return MHD_action_parse_post (request, 772 pi->buffer_size, 773 pi->auto_stream_size, 774 pi->enc, 775 &post_stream_reader, 776 pi, 777 &post_stream_done, 778 pi); 779 } 780 781 782 const struct MHD_Action * 783 MHDT_server_reply_check_basic_auth ( 784 void *cls, 785 struct MHD_Request *MHD_RESTRICT request, 786 const struct MHD_String *MHD_RESTRICT path, 787 enum MHD_HTTP_Method method, 788 uint_fast64_t upload_size) 789 { 790 const char *cred = cls; 791 union MHD_RequestInfoDynamicData dd; 792 enum MHD_StatusCode sc; 793 const struct MHD_AuthBasicCreds *ba; 794 795 /* should not be needed, except to make gcc happy */ 796 memset (&dd, 797 0, 798 sizeof (dd)); 799 sc = MHD_request_get_info_dynamic (request, 800 MHD_REQUEST_INFO_DYNAMIC_AUTH_BASIC_CREDS, 801 &dd); 802 if (MHD_SC_OK != sc) 803 { 804 fprintf (stderr, 805 "No credentials?\n"); 806 return MHD_action_basic_auth_challenge_p ( 807 request, 808 "test-realm", 809 MHD_YES, 810 MHD_response_from_empty ( 811 MHD_HTTP_STATUS_UNAUTHORIZED)); 812 } 813 ba = dd.v_auth_basic_creds; 814 assert (NULL != ba); 815 if ((0 != strncmp (ba->username.cstr, 816 cred, 817 ba->username.len)) 818 || (':' != cred[ba->username.len]) 819 || (NULL == ba->password.cstr) 820 || (0 != strcmp (ba->password.cstr, 821 &cred[ba->username.len + 1]))) 822 { 823 fprintf (stderr, 824 "Wrong credentials (Got: %s/%s Want: %s)!\n", 825 ba->username.cstr, 826 ba->password.cstr, 827 cred); 828 return MHD_action_basic_auth_challenge_p ( 829 request, 830 "test-realm", 831 MHD_YES, 832 MHD_response_from_empty ( 833 MHD_HTTP_STATUS_UNAUTHORIZED)); 834 } 835 return MHD_action_from_response ( 836 request, 837 MHD_response_from_empty ( 838 MHD_HTTP_STATUS_NO_CONTENT)); 839 } 840 841 842 const struct MHD_Action * 843 MHDT_server_reply_check_digest_auth ( 844 void *cls, 845 struct MHD_Request *MHD_RESTRICT request, 846 const struct MHD_String *MHD_RESTRICT path, 847 enum MHD_HTTP_Method method, 848 uint_fast64_t upload_size) 849 { 850 const char *cred = cls; 851 const char *colon = strchr (cred, ':'); 852 char *username; 853 const char *password; 854 enum MHD_DigestAuthResult dar; 855 const char *realm = "test-realm"; 856 #if CURL_AT_LEAST_VERSION (7, 57, 0) 857 enum MHD_DigestAuthAlgo algo = MHD_DIGEST_AUTH_ALGO_SHA256; 858 #else 859 enum MHD_DigestAuthAlgo algo = MHD_DIGEST_AUTH_ALGO_MD5; 860 #endif 861 size_t digest_len = MHD_digest_get_hash_size (algo); 862 863 (void)cls; /* Unused, mute compiler warning */ 864 865 if (0 == digest_len) 866 return NULL; 867 assert (NULL != colon); 868 password = colon + 1; 869 username = strndup (cred, 870 colon - cred); 871 assert (NULL != username); 872 { 873 enum MHD_StatusCode sc; 874 char digest[digest_len]; 875 876 // FIXME: why is this needed? We should not get a warning 877 // even without this memset! 878 memset (digest, 0, sizeof (digest)); 879 sc = MHD_digest_auth_calc_userdigest (algo, 880 username, 881 realm, 882 password, 883 sizeof (digest), 884 digest); 885 if (MHD_SC_OK != sc) 886 { 887 fprintf (stderr, 888 "MHD_digest_auth_calc_userdigest: %d\n", 889 (int)sc); 890 free (username); 891 return NULL; 892 } 893 dar = MHD_digest_auth_check_digest (request, 894 realm, 895 username, 896 sizeof (digest), 897 digest, 898 0, /* maximum nonce counter; 0: default */ 899 MHD_DIGEST_AUTH_MULT_QOP_AUTH, 900 (enum MHD_DigestAuthMultiAlgo)algo); 901 } 902 free (username); 903 if ((MHD_DAUTH_HEADER_MISSING == dar) 904 || (MHD_DAUTH_NONCE_STALE == dar)) 905 { 906 struct MHD_Response *resp; 907 enum MHD_StatusCode sc; 908 909 resp = MHD_response_from_empty ( 910 MHD_HTTP_STATUS_UNAUTHORIZED); 911 if (NULL == resp) 912 { 913 fprintf (stderr, 914 "Failed to create response body\n"); 915 return NULL; 916 } 917 sc = MHD_response_add_auth_digest_challenge ( 918 resp, 919 "test-realm", 920 "opaque", 921 NULL, /* domain */ 922 (MHD_DAUTH_NONCE_STALE == dar) ? MHD_YES : MHD_NO, /* indicate stale */ 923 MHD_DIGEST_AUTH_MULT_QOP_AUTH, 924 (enum MHD_DigestAuthMultiAlgo)algo, 925 MHD_NO /* userhash_support */, 926 MHD_YES /* prefer UTF8 */); 927 if (MHD_SC_OK != sc) 928 { 929 fprintf (stderr, 930 "MHD_response_add_auth_digest_challenge failed: %d\n", 931 (int)sc); 932 return NULL; 933 } 934 return MHD_action_from_response ( 935 request, 936 resp); 937 } 938 if (MHD_DAUTH_RESPONSE_WRONG == dar) 939 return MHD_action_from_response ( 940 request, 941 MHD_response_from_empty (MHD_HTTP_STATUS_FORBIDDEN)); 942 943 if (MHD_DAUTH_OK == dar) 944 return MHD_action_from_response ( 945 request, 946 MHD_response_from_empty ( 947 MHD_HTTP_STATUS_NO_CONTENT)); 948 949 return MHD_action_abort_request (request); 950 }