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