diff options
author | Christian Grothoff <christian@grothoff.org> | 2015-04-12 19:52:29 +0000 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2015-04-12 19:52:29 +0000 |
commit | 10a27715a5a01248f97d7853e37f1beb1f4a92db (patch) | |
tree | 57b109e91fc0798239f9a0a39b20a50ecd2506a1 /src/examples/demo_https.c | |
parent | 4d6fb01f8e229ece8a41fb2f4a94eb61b9e38e02 (diff) | |
download | libmicrohttpd-10a27715a5a01248f97d7853e37f1beb1f4a92db.tar.gz libmicrohttpd-10a27715a5a01248f97d7853e37f1beb1f4a92db.zip |
Adding "testcase" (demo_https) and a fix. -CG
Hi,
I am doing test with HTTPS at low bit rate for large files using:
wget -v --no-check-certificate --limit-rate=1000 https://....
When the MHD daemon is configured with MHD_USE_EPOLL_INTERNALLY_LINUX_ONLY, I noticed that the thread takes 100% CPU, whereas
the MHD_USE_POLL_INTERNALLY configuration show a normal CPU usage.
Adding logs I see that the busy loops takes place at daemon.c, line 2605 (0.9.38):
daemon->eready_tail never gets NULL most probably due to connection.c, line 2671 to 2721
Thanks,
Louis
Diffstat (limited to 'src/examples/demo_https.c')
-rw-r--r-- | src/examples/demo_https.c | 960 |
1 files changed, 960 insertions, 0 deletions
diff --git a/src/examples/demo_https.c b/src/examples/demo_https.c new file mode 100644 index 00000000..f34a715c --- /dev/null +++ b/src/examples/demo_https.c | |||
@@ -0,0 +1,960 @@ | |||
1 | /* | ||
2 | This file is part of libmicrohttpd | ||
3 | Copyright (C) 2013 Christian Grothoff (and other contributing authors) | ||
4 | |||
5 | This library is free software; you can redistribute it and/or | ||
6 | modify it under the terms of the GNU Lesser General Public | ||
7 | License as published by the Free Software Foundation; either | ||
8 | version 2.1 of the License, or (at your option) any later version. | ||
9 | |||
10 | This library is distributed in the hope that it will be useful, | ||
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | Lesser General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Lesser General Public | ||
16 | License along with this library; if not, write to the Free Software | ||
17 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
18 | */ | ||
19 | |||
20 | /** | ||
21 | * @file demo_https.c | ||
22 | * @brief complex demonstration site: create directory index, offer | ||
23 | * upload via form and HTTP POST, download with mime type detection | ||
24 | * and error reporting (403, etc.) --- and all of this with | ||
25 | * high-performance settings (large buffers, thread pool). | ||
26 | * If you want to benchmark MHD, this code should be used to | ||
27 | * run tests against. Note that the number of threads may need | ||
28 | * to be adjusted depending on the number of available cores. | ||
29 | * Logic is identical to demo.c, just adds HTTPS support. | ||
30 | * @author Christian Grothoff | ||
31 | */ | ||
32 | #include "platform.h" | ||
33 | #include <microhttpd.h> | ||
34 | #include <unistd.h> | ||
35 | #include <pthread.h> | ||
36 | #include <sys/types.h> | ||
37 | #include <sys/stat.h> | ||
38 | #include <dirent.h> | ||
39 | #include <magic.h> | ||
40 | #include <limits.h> | ||
41 | #include <ctype.h> | ||
42 | |||
43 | #if defined(CPU_COUNT) && (CPU_COUNT+0) < 2 | ||
44 | #undef CPU_COUNT | ||
45 | #endif | ||
46 | #if !defined(CPU_COUNT) | ||
47 | #define CPU_COUNT 2 | ||
48 | #endif | ||
49 | |||
50 | /** | ||
51 | * Number of threads to run in the thread pool. Should (roughly) match | ||
52 | * the number of cores on your system. | ||
53 | */ | ||
54 | #define NUMBER_OF_THREADS CPU_COUNT | ||
55 | |||
56 | /** | ||
57 | * How many bytes of a file do we give to libmagic to determine the mime type? | ||
58 | * 16k might be a bit excessive, but ought not hurt performance much anyway, | ||
59 | * and should definitively be on the safe side. | ||
60 | */ | ||
61 | #define MAGIC_HEADER_SIZE (16 * 1024) | ||
62 | |||
63 | |||
64 | /** | ||
65 | * Page returned for file-not-found. | ||
66 | */ | ||
67 | #define FILE_NOT_FOUND_PAGE "<html><head><title>File not found</title></head><body>File not found</body></html>" | ||
68 | |||
69 | |||
70 | /** | ||
71 | * Page returned for internal errors. | ||
72 | */ | ||
73 | #define INTERNAL_ERROR_PAGE "<html><head><title>Internal error</title></head><body>Internal error</body></html>" | ||
74 | |||
75 | |||
76 | /** | ||
77 | * Page returned for refused requests. | ||
78 | */ | ||
79 | #define REQUEST_REFUSED_PAGE "<html><head><title>Request refused</title></head><body>Request refused (file exists?)</body></html>" | ||
80 | |||
81 | |||
82 | /** | ||
83 | * Head of index page. | ||
84 | */ | ||
85 | #define INDEX_PAGE_HEADER "<html>\n<head><title>Welcome</title></head>\n<body>\n"\ | ||
86 | "<h1>Upload</h1>\n"\ | ||
87 | "<form method=\"POST\" enctype=\"multipart/form-data\" action=\"/\">\n"\ | ||
88 | "<dl><dt>Content type:</dt><dd>"\ | ||
89 | "<input type=\"radio\" name=\"category\" value=\"books\">Book</input>"\ | ||
90 | "<input type=\"radio\" name=\"category\" value=\"images\">Image</input>"\ | ||
91 | "<input type=\"radio\" name=\"category\" value=\"music\">Music</input>"\ | ||
92 | "<input type=\"radio\" name=\"category\" value=\"software\">Software</input>"\ | ||
93 | "<input type=\"radio\" name=\"category\" value=\"videos\">Videos</input>\n"\ | ||
94 | "<input type=\"radio\" name=\"category\" value=\"other\" checked>Other</input></dd>"\ | ||
95 | "<dt>Language:</dt><dd>"\ | ||
96 | "<input type=\"radio\" name=\"language\" value=\"no-lang\" checked>none</input>"\ | ||
97 | "<input type=\"radio\" name=\"language\" value=\"en\">English</input>"\ | ||
98 | "<input type=\"radio\" name=\"language\" value=\"de\">German</input>"\ | ||
99 | "<input type=\"radio\" name=\"language\" value=\"fr\">French</input>"\ | ||
100 | "<input type=\"radio\" name=\"language\" value=\"es\">Spanish</input></dd>\n"\ | ||
101 | "<dt>File:</dt><dd>"\ | ||
102 | "<input type=\"file\" name=\"upload\"/></dd></dl>"\ | ||
103 | "<input type=\"submit\" value=\"Send!\"/>\n"\ | ||
104 | "</form>\n"\ | ||
105 | "<h1>Download</h1>\n"\ | ||
106 | "<ol>\n" | ||
107 | |||
108 | /** | ||
109 | * Footer of index page. | ||
110 | */ | ||
111 | #define INDEX_PAGE_FOOTER "</ol>\n</body>\n</html>" | ||
112 | |||
113 | |||
114 | /** | ||
115 | * NULL-terminated array of supported upload categories. Should match HTML | ||
116 | * in the form. | ||
117 | */ | ||
118 | static const char * const categories[] = | ||
119 | { | ||
120 | "books", | ||
121 | "images", | ||
122 | "music", | ||
123 | "software", | ||
124 | "videos", | ||
125 | "other", | ||
126 | NULL, | ||
127 | }; | ||
128 | |||
129 | |||
130 | /** | ||
131 | * Specification of a supported language. | ||
132 | */ | ||
133 | struct Language | ||
134 | { | ||
135 | /** | ||
136 | * Directory name for the language. | ||
137 | */ | ||
138 | const char *dirname; | ||
139 | |||
140 | /** | ||
141 | * Long name for humans. | ||
142 | */ | ||
143 | const char *longname; | ||
144 | |||
145 | }; | ||
146 | |||
147 | /** | ||
148 | * NULL-terminated array of supported upload categories. Should match HTML | ||
149 | * in the form. | ||
150 | */ | ||
151 | static const struct Language languages[] = | ||
152 | { | ||
153 | { "no-lang", "No language specified" }, | ||
154 | { "en", "English" }, | ||
155 | { "de", "German" }, | ||
156 | { "fr", "French" }, | ||
157 | { "es", "Spanish" }, | ||
158 | { NULL, NULL }, | ||
159 | }; | ||
160 | |||
161 | |||
162 | /** | ||
163 | * Response returned if the requested file does not exist (or is not accessible). | ||
164 | */ | ||
165 | static struct MHD_Response *file_not_found_response; | ||
166 | |||
167 | /** | ||
168 | * Response returned for internal errors. | ||
169 | */ | ||
170 | static struct MHD_Response *internal_error_response; | ||
171 | |||
172 | /** | ||
173 | * Response returned for '/' (GET) to list the contents of the directory and allow upload. | ||
174 | */ | ||
175 | static struct MHD_Response *cached_directory_response; | ||
176 | |||
177 | /** | ||
178 | * Response returned for refused uploads. | ||
179 | */ | ||
180 | static struct MHD_Response *request_refused_response; | ||
181 | |||
182 | /** | ||
183 | * Mutex used when we update the cached directory response object. | ||
184 | */ | ||
185 | static pthread_mutex_t mutex; | ||
186 | |||
187 | /** | ||
188 | * Global handle to MAGIC data. | ||
189 | */ | ||
190 | static magic_t magic; | ||
191 | |||
192 | |||
193 | /** | ||
194 | * Mark the given response as HTML for the brower. | ||
195 | * | ||
196 | * @param response response to mark | ||
197 | */ | ||
198 | static void | ||
199 | mark_as_html (struct MHD_Response *response) | ||
200 | { | ||
201 | (void) MHD_add_response_header (response, | ||
202 | MHD_HTTP_HEADER_CONTENT_TYPE, | ||
203 | "text/html"); | ||
204 | } | ||
205 | |||
206 | |||
207 | /** | ||
208 | * Replace the existing 'cached_directory_response' with the | ||
209 | * given response. | ||
210 | * | ||
211 | * @param response new directory response | ||
212 | */ | ||
213 | static void | ||
214 | update_cached_response (struct MHD_Response *response) | ||
215 | { | ||
216 | (void) pthread_mutex_lock (&mutex); | ||
217 | if (NULL != cached_directory_response) | ||
218 | MHD_destroy_response (cached_directory_response); | ||
219 | cached_directory_response = response; | ||
220 | (void) pthread_mutex_unlock (&mutex); | ||
221 | } | ||
222 | |||
223 | |||
224 | /** | ||
225 | * Context keeping the data for the response we're building. | ||
226 | */ | ||
227 | struct ResponseDataContext | ||
228 | { | ||
229 | /** | ||
230 | * Response data string. | ||
231 | */ | ||
232 | char *buf; | ||
233 | |||
234 | /** | ||
235 | * Number of bytes allocated for 'buf'. | ||
236 | */ | ||
237 | size_t buf_len; | ||
238 | |||
239 | /** | ||
240 | * Current position where we append to 'buf'. Must be smaller or equal to 'buf_len'. | ||
241 | */ | ||
242 | size_t off; | ||
243 | |||
244 | }; | ||
245 | |||
246 | |||
247 | /** | ||
248 | * Create a listing of the files in 'dirname' in HTML. | ||
249 | * | ||
250 | * @param rdc where to store the list of files | ||
251 | * @param dirname name of the directory to list | ||
252 | * @return MHD_YES on success, MHD_NO on error | ||
253 | */ | ||
254 | static int | ||
255 | list_directory (struct ResponseDataContext *rdc, | ||
256 | const char *dirname) | ||
257 | { | ||
258 | char fullname[PATH_MAX]; | ||
259 | struct stat sbuf; | ||
260 | DIR *dir; | ||
261 | struct dirent *de; | ||
262 | |||
263 | if (NULL == (dir = opendir (dirname))) | ||
264 | return MHD_NO; | ||
265 | while (NULL != (de = readdir (dir))) | ||
266 | { | ||
267 | if ('.' == de->d_name[0]) | ||
268 | continue; | ||
269 | if (sizeof (fullname) <= (size_t) | ||
270 | snprintf (fullname, sizeof (fullname), | ||
271 | "%s/%s", | ||
272 | dirname, de->d_name)) | ||
273 | continue; /* ugh, file too long? how can this be!? */ | ||
274 | if (0 != stat (fullname, &sbuf)) | ||
275 | continue; /* ugh, failed to 'stat' */ | ||
276 | if (! S_ISREG (sbuf.st_mode)) | ||
277 | continue; /* not a regular file, skip */ | ||
278 | if (rdc->off + 1024 > rdc->buf_len) | ||
279 | { | ||
280 | void *r; | ||
281 | |||
282 | if ( (2 * rdc->buf_len + 1024) < rdc->buf_len) | ||
283 | break; /* more than SIZE_T _index_ size? Too big for us */ | ||
284 | rdc->buf_len = 2 * rdc->buf_len + 1024; | ||
285 | if (NULL == (r = realloc (rdc->buf, rdc->buf_len))) | ||
286 | break; /* out of memory */ | ||
287 | rdc->buf = r; | ||
288 | } | ||
289 | rdc->off += snprintf (&rdc->buf[rdc->off], | ||
290 | rdc->buf_len - rdc->off, | ||
291 | "<li><a href=\"/%s\">%s</a></li>\n", | ||
292 | fullname, | ||
293 | de->d_name); | ||
294 | } | ||
295 | (void) closedir (dir); | ||
296 | return MHD_YES; | ||
297 | } | ||
298 | |||
299 | |||
300 | /** | ||
301 | * Re-scan our local directory and re-build the index. | ||
302 | */ | ||
303 | static void | ||
304 | update_directory () | ||
305 | { | ||
306 | static size_t initial_allocation = 32 * 1024; /* initial size for response buffer */ | ||
307 | struct MHD_Response *response; | ||
308 | struct ResponseDataContext rdc; | ||
309 | unsigned int language_idx; | ||
310 | unsigned int category_idx; | ||
311 | const struct Language *language; | ||
312 | const char *category; | ||
313 | char dir_name[128]; | ||
314 | struct stat sbuf; | ||
315 | |||
316 | rdc.buf_len = initial_allocation; | ||
317 | if (NULL == (rdc.buf = malloc (rdc.buf_len))) | ||
318 | { | ||
319 | update_cached_response (NULL); | ||
320 | return; | ||
321 | } | ||
322 | rdc.off = snprintf (rdc.buf, rdc.buf_len, | ||
323 | "%s", | ||
324 | INDEX_PAGE_HEADER); | ||
325 | for (language_idx = 0; NULL != languages[language_idx].dirname; language_idx++) | ||
326 | { | ||
327 | language = &languages[language_idx]; | ||
328 | |||
329 | if (0 != stat (language->dirname, &sbuf)) | ||
330 | continue; /* empty */ | ||
331 | /* we ensured always +1k room, filenames are ~256 bytes, | ||
332 | so there is always still enough space for the header | ||
333 | without need for an additional reallocation check. */ | ||
334 | rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off, | ||
335 | "<h2>%s</h2>\n", | ||
336 | language->longname); | ||
337 | for (category_idx = 0; NULL != categories[category_idx]; category_idx++) | ||
338 | { | ||
339 | category = categories[category_idx]; | ||
340 | snprintf (dir_name, sizeof (dir_name), | ||
341 | "%s/%s", | ||
342 | language->dirname, | ||
343 | category); | ||
344 | if (0 != stat (dir_name, &sbuf)) | ||
345 | continue; /* empty */ | ||
346 | |||
347 | /* we ensured always +1k room, filenames are ~256 bytes, | ||
348 | so there is always still enough space for the header | ||
349 | without need for an additional reallocation check. */ | ||
350 | rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off, | ||
351 | "<h3>%s</h3>\n", | ||
352 | category); | ||
353 | |||
354 | if (MHD_NO == list_directory (&rdc, dir_name)) | ||
355 | { | ||
356 | free (rdc.buf); | ||
357 | update_cached_response (NULL); | ||
358 | return; | ||
359 | } | ||
360 | } | ||
361 | } | ||
362 | /* we ensured always +1k room, filenames are ~256 bytes, | ||
363 | so there is always still enough space for the footer | ||
364 | without need for a final reallocation check. */ | ||
365 | rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off, | ||
366 | "%s", | ||
367 | INDEX_PAGE_FOOTER); | ||
368 | initial_allocation = rdc.buf_len; /* remember for next time */ | ||
369 | response = MHD_create_response_from_buffer (rdc.off, | ||
370 | rdc.buf, | ||
371 | MHD_RESPMEM_MUST_FREE); | ||
372 | mark_as_html (response); | ||
373 | #if FORCE_CLOSE | ||
374 | (void) MHD_add_response_header (response, | ||
375 | MHD_HTTP_HEADER_CONNECTION, | ||
376 | "close"); | ||
377 | #endif | ||
378 | update_cached_response (response); | ||
379 | } | ||
380 | |||
381 | |||
382 | /** | ||
383 | * Context we keep for an upload. | ||
384 | */ | ||
385 | struct UploadContext | ||
386 | { | ||
387 | /** | ||
388 | * Handle where we write the uploaded file to. | ||
389 | */ | ||
390 | int fd; | ||
391 | |||
392 | /** | ||
393 | * Name of the file on disk (used to remove on errors). | ||
394 | */ | ||
395 | char *filename; | ||
396 | |||
397 | /** | ||
398 | * Language for the upload. | ||
399 | */ | ||
400 | char *language; | ||
401 | |||
402 | /** | ||
403 | * Category for the upload. | ||
404 | */ | ||
405 | char *category; | ||
406 | |||
407 | /** | ||
408 | * Post processor we're using to process the upload. | ||
409 | */ | ||
410 | struct MHD_PostProcessor *pp; | ||
411 | |||
412 | /** | ||
413 | * Handle to connection that we're processing the upload for. | ||
414 | */ | ||
415 | struct MHD_Connection *connection; | ||
416 | |||
417 | /** | ||
418 | * Response to generate, NULL to use directory. | ||
419 | */ | ||
420 | struct MHD_Response *response; | ||
421 | }; | ||
422 | |||
423 | |||
424 | /** | ||
425 | * Append the 'size' bytes from 'data' to '*ret', adding | ||
426 | * 0-termination. If '*ret' is NULL, allocate an empty string first. | ||
427 | * | ||
428 | * @param ret string to update, NULL or 0-terminated | ||
429 | * @param data data to append | ||
430 | * @param size number of bytes in 'data' | ||
431 | * @return MHD_NO on allocation failure, MHD_YES on success | ||
432 | */ | ||
433 | static int | ||
434 | do_append (char **ret, | ||
435 | const char *data, | ||
436 | size_t size) | ||
437 | { | ||
438 | char *buf; | ||
439 | size_t old_len; | ||
440 | |||
441 | if (NULL == *ret) | ||
442 | old_len = 0; | ||
443 | else | ||
444 | old_len = strlen (*ret); | ||
445 | buf = malloc (old_len + size + 1); | ||
446 | if (NULL == buf) | ||
447 | return MHD_NO; | ||
448 | memcpy (buf, *ret, old_len); | ||
449 | if (NULL != *ret) | ||
450 | free (*ret); | ||
451 | memcpy (&buf[old_len], data, size); | ||
452 | buf[old_len + size] = '\0'; | ||
453 | *ret = buf; | ||
454 | return MHD_YES; | ||
455 | } | ||
456 | |||
457 | |||
458 | /** | ||
459 | * Iterator over key-value pairs where the value | ||
460 | * maybe made available in increments and/or may | ||
461 | * not be zero-terminated. Used for processing | ||
462 | * POST data. | ||
463 | * | ||
464 | * @param cls user-specified closure | ||
465 | * @param kind type of the value, always MHD_POSTDATA_KIND when called from MHD | ||
466 | * @param key 0-terminated key for the value | ||
467 | * @param filename name of the uploaded file, NULL if not known | ||
468 | * @param content_type mime-type of the data, NULL if not known | ||
469 | * @param transfer_encoding encoding of the data, NULL if not known | ||
470 | * @param data pointer to size bytes of data at the | ||
471 | * specified offset | ||
472 | * @param off offset of data in the overall value | ||
473 | * @param size number of bytes in data available | ||
474 | * @return MHD_YES to continue iterating, | ||
475 | * MHD_NO to abort the iteration | ||
476 | */ | ||
477 | static int | ||
478 | process_upload_data (void *cls, | ||
479 | enum MHD_ValueKind kind, | ||
480 | const char *key, | ||
481 | const char *filename, | ||
482 | const char *content_type, | ||
483 | const char *transfer_encoding, | ||
484 | const char *data, | ||
485 | uint64_t off, | ||
486 | size_t size) | ||
487 | { | ||
488 | struct UploadContext *uc = cls; | ||
489 | int i; | ||
490 | |||
491 | if (0 == strcmp (key, "category")) | ||
492 | return do_append (&uc->category, data, size); | ||
493 | if (0 == strcmp (key, "language")) | ||
494 | return do_append (&uc->language, data, size); | ||
495 | if (0 != strcmp (key, "upload")) | ||
496 | { | ||
497 | fprintf (stderr, | ||
498 | "Ignoring unexpected form value `%s'\n", | ||
499 | key); | ||
500 | return MHD_YES; /* ignore */ | ||
501 | } | ||
502 | if (NULL == filename) | ||
503 | { | ||
504 | fprintf (stderr, "No filename, aborting upload\n"); | ||
505 | return MHD_NO; /* no filename, error */ | ||
506 | } | ||
507 | if ( (NULL == uc->category) || | ||
508 | (NULL == uc->language) ) | ||
509 | { | ||
510 | fprintf (stderr, | ||
511 | "Missing form data for upload `%s'\n", | ||
512 | filename); | ||
513 | uc->response = request_refused_response; | ||
514 | return MHD_NO; | ||
515 | } | ||
516 | if (-1 == uc->fd) | ||
517 | { | ||
518 | char fn[PATH_MAX]; | ||
519 | |||
520 | if ( (NULL != strstr (filename, "..")) || | ||
521 | (NULL != strchr (filename, '/')) || | ||
522 | (NULL != strchr (filename, '\\')) ) | ||
523 | { | ||
524 | uc->response = request_refused_response; | ||
525 | return MHD_NO; | ||
526 | } | ||
527 | /* create directories -- if they don't exist already */ | ||
528 | #ifdef WINDOWS | ||
529 | (void) mkdir (uc->language); | ||
530 | #else | ||
531 | (void) mkdir (uc->language, S_IRWXU); | ||
532 | #endif | ||
533 | snprintf (fn, sizeof (fn), | ||
534 | "%s/%s", | ||
535 | uc->language, | ||
536 | uc->category); | ||
537 | #ifdef WINDOWS | ||
538 | (void) mkdir (fn); | ||
539 | #else | ||
540 | (void) mkdir (fn, S_IRWXU); | ||
541 | #endif | ||
542 | /* open file */ | ||
543 | snprintf (fn, sizeof (fn), | ||
544 | "%s/%s/%s", | ||
545 | uc->language, | ||
546 | uc->category, | ||
547 | filename); | ||
548 | for (i=strlen (fn)-1;i>=0;i--) | ||
549 | if (! isprint ((int) fn[i])) | ||
550 | fn[i] = '_'; | ||
551 | uc->fd = open (fn, | ||
552 | O_CREAT | O_EXCL | ||
553 | #if O_LARGEFILE | ||
554 | | O_LARGEFILE | ||
555 | #endif | ||
556 | | O_WRONLY, | ||
557 | S_IRUSR | S_IWUSR); | ||
558 | if (-1 == uc->fd) | ||
559 | { | ||
560 | fprintf (stderr, | ||
561 | "Error opening file `%s' for upload: %s\n", | ||
562 | fn, | ||
563 | strerror (errno)); | ||
564 | uc->response = request_refused_response; | ||
565 | return MHD_NO; | ||
566 | } | ||
567 | uc->filename = strdup (fn); | ||
568 | } | ||
569 | if ( (0 != size) && | ||
570 | (size != (size_t) write (uc->fd, data, size)) ) | ||
571 | { | ||
572 | /* write failed; likely: disk full */ | ||
573 | fprintf (stderr, | ||
574 | "Error writing to file `%s': %s\n", | ||
575 | uc->filename, | ||
576 | strerror (errno)); | ||
577 | uc->response = internal_error_response; | ||
578 | close (uc->fd); | ||
579 | uc->fd = -1; | ||
580 | if (NULL != uc->filename) | ||
581 | { | ||
582 | unlink (uc->filename); | ||
583 | free (uc->filename); | ||
584 | uc->filename = NULL; | ||
585 | } | ||
586 | return MHD_NO; | ||
587 | } | ||
588 | return MHD_YES; | ||
589 | } | ||
590 | |||
591 | |||
592 | /** | ||
593 | * Function called whenever a request was completed. | ||
594 | * Used to clean up 'struct UploadContext' objects. | ||
595 | * | ||
596 | * @param cls client-defined closure, NULL | ||
597 | * @param connection connection handle | ||
598 | * @param con_cls value as set by the last call to | ||
599 | * the MHD_AccessHandlerCallback, points to NULL if this was | ||
600 | * not an upload | ||
601 | * @param toe reason for request termination | ||
602 | */ | ||
603 | static void | ||
604 | response_completed_callback (void *cls, | ||
605 | struct MHD_Connection *connection, | ||
606 | void **con_cls, | ||
607 | enum MHD_RequestTerminationCode toe) | ||
608 | { | ||
609 | struct UploadContext *uc = *con_cls; | ||
610 | |||
611 | if (NULL == uc) | ||
612 | return; /* this request wasn't an upload request */ | ||
613 | if (NULL != uc->pp) | ||
614 | { | ||
615 | MHD_destroy_post_processor (uc->pp); | ||
616 | uc->pp = NULL; | ||
617 | } | ||
618 | if (-1 != uc->fd) | ||
619 | { | ||
620 | (void) close (uc->fd); | ||
621 | if (NULL != uc->filename) | ||
622 | { | ||
623 | fprintf (stderr, | ||
624 | "Upload of file `%s' failed (incomplete or aborted), removing file.\n", | ||
625 | uc->filename); | ||
626 | (void) unlink (uc->filename); | ||
627 | } | ||
628 | } | ||
629 | if (NULL != uc->filename) | ||
630 | free (uc->filename); | ||
631 | free (uc); | ||
632 | } | ||
633 | |||
634 | |||
635 | /** | ||
636 | * Return the current directory listing. | ||
637 | * | ||
638 | * @param connection connection to return the directory for | ||
639 | * @return MHD_YES on success, MHD_NO on error | ||
640 | */ | ||
641 | static int | ||
642 | return_directory_response (struct MHD_Connection *connection) | ||
643 | { | ||
644 | int ret; | ||
645 | |||
646 | (void) pthread_mutex_lock (&mutex); | ||
647 | if (NULL == cached_directory_response) | ||
648 | ret = MHD_queue_response (connection, | ||
649 | MHD_HTTP_INTERNAL_SERVER_ERROR, | ||
650 | internal_error_response); | ||
651 | else | ||
652 | ret = MHD_queue_response (connection, | ||
653 | MHD_HTTP_OK, | ||
654 | cached_directory_response); | ||
655 | (void) pthread_mutex_unlock (&mutex); | ||
656 | return ret; | ||
657 | } | ||
658 | |||
659 | |||
660 | /** | ||
661 | * Main callback from MHD, used to generate the page. | ||
662 | * | ||
663 | * @param cls NULL | ||
664 | * @param connection connection handle | ||
665 | * @param url requested URL | ||
666 | * @param method GET, PUT, POST, etc. | ||
667 | * @param version HTTP version | ||
668 | * @param upload_data data from upload (PUT/POST) | ||
669 | * @param upload_data_size number of bytes in "upload_data" | ||
670 | * @param ptr our context | ||
671 | * @return MHD_YES on success, MHD_NO to drop connection | ||
672 | */ | ||
673 | static int | ||
674 | generate_page (void *cls, | ||
675 | struct MHD_Connection *connection, | ||
676 | const char *url, | ||
677 | const char *method, | ||
678 | const char *version, | ||
679 | const char *upload_data, | ||
680 | size_t *upload_data_size, void **ptr) | ||
681 | { | ||
682 | struct MHD_Response *response; | ||
683 | int ret; | ||
684 | int fd; | ||
685 | struct stat buf; | ||
686 | |||
687 | if (0 != strcmp (url, "/")) | ||
688 | { | ||
689 | /* should be file download */ | ||
690 | char file_data[MAGIC_HEADER_SIZE]; | ||
691 | ssize_t got; | ||
692 | const char *mime; | ||
693 | |||
694 | if (0 != strcmp (method, MHD_HTTP_METHOD_GET)) | ||
695 | return MHD_NO; /* unexpected method (we're not polite...) */ | ||
696 | if ( (0 == stat (&url[1], &buf)) && | ||
697 | (NULL == strstr (&url[1], "..")) && | ||
698 | ('/' != url[1])) | ||
699 | fd = open (&url[1], O_RDONLY); | ||
700 | else | ||
701 | fd = -1; | ||
702 | if (-1 == fd) | ||
703 | return MHD_queue_response (connection, | ||
704 | MHD_HTTP_NOT_FOUND, | ||
705 | file_not_found_response); | ||
706 | /* read beginning of the file to determine mime type */ | ||
707 | got = read (fd, file_data, sizeof (file_data)); | ||
708 | if (-1 != got) | ||
709 | mime = magic_buffer (magic, file_data, got); | ||
710 | else | ||
711 | mime = NULL; | ||
712 | (void) lseek (fd, 0, SEEK_SET); | ||
713 | |||
714 | if (NULL == (response = MHD_create_response_from_fd (buf.st_size, | ||
715 | fd))) | ||
716 | { | ||
717 | /* internal error (i.e. out of memory) */ | ||
718 | (void) close (fd); | ||
719 | return MHD_NO; | ||
720 | } | ||
721 | |||
722 | /* add mime type if we had one */ | ||
723 | if (NULL != mime) | ||
724 | (void) MHD_add_response_header (response, | ||
725 | MHD_HTTP_HEADER_CONTENT_TYPE, | ||
726 | mime); | ||
727 | ret = MHD_queue_response (connection, | ||
728 | MHD_HTTP_OK, | ||
729 | response); | ||
730 | MHD_destroy_response (response); | ||
731 | return ret; | ||
732 | } | ||
733 | |||
734 | if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) | ||
735 | { | ||
736 | /* upload! */ | ||
737 | struct UploadContext *uc = *ptr; | ||
738 | |||
739 | if (NULL == uc) | ||
740 | { | ||
741 | if (NULL == (uc = malloc (sizeof (struct UploadContext)))) | ||
742 | return MHD_NO; /* out of memory, close connection */ | ||
743 | memset (uc, 0, sizeof (struct UploadContext)); | ||
744 | uc->fd = -1; | ||
745 | uc->connection = connection; | ||
746 | uc->pp = MHD_create_post_processor (connection, | ||
747 | 64 * 1024 /* buffer size */, | ||
748 | &process_upload_data, uc); | ||
749 | if (NULL == uc->pp) | ||
750 | { | ||
751 | /* out of memory, close connection */ | ||
752 | free (uc); | ||
753 | return MHD_NO; | ||
754 | } | ||
755 | *ptr = uc; | ||
756 | return MHD_YES; | ||
757 | } | ||
758 | if (0 != *upload_data_size) | ||
759 | { | ||
760 | if (NULL == uc->response) | ||
761 | (void) MHD_post_process (uc->pp, | ||
762 | upload_data, | ||
763 | *upload_data_size); | ||
764 | *upload_data_size = 0; | ||
765 | return MHD_YES; | ||
766 | } | ||
767 | /* end of upload, finish it! */ | ||
768 | MHD_destroy_post_processor (uc->pp); | ||
769 | uc->pp = NULL; | ||
770 | if (-1 != uc->fd) | ||
771 | { | ||
772 | close (uc->fd); | ||
773 | uc->fd = -1; | ||
774 | } | ||
775 | if (NULL != uc->response) | ||
776 | { | ||
777 | return MHD_queue_response (connection, | ||
778 | MHD_HTTP_FORBIDDEN, | ||
779 | uc->response); | ||
780 | } | ||
781 | else | ||
782 | { | ||
783 | update_directory (); | ||
784 | return return_directory_response (connection); | ||
785 | } | ||
786 | } | ||
787 | if (0 == strcmp (method, MHD_HTTP_METHOD_GET)) | ||
788 | { | ||
789 | return return_directory_response (connection); | ||
790 | } | ||
791 | |||
792 | /* unexpected request, refuse */ | ||
793 | return MHD_queue_response (connection, | ||
794 | MHD_HTTP_FORBIDDEN, | ||
795 | request_refused_response); | ||
796 | } | ||
797 | |||
798 | |||
799 | /** | ||
800 | * Function called if we get a SIGPIPE. Does nothing. | ||
801 | * | ||
802 | * @param sig will be SIGPIPE (ignored) | ||
803 | */ | ||
804 | static void | ||
805 | catcher (int sig) | ||
806 | { | ||
807 | /* do nothing */ | ||
808 | } | ||
809 | |||
810 | |||
811 | /** | ||
812 | * setup handlers to ignore SIGPIPE. | ||
813 | */ | ||
814 | #ifndef MINGW | ||
815 | static void | ||
816 | ignore_sigpipe () | ||
817 | { | ||
818 | struct sigaction oldsig; | ||
819 | struct sigaction sig; | ||
820 | |||
821 | sig.sa_handler = &catcher; | ||
822 | sigemptyset (&sig.sa_mask); | ||
823 | #ifdef SA_INTERRUPT | ||
824 | sig.sa_flags = SA_INTERRUPT; /* SunOS */ | ||
825 | #else | ||
826 | sig.sa_flags = SA_RESTART; | ||
827 | #endif | ||
828 | if (0 != sigaction (SIGPIPE, &sig, &oldsig)) | ||
829 | fprintf (stderr, | ||
830 | "Failed to install SIGPIPE handler: %s\n", strerror (errno)); | ||
831 | } | ||
832 | #endif | ||
833 | |||
834 | /* test server key */ | ||
835 | const char srv_signed_key_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n" | ||
836 | "MIIEowIBAAKCAQEAvfTdv+3fgvVTKRnP/HVNG81cr8TrUP/iiyuve/THMzvFXhCW\n" | ||
837 | "+K03KwEku55QvnUndwBfU/ROzLlv+5hotgiDRNFT3HxurmhouySBrJNJv7qWp8IL\n" | ||
838 | "q4sw32vo0fbMu5BZF49bUXK9L3kW2PdhTtSQPWHEzNrCxO+YgCilKHkY3vQNfdJ0\n" | ||
839 | "20Q5EAAEseD1YtWCIpRvJzYlZMpjYB1ubTl24kwrgOKUJYKqM4jmF4DVQp4oOK/6\n" | ||
840 | "QYGGh1QmHRPAy3CBII6sbb+sZT9cAqU6GYQVB35lm4XAgibXV6KgmpVxVQQ69U6x\n" | ||
841 | "yoOl204xuekZOaG9RUPId74Rtmwfi1TLbBzo2wIDAQABAoIBADu09WSICNq5cMe4\n" | ||
842 | "+NKCLlgAT1NiQpLls1gKRbDhKiHU9j8QWNvWWkJWrCya4QdUfLCfeddCMeiQmv3K\n" | ||
843 | "lJMvDs+5OjJSHFoOsGiuW2Ias7IjnIojaJalfBml6frhJ84G27IXmdz6gzOiTIer\n" | ||
844 | "DjeAgcwBaKH5WwIay2TxIaScl7AwHBauQkrLcyb4hTmZuQh6ArVIN6+pzoVuORXM\n" | ||
845 | "bpeNWl2l/HSN3VtUN6aCAKbN/X3o0GavCCMn5Fa85uJFsab4ss/uP+2PusU71+zP\n" | ||
846 | "sBm6p/2IbGvF5k3VPDA7X5YX61sukRjRBihY8xSnNYx1UcoOsX6AiPnbhifD8+xQ\n" | ||
847 | "Tlf8oJUCgYEA0BTfzqNpr9Wxw5/QXaSdw7S/0eP5a0C/nwURvmfSzuTD4equzbEN\n" | ||
848 | "d+dI/s2JMxrdj/I4uoAfUXRGaabevQIjFzC9uyE3LaOyR2zhuvAzX+vVcs6bSXeU\n" | ||
849 | "pKpCAcN+3Z3evMaX2f+z/nfSUAl2i4J2R+/LQAWJW4KwRky/m+cxpfUCgYEA6bN1\n" | ||
850 | "b73bMgM8wpNt6+fcmS+5n0iZihygQ2U2DEud8nZJL4Nrm1dwTnfZfJBnkGj6+0Q0\n" | ||
851 | "cOwj2KS0/wcEdJBP0jucU4v60VMhp75AQeHqidIde0bTViSRo3HWKXHBIFGYoU3T\n" | ||
852 | "LyPyKndbqsOObnsFXHn56Nwhr2HLf6nw4taGQY8CgYBoSW36FLCNbd6QGvLFXBGt\n" | ||
853 | "2lMhEM8az/K58kJ4WXSwOLtr6MD/WjNT2tkcy0puEJLm6BFCd6A6pLn9jaKou/92\n" | ||
854 | "SfltZjJPb3GUlp9zn5tAAeSSi7YMViBrfuFiHObij5LorefBXISLjuYbMwL03MgH\n" | ||
855 | "Ocl2JtA2ywMp2KFXs8GQWQKBgFyIVv5ogQrbZ0pvj31xr9HjqK6d01VxIi+tOmpB\n" | ||
856 | "4ocnOLEcaxX12BzprW55ytfOCVpF1jHD/imAhb3YrHXu0fwe6DXYXfZV4SSG2vB7\n" | ||
857 | "IB9z14KBN5qLHjNGFpMQXHSMek+b/ftTU0ZnPh9uEM5D3YqRLVd7GcdUhHvG8P8Q\n" | ||
858 | "C9aXAoGBAJtID6h8wOGMP0XYX5YYnhlC7dOLfk8UYrzlp3xhqVkzKthTQTj6wx9R\n" | ||
859 | "GtC4k7U1ki8oJsfcIlBNXd768fqDVWjYju5rzShMpo8OCTS6ipAblKjCxPPVhIpv\n" | ||
860 | "tWPlbSn1qj6wylstJ5/3Z+ZW5H4wIKp5jmLiioDhcP0L/Ex3Zx8O\n" | ||
861 | "-----END RSA PRIVATE KEY-----\n"; | ||
862 | |||
863 | /* test server CA signed certificates */ | ||
864 | const char srv_signed_cert_pem[] = "-----BEGIN CERTIFICATE-----\n" | ||
865 | "MIIDGzCCAgWgAwIBAgIES0KCvTALBgkqhkiG9w0BAQUwFzEVMBMGA1UEAxMMdGVz\n" | ||
866 | "dF9jYV9jZXJ0MB4XDTEwMDEwNTAwMDcyNVoXDTQ1MDMxMjAwMDcyNVowFzEVMBMG\n" | ||
867 | "A1UEAxMMdGVzdF9jYV9jZXJ0MIIBHzALBgkqhkiG9w0BAQEDggEOADCCAQkCggEA\n" | ||
868 | "vfTdv+3fgvVTKRnP/HVNG81cr8TrUP/iiyuve/THMzvFXhCW+K03KwEku55QvnUn\n" | ||
869 | "dwBfU/ROzLlv+5hotgiDRNFT3HxurmhouySBrJNJv7qWp8ILq4sw32vo0fbMu5BZ\n" | ||
870 | "F49bUXK9L3kW2PdhTtSQPWHEzNrCxO+YgCilKHkY3vQNfdJ020Q5EAAEseD1YtWC\n" | ||
871 | "IpRvJzYlZMpjYB1ubTl24kwrgOKUJYKqM4jmF4DVQp4oOK/6QYGGh1QmHRPAy3CB\n" | ||
872 | "II6sbb+sZT9cAqU6GYQVB35lm4XAgibXV6KgmpVxVQQ69U6xyoOl204xuekZOaG9\n" | ||
873 | "RUPId74Rtmwfi1TLbBzo2wIDAQABo3YwdDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQM\n" | ||
874 | "MAoGCCsGAQUFBwMBMA8GA1UdDwEB/wQFAwMHIAAwHQYDVR0OBBYEFOFi4ilKOP1d\n" | ||
875 | "XHlWCMwmVKr7mgy8MB8GA1UdIwQYMBaAFP2olB4s2T/xuoQ5pT2RKojFwZo2MAsG\n" | ||
876 | "CSqGSIb3DQEBBQOCAQEAHVWPxazupbOkG7Did+dY9z2z6RjTzYvurTtEKQgzM2Vz\n" | ||
877 | "GQBA+3pZ3c5mS97fPIs9hZXfnQeelMeZ2XP1a+9vp35bJjZBBhVH+pqxjCgiUflg\n" | ||
878 | "A3Zqy0XwwVCgQLE2HyaU3DLUD/aeIFK5gJaOSdNTXZLv43K8kl4cqDbMeRpVTbkt\n" | ||
879 | "YmG4AyEOYRNKGTqMEJXJoxD5E3rBUNrVI/XyTjYrulxbNPcMWEHKNeeqWpKDYTFo\n" | ||
880 | "Bb01PCthGXiq/4A2RLAFosadzRa8SBpoSjPPfZ0b2w4MJpReHqKbR5+T2t6hzml6\n" | ||
881 | "4ToyOKPDmamiTuN5KzLN3cw7DQlvWMvqSOChPLnA3Q==\n" | ||
882 | "-----END CERTIFICATE-----\n"; | ||
883 | |||
884 | |||
885 | /** | ||
886 | * Entry point to demo. Note: this HTTP server will make all | ||
887 | * files in the current directory and its subdirectories available | ||
888 | * to anyone. Press ENTER to stop the server once it has started. | ||
889 | * | ||
890 | * @param argc number of arguments in argv | ||
891 | * @param argv first and only argument should be the port number | ||
892 | * @return 0 on success | ||
893 | */ | ||
894 | int | ||
895 | main (int argc, char *const *argv) | ||
896 | { | ||
897 | struct MHD_Daemon *d; | ||
898 | unsigned int port; | ||
899 | |||
900 | if ( (argc != 2) || | ||
901 | (1 != sscanf (argv[1], "%u", &port)) || | ||
902 | (UINT16_MAX < port) ) | ||
903 | { | ||
904 | fprintf (stderr, | ||
905 | "%s PORT\n", argv[0]); | ||
906 | return 1; | ||
907 | } | ||
908 | #ifndef MINGW | ||
909 | ignore_sigpipe (); | ||
910 | #endif | ||
911 | magic = magic_open (MAGIC_MIME_TYPE); | ||
912 | (void) magic_load (magic, NULL); | ||
913 | |||
914 | (void) pthread_mutex_init (&mutex, NULL); | ||
915 | file_not_found_response = MHD_create_response_from_buffer (strlen (FILE_NOT_FOUND_PAGE), | ||
916 | (void *) FILE_NOT_FOUND_PAGE, | ||
917 | MHD_RESPMEM_PERSISTENT); | ||
918 | mark_as_html (file_not_found_response); | ||
919 | request_refused_response = MHD_create_response_from_buffer (strlen (REQUEST_REFUSED_PAGE), | ||
920 | (void *) REQUEST_REFUSED_PAGE, | ||
921 | MHD_RESPMEM_PERSISTENT); | ||
922 | mark_as_html (request_refused_response); | ||
923 | internal_error_response = MHD_create_response_from_buffer (strlen (INTERNAL_ERROR_PAGE), | ||
924 | (void *) INTERNAL_ERROR_PAGE, | ||
925 | MHD_RESPMEM_PERSISTENT); | ||
926 | mark_as_html (internal_error_response); | ||
927 | update_directory (); | ||
928 | d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | MHD_USE_SSL | ||
929 | #if EPOLL_SUPPORT | ||
930 | | MHD_USE_EPOLL_LINUX_ONLY | ||
931 | #endif | ||
932 | , | ||
933 | port, | ||
934 | NULL, NULL, | ||
935 | &generate_page, NULL, | ||
936 | MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (256 * 1024), | ||
937 | #if PRODUCTION | ||
938 | MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) (64), | ||
939 | #endif | ||
940 | MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) (120 /* seconds */), | ||
941 | MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) NUMBER_OF_THREADS, | ||
942 | MHD_OPTION_NOTIFY_COMPLETED, &response_completed_callback, NULL, | ||
943 | MHD_OPTION_HTTPS_MEM_KEY, srv_signed_key_pem, | ||
944 | MHD_OPTION_HTTPS_MEM_CERT, srv_signed_cert_pem, | ||
945 | MHD_OPTION_END); | ||
946 | if (NULL == d) | ||
947 | return 1; | ||
948 | fprintf (stderr, "HTTP server running. Press ENTER to stop the server\n"); | ||
949 | (void) getc (stdin); | ||
950 | MHD_stop_daemon (d); | ||
951 | MHD_destroy_response (file_not_found_response); | ||
952 | MHD_destroy_response (request_refused_response); | ||
953 | MHD_destroy_response (internal_error_response); | ||
954 | update_cached_response (NULL); | ||
955 | (void) pthread_mutex_destroy (&mutex); | ||
956 | magic_close (magic); | ||
957 | return 0; | ||
958 | } | ||
959 | |||
960 | /* end of demo_https.c */ | ||