demo.c (40968B)
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) 2013-2024 Christian Grothoff (and other contributing authors) 5 Copyright (C) 2014-2025 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 * @file demo.c 41 * @brief complex demonstration site: create directory index, offer 42 * upload via form and HTTP POST, download with mime type detection 43 * and error reporting (403, etc.) --- and all of this with 44 * high-performance settings (large buffers, thread pool). 45 * If you want to benchmark MHD, this code should be used to 46 * run tests against. Note that the number of threads may need 47 * to be adjusted depending on the number of available cores. 48 * @author Christian Grothoff 49 * @author Karlson2k (Evgeny Grin) 50 */ 51 #include <microhttpd2.h> 52 #include <sys/types.h> 53 #include <stdbool.h> 54 #if ! defined(_WIN32) || defined(__CYGWIN__) 55 # include <unistd.h> 56 #else 57 # include <direct.h> 58 #endif 59 #include <stdio.h> 60 #include <stdlib.h> 61 #include <signal.h> 62 #include <fcntl.h> 63 #include <sys/stat.h> 64 #include <string.h> 65 #if ! defined(_WIN32) || defined(__CYGWIN__) 66 # include <dirent.h> 67 #else 68 # include <io.h> 69 #endif 70 #ifdef MHD_HAVE_LIBMAGIC 71 # include <magic.h> 72 #endif /* MHD_HAVE_LIBMAGIC */ 73 #include <limits.h> 74 #include <ctype.h> 75 #include <errno.h> 76 #if defined(_WIN32) && ! defined(__CYGWIN__) 77 # include <locale.h> 78 #endif 79 80 #if ! defined(_WIN32) || defined(__CYGWIN__) 81 # include <pthread.h> 82 typedef pthread_mutex_t my_mutex_t; 83 # define my_mutex_init(pmtx) ((void) pthread_mutex_init ((pmtx), NULL)) 84 # define my_mutex_destroy(pmtx) ((void) pthread_mutex_destroy ((pmtx))) 85 # define my_mutex_lock(pmtx) ((void) pthread_mutex_lock ((pmtx))) 86 # define my_mutex_unlock(pmtx) ((void) pthread_mutex_unlock ((pmtx))) 87 #else 88 # define WIN32_LEAN_AND_MEAN 1 89 # include <windows.h> 90 typedef CRITICAL_SECTION my_mutex_t; 91 # define my_mutex_init(pmtx) ((void) InitializeCriticalSection ((pmtx))) 92 # define my_mutex_destroy(pmtx) DeleteCriticalSection ((pmtx)) 93 # define my_mutex_lock(pmtx) EnterCriticalSection ((pmtx)) 94 # define my_mutex_unlock(pmtx) LeaveCriticalSection ((pmtx)) 95 #endif 96 97 #ifdef S_ISREG 98 # define my_S_ISREG(arg) S_ISREG (arg) 99 #else 100 # define my_S_ISREG(arg) (_S_IFREG == ((arg)&_S_IFMT)) 101 #endif 102 103 #if defined(MHD_CPU_COUNT) && (MHD_CPU_COUNT + 0) < 2 104 #undef MHD_CPU_COUNT 105 #endif 106 #if ! defined(MHD_CPU_COUNT) 107 #define MHD_CPU_COUNT 2 108 #endif 109 110 #ifndef PATH_MAX 111 # ifdef MAX_PATH 112 # define PATH_MAX MAX_PATH 113 # else 114 /* Some platforms (namely: GNU Hurd) do no define PATH_MAX. 115 As it is only example for MHD, just use reasonable value for PATH_MAX. */ 116 # define PATH_MAX 16384 117 # endif 118 #endif 119 120 /** 121 * Number of threads to run in the thread pool. Should (roughly) match 122 * the number of cores on your system. 123 */ 124 #define NUMBER_OF_THREADS MHD_CPU_COUNT 125 126 #ifdef MHD_HAVE_LIBMAGIC 127 /** 128 * How many bytes of a file do we give to libmagic to determine the mime type? 129 * 16k might be a bit excessive, but ought not hurt performance much anyway, 130 * and should definitively be on the safe side. 131 */ 132 #define MAGIC_HEADER_SIZE (16 * 1024) 133 #endif /* MHD_HAVE_LIBMAGIC */ 134 135 #if defined(_MSC_VER) && _MSC_VER < 1900 /* Before VS 2015 */ 136 /* _snprintf works differently, but the code handles it correctly */ 137 # define snprintf _snprintf 138 #endif 139 140 #if ! defined(_WIN32) || defined(__CYGWIN__) 141 # define my_mkstemp(tmpl) mkstemp ((tmpl)) 142 #else 143 static int 144 my_mkstemp (char *tmpl) 145 { 146 int i; 147 148 for (i = 0; i < 32; ++i) 149 { 150 char tmp_file_name[PATH_MAX + 1]; 151 HANDLE hndl; 152 if (0 == GetTempFileNameA (".", 153 tmpl, 154 0, 155 tmp_file_name)) 156 return -1; 157 hndl = CreateFileA (tmp_file_name, 158 GENERIC_READ | GENERIC_WRITE, 159 0, 160 NULL, 161 CREATE_ALWAYS, 162 FILE_FLAG_SEQUENTIAL_SCAN, 163 NULL); 164 if (INVALID_HANDLE_VALUE != hndl) 165 { 166 int fd; 167 fd = _open_osfhandle ((intptr_t) hndl, 168 _O_RDWR | _O_BINARY); 169 if (-1 != fd) 170 { 171 strcpy (tmpl, tmp_file_name); 172 return fd; /* Success */ 173 } 174 175 /* Failure */ 176 CloseHandle (hndl); 177 } 178 (void) DeleteFileA (tmp_file_name); 179 } 180 return -1; 181 } 182 183 184 #endif 185 186 #ifdef HAVE_STRDUP 187 # define my_strdup(str) strdup ((str)) 188 #else 189 static char * 190 my_strdup (const char *str) 191 { 192 size_t sz; 193 char *str_copy; 194 195 sz = strlen (str); 196 str_copy = (char *) malloc (sz); 197 if (NULL != str_copy) 198 memcpy (str_copy, str, sz); 199 return str_copy; 200 } 201 202 203 #endif 204 205 206 /** 207 * Page returned for file-not-found. 208 */ 209 #define FILE_NOT_FOUND_PAGE \ 210 "<html><head><title>File not found</title></head><body>File not found</body></html>" 211 212 213 /** 214 * Page returned for internal errors. 215 */ 216 #define INTERNAL_ERROR_PAGE \ 217 "<html><head><title>Internal error</title></head><body>Internal error</body></html>" 218 219 220 /** 221 * Page returned for refused requests. 222 */ 223 #define REQUEST_REFUSED_PAGE \ 224 "<html><head><title>Request refused</title></head><body>Request refused (file exists?)</body></html>" 225 226 227 /** 228 * Head of index page. 229 */ 230 #define INDEX_PAGE_HEADER \ 231 "<html>\n<head><title>Welcome</title></head>\n<body>\n" \ 232 "<h1>Upload</h1>\n" \ 233 "<form method=\"POST\" enctype=\"multipart/form-data\" action=\"/\">\n" \ 234 "<dl><dt>Content type:</dt><dd>" \ 235 "<input type=\"radio\" name=\"category\" value=\"books\">Book</input>" \ 236 "<input type=\"radio\" name=\"category\" value=\"images\">Image</input>" \ 237 "<input type=\"radio\" name=\"category\" value=\"music\">Music</input>" \ 238 "<input type=\"radio\" name=\"category\" value=\"software\">Software</input>" \ 239 "<input type=\"radio\" name=\"category\" value=\"videos\">Videos</input>\n" \ 240 "<input type=\"radio\" name=\"category\" value=\"other\" checked>Other</input></dd>" \ 241 "<dt>Language:</dt><dd>" \ 242 "<input type=\"radio\" name=\"language\" value=\"no-lang\" checked>none</input>" \ 243 "<input type=\"radio\" name=\"language\" value=\"en\">English</input>" \ 244 "<input type=\"radio\" name=\"language\" value=\"de\">German</input>" \ 245 "<input type=\"radio\" name=\"language\" value=\"fr\">French</input>" \ 246 "<input type=\"radio\" name=\"language\" value=\"es\">Spanish</input></dd>\n" \ 247 "<dt>File:</dt><dd>" \ 248 "<input type=\"file\" name=\"upload\"/></dd></dl>" \ 249 "<input type=\"submit\" value=\"Send!\"/>\n" \ 250 "</form>\n" \ 251 "<h1>Download</h1>\n" \ 252 "<ol>\n" 253 254 /** 255 * Footer of index page. 256 */ 257 #define INDEX_PAGE_FOOTER "</ol>\n</body>\n</html>" 258 259 260 /** 261 * NULL-terminated array of supported upload categories. Should match HTML 262 * in the form. 263 */ 264 static const char *const categories[] = { 265 "books", 266 "images", 267 "music", 268 "software", 269 "videos", 270 "other", 271 NULL, 272 }; 273 274 275 /** 276 * Specification of a supported language. 277 */ 278 struct Language 279 { 280 /** 281 * Directory name for the language. 282 */ 283 const char *dirname; 284 285 /** 286 * Long name for humans. 287 */ 288 const char *longname; 289 290 }; 291 292 /** 293 * NULL-terminated array of supported upload categories. Should match HTML 294 * in the form. 295 */ 296 static const struct Language languages[] = { 297 { "no-lang", "No language specified" }, 298 { "en", "English" }, 299 { "de", "German" }, 300 { "fr", "French" }, 301 { "es", "Spanish" }, 302 { NULL, NULL }, 303 }; 304 305 306 /** 307 * Response returned if the requested file does not exist (or is not accessible). 308 */ 309 static struct MHD_Response *file_not_found_response; 310 311 /** 312 * Response returned for internal errors. 313 */ 314 static struct MHD_Response *internal_error_response; 315 316 /** 317 * Response returned for '/' (GET) to list the contents of the directory and allow upload. 318 */ 319 static struct MHD_Response *cached_directory_response; 320 321 /** 322 * Response returned for refused uploads. 323 */ 324 static struct MHD_Response *request_refused_response; 325 326 /** 327 * Mutex used when we update the cached directory response object. 328 */ 329 static my_mutex_t mutex; 330 331 #ifdef MHD_HAVE_LIBMAGIC 332 /** 333 * Global handle to MAGIC data. 334 */ 335 static magic_t magic; 336 #endif /* MHD_HAVE_LIBMAGIC */ 337 338 339 /** 340 * Mark the given response as HTML for the browser. 341 * 342 * @param response response to mark 343 */ 344 static void 345 mark_as_html (struct MHD_Response *response) 346 { 347 if (NULL == response) 348 return; 349 (void) MHD_response_add_header (response, 350 MHD_HTTP_HEADER_CONTENT_TYPE, 351 "text/html"); 352 } 353 354 355 /** 356 * Replace the existing 'cached_directory_response' with the 357 * given response. 358 * 359 * @param response new directory response 360 */ 361 static void 362 update_cached_response (struct MHD_Response *response) 363 { 364 if (NULL != response) 365 { 366 if (MHD_SC_OK != 367 MHD_response_set_option (response, 368 &MHD_R_OPTION_REUSABLE ( 369 MHD_YES))) 370 exit (1); 371 } 372 my_mutex_lock (&mutex); 373 if (NULL != cached_directory_response) 374 MHD_response_destroy (cached_directory_response); 375 cached_directory_response = response; 376 my_mutex_unlock (&mutex); 377 } 378 379 380 /** 381 * Context keeping the data for the response we're building. 382 */ 383 struct ResponseDataContext 384 { 385 /** 386 * Response data string. 387 */ 388 char *buf; 389 390 /** 391 * Number of bytes allocated for 'buf'. 392 */ 393 size_t buf_size; 394 395 /** 396 * Current position where we append to 'buf'. Must be smaller or equal to 'buf_size'. 397 */ 398 size_t off; 399 400 }; 401 402 403 /** 404 * Create a listing of the files in 'dirname' in HTML. 405 * 406 * @param rdc where to store the list of files 407 * @param dirname name of the directory to list 408 * @return true on success, false on error 409 */ 410 static bool 411 list_directory (struct ResponseDataContext *rdc, 412 const char *dirname) 413 { 414 #if ! defined(_WIN32) || defined(__CYGWIN__) 415 char fullname[PATH_MAX + 1]; 416 struct stat sbuf; 417 DIR *dir; 418 struct dirent *de; 419 420 if (NULL == (dir = opendir (dirname))) 421 return false; 422 while (NULL != (de = readdir (dir))) 423 { 424 int res; 425 if ('.' == de->d_name[0]) 426 continue; 427 if (sizeof (fullname) <= (unsigned int) 428 snprintf (fullname, sizeof (fullname), 429 "%s/%s", 430 dirname, de->d_name)) 431 continue; /* ugh, file too long? how can this be!? */ 432 if (0 != stat (fullname, &sbuf)) 433 continue; /* ugh, failed to 'stat' */ 434 if (! S_ISREG (sbuf.st_mode)) 435 continue; /* not a regular file, skip */ 436 if (rdc->off + 1024 > rdc->buf_size) 437 { 438 void *r; 439 440 if ((2 * rdc->buf_size < rdc->buf_size) || 441 (2 * rdc->buf_size + 1024) < rdc->buf_size) 442 break; /* more than SIZE_T _index_ size? Too big for us */ 443 rdc->buf_size = 2 * rdc->buf_size + 1024; 444 if (NULL == (r = realloc (rdc->buf, rdc->buf_size))) 445 break; /* out of memory */ 446 rdc->buf = (char *) r; 447 } 448 res = snprintf (rdc->buf + rdc->off, 449 rdc->buf_size - rdc->off, 450 "<li><a href=\"/%s\">%s</a></li>\n", 451 fullname, 452 de->d_name); 453 if (0 >= res) 454 continue; /* snprintf() error */ 455 if (rdc->buf_size - rdc->off <= (size_t) res) 456 continue; /* buffer too small?? */ 457 rdc->off += (size_t) res; 458 } 459 (void) closedir (dir); 460 #else 461 char search_patt[PATH_MAX + 1]; 462 struct _finddatai64_t finfo; 463 intptr_t dir; 464 if (sizeof(search_patt) <= 465 (size_t) snprintf (search_patt, 466 sizeof(search_patt), 467 "%s\\*", 468 dirname)) 469 return false; 470 if (-1 == (dir = _findfirsti64 (search_patt, 471 &finfo))) 472 return false; 473 do 474 { 475 int res; 476 477 if (0 != (finfo.attrib & (_A_HIDDEN | _A_SUBDIR | _A_SYSTEM))) 478 continue; /* Not a regular file, skip */ 479 if (rdc->off + 1024 > rdc->buf_size) 480 { 481 void *r; 482 483 if ((2 * rdc->buf_size < rdc->buf_size) || 484 (2 * rdc->buf_size + 1024) < rdc->buf_size) 485 break; /* more than SIZE_T _index_ size? Too big for us */ 486 rdc->buf_size = 2 * rdc->buf_size + 1024; 487 if (NULL == (r = realloc (rdc->buf, rdc->buf_size))) 488 break; /* out of memory */ 489 rdc->buf = (char *) r; 490 } 491 res = snprintf (rdc->buf + rdc->off, 492 rdc->buf_size - rdc->off, 493 "<li><a href=\"/%s/%s\">%s</a></li>\n", 494 dirname, 495 finfo.name, 496 finfo.name); 497 if (0 >= res) 498 continue; /* snprintf() error */ 499 if (rdc->buf_size - rdc->off <= (size_t) res) 500 continue; /* buffer too small?? */ 501 rdc->off += (size_t) res; 502 503 } while (0 == _findnexti64 (dir, 504 &finfo)); 505 (void) _findclose (dir); 506 #endif 507 return true; 508 } 509 510 511 /** 512 * Re-scan our local directory and re-build the index. 513 */ 514 static void 515 update_directory (void) 516 { 517 static size_t initial_allocation = 32 * 1024; /* initial size for response buffer */ 518 struct MHD_Response *response; 519 struct ResponseDataContext rdc; 520 unsigned int language_idx; 521 unsigned int category_idx; 522 const struct Language *language; 523 const char *category; 524 char dir_name[128]; 525 struct stat sbuf; 526 int res; 527 size_t len; 528 529 rdc.off = 0; 530 rdc.buf_size = initial_allocation; 531 rdc.buf = (char *) malloc (rdc.buf_size); 532 if (NULL == rdc.buf) 533 { 534 update_cached_response (NULL); 535 return; 536 } 537 len = strlen (INDEX_PAGE_HEADER); 538 if (rdc.buf_size < len) 539 { /* buffer too small */ 540 free (rdc.buf); 541 update_cached_response (NULL); 542 return; 543 } 544 memcpy (rdc.buf + rdc.off, 545 INDEX_PAGE_HEADER, 546 len); 547 rdc.off += len; 548 for (language_idx = 0; NULL != languages[language_idx].dirname; 549 language_idx++) 550 { 551 language = &languages[language_idx]; 552 553 if (0 != stat (language->dirname, &sbuf)) 554 continue; /* empty */ 555 /* we ensured always +1k room, filenames are ~256 bytes, 556 so there is always still enough space for the header 557 without need for an additional reallocation check. */ 558 res = snprintf (rdc.buf + rdc.off, rdc.buf_size - rdc.off, 559 "<h2>%s</h2>\n", 560 language->longname); 561 if (0 >= res) 562 continue; /* snprintf() error */ 563 if (rdc.buf_size - rdc.off <= (size_t) res) 564 continue; /* buffer too small?? */ 565 rdc.off += (size_t) res; 566 for (category_idx = 0; NULL != categories[category_idx]; category_idx++) 567 { 568 category = categories[category_idx]; 569 res = snprintf (dir_name, sizeof (dir_name), 570 "%s/%s", 571 language->dirname, 572 category); 573 if ((0 >= res) || (sizeof (dir_name) <= (size_t) res)) 574 continue; /* cannot print dir name */ 575 if (0 != stat (dir_name, &sbuf)) 576 continue; /* empty */ 577 578 /* we ensured always +1k room, filenames are ~256 bytes, 579 so there is always still enough space for the header 580 without need for an additional reallocation check. */ 581 res = snprintf (rdc.buf + rdc.off, rdc.buf_size - rdc.off, 582 "<h3>%s</h3>\n", 583 category); 584 if (0 >= res) 585 continue; /* snprintf() error */ 586 if (rdc.buf_size - rdc.off <= (size_t) res) 587 continue; /* buffer too small?? */ 588 rdc.off += (size_t) res; 589 590 if (! list_directory (&rdc, 591 dir_name)) 592 { 593 free (rdc.buf); 594 update_cached_response (NULL); 595 return; 596 } 597 } 598 } 599 /* we ensured always +1k room, filenames are ~256 bytes, 600 so there is always still enough space for the footer 601 without need for a final reallocation check. */ 602 len = strlen (INDEX_PAGE_FOOTER); 603 if (rdc.buf_size - rdc.off < len) 604 { /* buffer too small */ 605 free (rdc.buf); 606 update_cached_response (NULL); 607 return; 608 } 609 memcpy (rdc.buf + rdc.off, INDEX_PAGE_FOOTER, len); 610 rdc.off += len; 611 initial_allocation = rdc.buf_size; /* remember for next time */ 612 response = 613 MHD_response_from_buffer (MHD_HTTP_STATUS_OK, 614 rdc.off, 615 rdc.buf, 616 &free, 617 rdc.buf); 618 mark_as_html (response); 619 #ifdef FORCE_CLOSE 620 (void) MHD_response_set_option (response, 621 &MHD_R_OPTION_CONN_CLOSE (MHD_YES)); 622 #endif 623 update_cached_response (response); 624 } 625 626 627 /** 628 * Context we keep for an upload. 629 */ 630 struct UploadContext 631 { 632 /** 633 * Handle where we write the uploaded file to. 634 */ 635 int fd; 636 637 /** 638 * Name of our temporary file where we initially read to. 639 */ 640 char tmpname[PATH_MAX + 1]; 641 642 /** 643 * Name of the file on disk. 644 */ 645 char *filename; 646 647 /** 648 * True once @a tmpfile exists. 649 */ 650 bool have_file; 651 652 /** 653 * True if we had an error handling the upload. 654 */ 655 bool error_file; 656 657 }; 658 659 660 /** 661 * "Stream" reader for POST data. 662 * This callback is called to incrementally process parsed POST data sent by 663 * the client. 664 * The pointers to the MHD_String and MHD_StringNullable are valid only until 665 * return from this callback. 666 * The pointers to the strings and the @a data are valid only until return from 667 * this callback. 668 * 669 * @param req the request 670 * @param cls user-specified closure 671 * @param name the name of the POST field 672 * @param filename the name of the uploaded file, @a cstr member is NULL if not 673 * known / not provided 674 * @param content_type the mime-type of the data, cstr member is NULL if not 675 * known / not provided 676 * @param encoding the encoding of the data, cstr member is NULL if not known / 677 * not provided 678 * @param size the number of bytes in @a data available, may be zero if 679 * the @a final_data is #MHD_YES 680 * @param data the pointer to @a size bytes of data at the specified 681 * @a off offset, NOT zero-terminated 682 * @param off the offset of @a data in the overall value, always equal to 683 * the sum of sizes of previous calls for the same field / file; 684 * client may provide more than one field with the same name and 685 * the same filename, the new filed (or file) is indicated by zero 686 * value of @a off (and the end is indicated by @a final_data) 687 * @param final_data if set to #MHD_YES then full field data is provided, 688 * if set to #MHD_NO then more field data may be provided 689 * @return action specifying how to proceed: 690 * #MHD_upload_action_continue() if all is well, 691 * #MHD_upload_action_suspend() to stop reading the upload until 692 * the request is resumed, 693 * #MHD_upload_action_abort_request() to close the socket, 694 * or a response to discard the rest of the upload and transmit 695 * the response 696 * @ingroup action 697 */ 698 static const struct MHD_UploadAction * 699 stream_reader (struct MHD_Request *req, 700 void *cls, 701 const struct MHD_String *name, 702 const struct MHD_StringNullable *filename, 703 const struct MHD_StringNullable *content_type, 704 const struct MHD_StringNullable *encoding, 705 size_t size, 706 const void *data, 707 uint_fast64_t off, 708 enum MHD_Bool final_data) 709 { 710 struct UploadContext *uc = (struct UploadContext *) cls; 711 712 (void) content_type; /* Unused. Silent compiler warning. */ 713 (void) encoding; /* Unused. Silent compiler warning. */ 714 (void) off; /* Unused. Silent compiler warning. */ 715 if ( (0 == strcmp (name->cstr, 716 "category")) || 717 (0 == strcmp (name->cstr, 718 "filename")) || 719 (0 == strcmp (name->cstr, 720 "language")) ) 721 { 722 return MHD_upload_action_from_response (req, 723 request_refused_response); 724 } 725 if (0 != strcmp (name->cstr, 726 "upload")) 727 { 728 fprintf (stderr, 729 "Ignoring unexpected form value `%s'\n", 730 name->cstr); 731 return MHD_upload_action_continue (req); 732 } 733 if (NULL == filename->cstr) 734 { 735 fprintf (stderr, 736 "No filename, aborting upload.\n"); 737 return MHD_upload_action_from_response (req, 738 request_refused_response); 739 } 740 if (-1 == uc->fd) 741 { 742 if (0 != filename->len) 743 { 744 if ( (NULL != strstr (filename->cstr, 745 "..")) || 746 (NULL != strchr (filename->cstr, 747 '/')) || 748 (NULL != strchr (filename->cstr, 749 '\\')) ) 750 { 751 return MHD_upload_action_from_response (req, 752 request_refused_response); 753 } 754 uc->filename = my_strdup (filename->cstr); 755 } 756 if (NULL == uc->filename) 757 { 758 fprintf (stderr, 759 "No filename for incremental upload\n"); 760 return MHD_upload_action_from_response (req, 761 internal_error_response); 762 } 763 764 { 765 size_t slen = strlen (uc->filename); 766 size_t i; 767 768 for (i = 0; i < slen; i++) 769 if (! isprint ((unsigned char) uc->filename[i])) 770 uc->filename[i] = '_'; 771 } 772 uc->fd = my_mkstemp (uc->tmpname); 773 if (-1 == uc->fd) 774 { 775 fprintf (stderr, 776 "Error creating temporary file `%s' for upload: %s\n", 777 uc->tmpname, 778 strerror (errno)); 779 return MHD_upload_action_from_response (req, 780 request_refused_response); 781 } 782 } 783 if ( (0 != size) && 784 #if ! defined(_WIN32) || defined(__CYGWIN__) 785 (size != 786 (size_t) write (uc->fd, 787 data, 788 size)) 789 #else /* Native W32 */ 790 (size != 791 (size_t) write (uc->fd, 792 data, 793 (unsigned int) size)) 794 #endif /* Native W32 */ 795 ) 796 { 797 /* write failed; likely: disk full */ 798 fprintf (stderr, 799 "Error writing to file `%s': %s\n", 800 uc->tmpname, 801 strerror (errno)); 802 (void) close (uc->fd); 803 uc->fd = -1; 804 unlink (uc->tmpname); 805 return MHD_upload_action_from_response (req, 806 internal_error_response); 807 } 808 if (final_data) 809 { 810 (void) close (uc->fd); 811 uc->fd = -1; 812 uc->have_file = true; 813 } 814 return MHD_upload_action_continue (req); 815 } 816 817 818 /** 819 * Iterator over POST data. 820 * 821 * The @a data pointer is valid only until return from this function. 822 * 823 * The pointers to the strings in @a data are valid until any MHD_UploadAction 824 * is provided. If the data is needed beyond this point, it should be copied. 825 * 826 * @param cls closure 827 * @param data the element of the post data, the pointer is valid only until 828 * return from this function 829 * @return #MHD_YES to continue iterating, 830 * #MHD_NO to abort the iteration 831 * @ingroup request 832 */ 833 static enum MHD_Bool 834 handle_full_upload (void *cls, 835 const struct MHD_PostField *data) 836 { 837 struct UploadContext *uc = (struct UploadContext *) cls; 838 int fd; 839 840 if (0 != strcmp ("upload", 841 data->name.cstr)) 842 return MHD_YES; 843 if (uc->have_file) 844 { 845 uc->error_file = true; 846 return MHD_NO; 847 } 848 if (NULL == data->filename.cstr) 849 { 850 fprintf (stderr, 851 "Filename missing for full upload\n"); 852 uc->error_file = true; 853 return MHD_NO; 854 } 855 uc->filename = my_strdup (data->filename.cstr); 856 fd = my_mkstemp (uc->tmpname); 857 if (-1 == fd) 858 { 859 fprintf (stderr, 860 "Error creating temporary file `%s' for upload: %s\n", 861 uc->tmpname, 862 strerror (errno)); 863 uc->error_file = true; 864 return MHD_NO; 865 } 866 if (NULL != data->value.cstr) 867 { 868 // FIXME: error handling, ... 869 #if ! defined(_WIN32) || defined(__CYGWIN__) 870 write (fd, 871 data->value.cstr, 872 data->value.len); 873 #else /* Native W32 */ 874 write (fd, 875 data->value.cstr, 876 (unsigned int) data->value.len); 877 #endif /* Native W32 */ 878 } 879 close (fd); 880 uc->have_file = true; 881 return MHD_NO; 882 } 883 884 885 /** 886 * The callback to be called when finished with processing 887 * of the postprocessor upload data. 888 * @param req the request 889 * @param cls the closure 890 * @param parsing_result the result of POST data parsing 891 * @return the action to proceed 892 */ 893 static const struct MHD_UploadAction * 894 done_cb (struct MHD_Request *req, 895 void *cls, 896 enum MHD_PostParseResult parsing_result) 897 { 898 struct UploadContext *uc = (struct UploadContext *) cls; 899 const struct MHD_UploadAction *ret; 900 struct MHD_StringNullable cat; 901 struct MHD_StringNullable lang; 902 char fn[PATH_MAX + 1]; 903 int res; 904 905 if (MHD_POST_PARSE_RES_OK != parsing_result) 906 { 907 fprintf (stderr, 908 "Upload parsing failed with status %d\n", 909 (int) parsing_result); 910 ret = MHD_upload_action_from_response (req, 911 request_refused_response); 912 goto cleanup; 913 } 914 if (-1 != uc->fd) 915 { 916 fprintf (stderr, 917 "Upload incomplete (fd still open)\n"); 918 (void) close (uc->fd); 919 if (NULL != uc->filename) 920 { 921 fprintf (stderr, 922 "Upload of file `%s' failed (incomplete or aborted), removing file.\n", 923 uc->filename); 924 } 925 (void) unlink (uc->tmpname); 926 ret = MHD_upload_action_from_response (req, 927 internal_error_response); 928 goto cleanup; 929 } 930 if (! MHD_request_get_value (req, 931 MHD_VK_POSTDATA, 932 "category", 933 &cat) || 934 ! MHD_request_get_value (req, 935 MHD_VK_POSTDATA, 936 "language", 937 &lang) || 938 (NULL == cat.cstr) || 939 (NULL == lang.cstr) ) 940 { 941 fprintf (stderr, 942 "Required argument missing\n"); 943 if (uc->have_file) 944 (void) unlink (uc->tmpname); 945 ret = MHD_upload_action_from_response (req, 946 request_refused_response); 947 goto cleanup; 948 } 949 /* FIXME: ugly that we may have to deal with upload 950 here as well! */ 951 MHD_request_get_post_data_cb (req, 952 &handle_full_upload, 953 uc); 954 if (uc->error_file) 955 { 956 fprintf (stderr, 957 "Upload data provided twice!\n"); 958 ret = MHD_upload_action_from_response (req, 959 internal_error_response); 960 goto cleanup; 961 } 962 if (NULL == uc->filename) 963 { 964 fprintf (stderr, 965 "Filename unavailable!?\n"); 966 ret = MHD_upload_action_from_response (req, 967 internal_error_response); 968 goto cleanup; 969 } 970 /* create directories -- if they don't exist already */ 971 #if ! defined(_WIN32) || defined(__CYGWIN__) 972 (void) mkdir (lang.cstr, 973 S_IRWXU); 974 #else 975 (void) mkdir (lang.cstr); 976 #endif 977 res = snprintf (fn, 978 sizeof (fn), 979 "%s/%s", 980 lang.cstr, 981 cat.cstr); 982 if ( (0 >= res) || 983 (sizeof (fn) <= (size_t) res) ) 984 { 985 fprintf (stderr, 986 "snprintf() failed at %d\n", __LINE__); 987 ret = MHD_upload_action_from_response (req, 988 request_refused_response); 989 goto cleanup; 990 } 991 #if ! defined(_WIN32) || defined(__CYGWIN__) 992 (void) mkdir (fn, 993 S_IRWXU); 994 #else 995 (void) mkdir (fn); 996 #endif 997 /* compute filename */ 998 res = snprintf (fn, 999 sizeof (fn), 1000 "%s/%s/%s", 1001 lang.cstr, 1002 cat.cstr, 1003 uc->filename); 1004 if ( (0 >= res) || 1005 (sizeof (fn) <= (size_t) res) ) 1006 { 1007 fprintf (stderr, 1008 "snprintf() failed at %d\n", __LINE__); 1009 ret = MHD_upload_action_from_response (req, 1010 request_refused_response); 1011 goto cleanup; 1012 } 1013 if (0 != 1014 rename (uc->tmpname, 1015 fn)) 1016 { 1017 fprintf (stderr, 1018 "Failed to rename %s to %s: %s\n", 1019 uc->tmpname, 1020 fn, 1021 strerror (errno)); 1022 ret = MHD_upload_action_from_response (req, 1023 request_refused_response); 1024 goto cleanup; 1025 } 1026 #if ! defined(_WIN32) || defined(__CYGWIN__) 1027 chmod (uc->filename, 1028 S_IRUSR | S_IWUSR); 1029 #endif 1030 1031 update_directory (); 1032 my_mutex_lock (&mutex); 1033 if (NULL == cached_directory_response) 1034 ret = MHD_upload_action_from_response (req, 1035 internal_error_response); 1036 else 1037 ret = MHD_upload_action_from_response (req, 1038 cached_directory_response); 1039 my_mutex_unlock (&mutex); 1040 cleanup: 1041 if (NULL != uc->filename) 1042 free (uc->filename); 1043 free (uc); 1044 return ret; 1045 } 1046 1047 1048 /** 1049 * Main callback from MHD, used to generate the page. 1050 * 1051 * @param cls NULL 1052 * @param request the request object 1053 * @param path the requested uri (without arguments after "?") 1054 * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, 1055 * #MHD_HTTP_METHOD_PUT, etc.) 1056 * @param upload_size the size of the message upload content payload, 1057 * #MHD_SIZE_UNKNOWN for chunked uploads (if the 1058 * final chunk has not been processed yet) 1059 * @return action how to proceed, NULL 1060 * if the request must be aborted due to a serious 1061 * error while handling the request (implies closure 1062 * of underling data stream, for HTTP/1.1 it means 1063 * socket closure). */ 1064 static const struct MHD_Action * 1065 generate_page (void *cls, 1066 struct MHD_Request *MHD_RESTRICT request, 1067 const struct MHD_String *MHD_RESTRICT path, 1068 enum MHD_HTTP_Method method, 1069 uint_fast64_t upload_size) 1070 { 1071 struct MHD_Response *response; 1072 const char *url = path->cstr; 1073 1074 (void ) cls; 1075 1076 if ((0 != upload_size) && 1077 ( (MHD_HTTP_METHOD_GET == method) || 1078 (MHD_HTTP_METHOD_HEAD == method) )) 1079 { 1080 /* Wrong request, refuse */ 1081 return MHD_action_from_response (request, 1082 request_refused_response); 1083 } 1084 1085 if ( ( (MHD_HTTP_METHOD_GET == method) || 1086 (MHD_HTTP_METHOD_HEAD == method) ) && 1087 (0 != strcmp (url, 1088 "/")) ) 1089 { 1090 /* should be file download */ 1091 #ifdef MHD_HAVE_LIBMAGIC 1092 char file_data[MAGIC_HEADER_SIZE]; 1093 ssize_t got; 1094 #endif /* MHD_HAVE_LIBMAGIC */ 1095 const char *mime; 1096 int fd; 1097 struct stat buf; 1098 1099 fd = -1; 1100 buf.st_size = 0; /* Mute compiler warning */ 1101 if ((0 != path->len) && 1102 (NULL == strstr (&url[1], 1103 "..")) && 1104 (0 != url[0]) && 1105 (0 != url[1]) && 1106 #ifdef _WIN32 1107 ('\\' != url[1]) && 1108 ((3 > path->len) || ':' != url[2]) && 1109 #endif 1110 ('/' != url[1]) ) 1111 fd = open (url + 1, 1112 O_RDONLY); 1113 if (-1 != fd) 1114 { 1115 if ((0 != fstat (fd, 1116 &buf)) 1117 || ! my_S_ISREG (buf.st_mode)) 1118 { 1119 (void) close (fd); 1120 fd = -1; 1121 } 1122 } 1123 if (-1 == fd) 1124 return MHD_action_from_response (request, 1125 file_not_found_response); 1126 #ifdef MHD_HAVE_LIBMAGIC 1127 /* read beginning of the file to determine mime type */ 1128 got = read (fd, 1129 file_data, 1130 sizeof (file_data)); 1131 (void) lseek (fd, 1132 0, 1133 SEEK_SET); 1134 if (0 < got) 1135 mime = magic_buffer (magic, 1136 file_data, 1137 (size_t) got); 1138 else 1139 #endif /* MHD_HAVE_LIBMAGIC */ 1140 mime = NULL; 1141 { 1142 /* Set mime-type by file-extension in some cases */ 1143 const char *ldot = strrchr (&url[1], 1144 '.'); 1145 1146 if (NULL != ldot) 1147 { 1148 if ((0 == strcmp (ldot, 1149 ".html")) 1150 || (0 == strcmp (ldot, 1151 ".htm")) 1152 || (0 == strcmp (ldot, 1153 ".HTML"))) 1154 mime = "text/html"; 1155 if (0 == strcmp (ldot, 1156 ".css")) 1157 mime = "text/css"; 1158 if (0 == strcmp (ldot, 1159 ".css3")) 1160 mime = "text/css"; 1161 if (0 == strcmp (ldot, 1162 ".js")) 1163 mime = "application/javascript"; 1164 } 1165 } 1166 1167 response = MHD_response_from_fd (MHD_HTTP_STATUS_OK, 1168 fd, 1169 0LLU /* offset */, 1170 (uint_fast64_t) buf.st_size); 1171 if (NULL == response) 1172 { 1173 /* internal error (i.e. out of memory) */ 1174 (void) close (fd); 1175 return MHD_action_abort_request (request); 1176 } 1177 1178 /* add mime type if we had one */ 1179 if (NULL != mime) 1180 (void) MHD_response_add_header (response, 1181 MHD_HTTP_HEADER_CONTENT_TYPE, 1182 mime); 1183 return MHD_action_from_response (request, 1184 response); 1185 } 1186 1187 if ( (MHD_HTTP_METHOD_POST == method) && 1188 (0 == strcmp (path->cstr, 1189 "/")) ) 1190 { 1191 struct UploadContext *uc; 1192 1193 uc = (struct UploadContext *) malloc (sizeof (struct UploadContext)); 1194 if (NULL == uc) 1195 return MHD_action_abort_request (request); /* out of memory, close connection */ 1196 memset (uc, 1197 0, 1198 sizeof (struct UploadContext)); 1199 strcpy (uc->tmpname, 1200 "mhd-demo-XXXXXX"); 1201 uc->fd = -1; 1202 return MHD_action_parse_post (request, 1203 64 * 1024 /* buffer size */, 1204 1024 /* max non-stream size */, 1205 MHD_HTTP_POST_ENCODING_OTHER, 1206 &stream_reader, 1207 uc, 1208 &done_cb, 1209 uc); 1210 } 1211 if ( ( (MHD_HTTP_METHOD_GET == method) || 1212 (MHD_HTTP_METHOD_HEAD == method) ) && 1213 (0 == strcmp (url, 1214 "/")) ) 1215 { 1216 const struct MHD_Action *ret; 1217 1218 my_mutex_lock (&mutex); 1219 if (NULL == cached_directory_response) 1220 ret = MHD_action_from_response (request, 1221 internal_error_response); 1222 else 1223 ret = MHD_action_from_response (request, 1224 cached_directory_response); 1225 my_mutex_unlock (&mutex); 1226 return ret; 1227 } 1228 /* unexpected request, refuse */ 1229 return MHD_action_from_response (request, 1230 request_refused_response); 1231 } 1232 1233 1234 #if ! defined(_WIN32) || defined(__CYGWIN__) 1235 /** 1236 * Function called if we get a SIGPIPE. Does nothing. 1237 * 1238 * @param sig will be SIGPIPE (ignored) 1239 */ 1240 static void 1241 catcher (int sig) 1242 { 1243 (void) sig; /* Unused. Silent compiler warning. */ 1244 /* do nothing */ 1245 } 1246 1247 1248 /** 1249 * setup handlers to ignore SIGPIPE. 1250 */ 1251 static void 1252 ignore_sigpipe (void) 1253 { 1254 struct sigaction oldsig; 1255 struct sigaction sig; 1256 1257 sig.sa_handler = &catcher; 1258 sigemptyset (&sig.sa_mask); 1259 #ifdef SA_INTERRUPT 1260 sig.sa_flags = SA_INTERRUPT; /* SunOS */ 1261 #else 1262 sig.sa_flags = SA_RESTART; 1263 #endif 1264 if (0 != sigaction (SIGPIPE, 1265 &sig, 1266 &oldsig)) 1267 fprintf (stderr, 1268 "Failed to install SIGPIPE handler: %s\n", strerror (errno)); 1269 } 1270 1271 1272 #endif 1273 1274 #if defined(_WIN32) && ! defined(__CYGWIN__) 1275 static void 1276 set_utf8_locale (void) 1277 { 1278 if (NULL != setlocale (LC_CTYPE, ".UTF8")) 1279 return; /* Success */ 1280 (void) setlocale (LC_CTYPE, "C"); /* A fallback, not really helpful */ 1281 } 1282 1283 1284 #endif 1285 1286 /** 1287 * Entry point to demo. Note: this HTTP server will make all 1288 * files in the current directory and its subdirectories available 1289 * to anyone. Press ENTER to stop the server once it has started. 1290 * 1291 * @param argc number of arguments in argv 1292 * @param argv first and only argument should be the port number 1293 * @return 0 on success 1294 */ 1295 int 1296 main (int argc, 1297 char *const *argv) 1298 { 1299 struct MHD_Daemon *d; 1300 unsigned int port; 1301 char dummy; 1302 1303 if ( (argc != 2) || 1304 (1 != sscanf (argv[1], 1305 "%u%c", 1306 &port, 1307 &dummy)) || 1308 (UINT16_MAX < port) ) 1309 { 1310 if (2 == argc) 1311 { 1312 fprintf (stderr, 1313 "Usage: %s PORT\n", 1314 argv[0]); 1315 return 1; 1316 } 1317 port = 8080; 1318 } 1319 #if ! defined(_WIN32) || defined(__CYGWIN__) 1320 ignore_sigpipe (); 1321 #endif 1322 #if defined(_WIN32) && ! defined(__CYGWIN__) 1323 set_utf8_locale (); 1324 #endif 1325 #ifdef MHD_HAVE_LIBMAGIC 1326 magic = magic_open (MAGIC_MIME_TYPE); 1327 (void) magic_load (magic, NULL); 1328 #endif /* MHD_HAVE_LIBMAGIC */ 1329 1330 my_mutex_init (&mutex); 1331 file_not_found_response = 1332 MHD_response_from_buffer_static ( 1333 MHD_HTTP_STATUS_NOT_FOUND, 1334 strlen (FILE_NOT_FOUND_PAGE), 1335 FILE_NOT_FOUND_PAGE); 1336 mark_as_html (file_not_found_response); 1337 if (MHD_SC_OK != 1338 MHD_response_set_option (file_not_found_response, 1339 &MHD_R_OPTION_REUSABLE (MHD_YES))) 1340 return 1; 1341 request_refused_response = 1342 MHD_response_from_buffer_static ( 1343 MHD_HTTP_STATUS_FORBIDDEN, 1344 strlen (REQUEST_REFUSED_PAGE), 1345 REQUEST_REFUSED_PAGE); 1346 mark_as_html (request_refused_response); 1347 if (MHD_SC_OK != 1348 MHD_response_set_option (request_refused_response, 1349 &MHD_R_OPTION_REUSABLE (MHD_YES))) 1350 return 1; 1351 internal_error_response = 1352 MHD_response_from_buffer_static ( 1353 MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR, 1354 strlen (INTERNAL_ERROR_PAGE), 1355 INTERNAL_ERROR_PAGE); 1356 mark_as_html (internal_error_response); 1357 if (MHD_SC_OK != 1358 MHD_response_set_option (internal_error_response, 1359 &MHD_R_OPTION_REUSABLE (MHD_YES))) 1360 return 1; 1361 update_directory (); 1362 d = MHD_daemon_create (&generate_page, 1363 NULL); 1364 if (NULL == d) 1365 return 1; 1366 if (MHD_SC_OK != 1367 MHD_DAEMON_SET_OPTIONS ( 1368 d, 1369 MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_AUTO), 1370 MHD_D_OPTION_WM_WORKER_THREADS (NUMBER_OF_THREADS), 1371 MHD_D_OPTION_DEFAULT_TIMEOUT (120 /* seconds */), 1372 MHD_D_OPTION_CONN_MEMORY_LIMIT (256 * 1024), 1373 MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO, 1374 (uint_least16_t) port))) 1375 return 1; 1376 #ifdef PRODUCTION 1377 if (MHD_SC_OK != 1378 MHD_DAEMON_SET_OPTIONS ( 1379 d, 1380 MHD_D_OPTION_PER_IP_LIMIT (64)) 1381 return 1; 1382 #endif 1383 if (MHD_SC_OK != 1384 MHD_daemon_start (d)) 1385 { 1386 MHD_daemon_destroy (d); 1387 return 1; 1388 } 1389 fprintf (stderr, 1390 "HTTP server running on port %u. Press ENTER to stop the server.\n", 1391 port); 1392 (void) getc (stdin); 1393 MHD_daemon_destroy (d); 1394 MHD_response_destroy (file_not_found_response); 1395 MHD_response_destroy (request_refused_response); 1396 MHD_response_destroy (internal_error_response); 1397 update_cached_response (NULL); 1398 my_mutex_destroy (&mutex); 1399 #ifdef MHD_HAVE_LIBMAGIC 1400 magic_close (magic); 1401 #endif /* MHD_HAVE_LIBMAGIC */ 1402 return 0; 1403 } 1404 1405 1406 /* end of demo.c */