blob: 49bf30aea3597ee84dc9cda9a12ae0f70e81989a [file] [log] [blame]
Daniel Erat748945e2015-08-11 09:22:30 -06001/*
2 This file is part of libmicrohttpd
3 Copyright (C) 2007 Christian Grothoff
4
5 libmicrohttpd is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 2, or (at your
8 option) any later version.
9
10 libmicrohttpd is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with libmicrohttpd; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA.
19*/
20
21/**
22 * @file test_post.c
23 * @brief Testcase for libmicrohttpd POST operations using URL-encoding
24 * @author Christian Grothoff
25 */
26
27#include "MHD_config.h"
28#include "platform.h"
29#include <curl/curl.h>
30#include <microhttpd.h>
31#include <stdlib.h>
32#include <string.h>
33#include <time.h>
34
35#ifndef WINDOWS
36#include <unistd.h>
37#endif
38
39#ifdef _WIN32
40#ifndef WIN32_LEAN_AND_MEAN
41#define WIN32_LEAN_AND_MEAN 1
42#endif /* !WIN32_LEAN_AND_MEAN */
43#include <windows.h>
44#endif
45
46#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
47#undef CPU_COUNT
48#endif
49#if !defined(CPU_COUNT)
50#define CPU_COUNT 2
51#endif
52
53#define POST_DATA "name=daniel&project=curl"
54
55static int oneone;
56
57struct CBC
58{
59 char *buf;
60 size_t pos;
61 size_t size;
62};
63
64
65static void
66completed_cb (void *cls,
67 struct MHD_Connection *connection,
68 void **con_cls,
69 enum MHD_RequestTerminationCode toe)
70{
71 struct MHD_PostProcessor *pp = *con_cls;
72
73 if (NULL != pp)
74 MHD_destroy_post_processor (pp);
75 *con_cls = NULL;
76}
77
78
79static size_t
80copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
81{
82 struct CBC *cbc = ctx;
83
84 if (cbc->pos + size * nmemb > cbc->size)
85 return 0; /* overflow */
86 memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
87 cbc->pos += size * nmemb;
88 return size * nmemb;
89}
90
91
92/**
93 * Note that this post_iterator is not perfect
94 * in that it fails to support incremental processing.
95 * (to be fixed in the future)
96 */
97static int
98post_iterator (void *cls,
99 enum MHD_ValueKind kind,
100 const char *key,
101 const char *filename,
102 const char *content_type,
103 const char *transfer_encoding,
104 const char *value, uint64_t off, size_t size)
105{
106 int *eok = cls;
107
108 if ((0 == strcmp (key, "name")) &&
109 (size == strlen ("daniel")) && (0 == strncmp (value, "daniel", size)))
110 (*eok) |= 1;
111 if ((0 == strcmp (key, "project")) &&
112 (size == strlen ("curl")) && (0 == strncmp (value, "curl", size)))
113 (*eok) |= 2;
114 return MHD_YES;
115}
116
117
118static int
119ahc_echo (void *cls,
120 struct MHD_Connection *connection,
121 const char *url,
122 const char *method,
123 const char *version,
124 const char *upload_data, size_t *upload_data_size,
125 void **unused)
126{
127 static int eok;
128 struct MHD_Response *response;
129 struct MHD_PostProcessor *pp;
130 int ret;
131
132 if (0 != strcmp ("POST", method))
133 {
134 printf ("METHOD: %s\n", method);
135 return MHD_NO; /* unexpected method */
136 }
137 pp = *unused;
138 if (pp == NULL)
139 {
140 eok = 0;
141 pp = MHD_create_post_processor (connection, 1024, &post_iterator, &eok);
142 *unused = pp;
143 }
144 MHD_post_process (pp, upload_data, *upload_data_size);
145 if ((eok == 3) && (0 == *upload_data_size))
146 {
147 response = MHD_create_response_from_buffer (strlen (url),
148 (void *) url,
149 MHD_RESPMEM_MUST_COPY);
150 ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
151 MHD_destroy_response (response);
152 MHD_destroy_post_processor (pp);
153 *unused = NULL;
154 return ret;
155 }
156 *upload_data_size = 0;
157 return MHD_YES;
158}
159
160
161static int
162testInternalPost ()
163{
164 struct MHD_Daemon *d;
165 CURL *c;
166 char buf[2048];
167 struct CBC cbc;
168 CURLcode errornum;
169
170 cbc.buf = buf;
171 cbc.size = 2048;
172 cbc.pos = 0;
173 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
174 1080, NULL, NULL, &ahc_echo, NULL,
175 MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
176 MHD_OPTION_END);
177 if (d == NULL)
178 return 1;
179 c = curl_easy_init ();
180 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1080/hello_world");
181 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
182 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
183 curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
184 curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
185 curl_easy_setopt (c, CURLOPT_POST, 1L);
186 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
187 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
188 if (oneone)
189 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
190 else
191 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
192 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
193 // NOTE: use of CONNECTTIMEOUT without also
194 // setting NOSIGNAL results in really weird
195 // crashes on my system!
196 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
197 if (CURLE_OK != (errornum = curl_easy_perform (c)))
198 {
199 fprintf (stderr,
200 "curl_easy_perform failed: `%s'\n",
201 curl_easy_strerror (errornum));
202 curl_easy_cleanup (c);
203 MHD_stop_daemon (d);
204 return 2;
205 }
206 curl_easy_cleanup (c);
207 MHD_stop_daemon (d);
208 if (cbc.pos != strlen ("/hello_world"))
209 return 4;
210 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
211 return 8;
212 return 0;
213}
214
215static int
216testMultithreadedPost ()
217{
218 struct MHD_Daemon *d;
219 CURL *c;
220 char buf[2048];
221 struct CBC cbc;
222 CURLcode errornum;
223
224 cbc.buf = buf;
225 cbc.size = 2048;
226 cbc.pos = 0;
227 d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
228 1081, NULL, NULL, &ahc_echo, NULL,
229 MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
230 MHD_OPTION_END);
231 if (d == NULL)
232 return 16;
233 c = curl_easy_init ();
234 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
235 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
236 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
237 curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
238 curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
239 curl_easy_setopt (c, CURLOPT_POST, 1L);
240 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
241 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
242 if (oneone)
243 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
244 else
245 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
246 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
247 // NOTE: use of CONNECTTIMEOUT without also
248 // setting NOSIGNAL results in really weird
249 // crashes on my system!
250 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
251 if (CURLE_OK != (errornum = curl_easy_perform (c)))
252 {
253 fprintf (stderr,
254 "curl_easy_perform failed: `%s'\n",
255 curl_easy_strerror (errornum));
256 curl_easy_cleanup (c);
257 MHD_stop_daemon (d);
258 return 32;
259 }
260 curl_easy_cleanup (c);
261 MHD_stop_daemon (d);
262 if (cbc.pos != strlen ("/hello_world"))
263 return 64;
264 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
265 return 128;
266 return 0;
267}
268
269static int
270testMultithreadedPoolPost ()
271{
272 struct MHD_Daemon *d;
273 CURL *c;
274 char buf[2048];
275 struct CBC cbc;
276 CURLcode errornum;
277
278 cbc.buf = buf;
279 cbc.size = 2048;
280 cbc.pos = 0;
281 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
282 1081, NULL, NULL, &ahc_echo, NULL,
283 MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT,
284 MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
285 MHD_OPTION_END);
286 if (d == NULL)
287 return 16;
288 c = curl_easy_init ();
289 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
290 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
291 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
292 curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
293 curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
294 curl_easy_setopt (c, CURLOPT_POST, 1L);
295 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
296 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
297 if (oneone)
298 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
299 else
300 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
301 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
302 // NOTE: use of CONNECTTIMEOUT without also
303 // setting NOSIGNAL results in really weird
304 // crashes on my system!
305 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
306 if (CURLE_OK != (errornum = curl_easy_perform (c)))
307 {
308 fprintf (stderr,
309 "curl_easy_perform failed: `%s'\n",
310 curl_easy_strerror (errornum));
311 curl_easy_cleanup (c);
312 MHD_stop_daemon (d);
313 return 32;
314 }
315 curl_easy_cleanup (c);
316 MHD_stop_daemon (d);
317 if (cbc.pos != strlen ("/hello_world"))
318 return 64;
319 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
320 return 128;
321 return 0;
322}
323
324static int
325testExternalPost ()
326{
327 struct MHD_Daemon *d;
328 CURL *c;
329 char buf[2048];
330 struct CBC cbc;
331 CURLM *multi;
332 CURLMcode mret;
333 fd_set rs;
334 fd_set ws;
335 fd_set es;
336 MHD_socket max;
337 int running;
338 struct CURLMsg *msg;
339 time_t start;
340 struct timeval tv;
341
342 multi = NULL;
343 cbc.buf = buf;
344 cbc.size = 2048;
345 cbc.pos = 0;
346 d = MHD_start_daemon (MHD_USE_DEBUG,
347 1082, NULL, NULL, &ahc_echo, NULL,
348 MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
349 MHD_OPTION_END);
350 if (d == NULL)
351 return 256;
352 c = curl_easy_init ();
353 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1082/hello_world");
354 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
355 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
356 curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
357 curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
358 curl_easy_setopt (c, CURLOPT_POST, 1L);
359 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
360 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
361 if (oneone)
362 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
363 else
364 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
365 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
366 // NOTE: use of CONNECTTIMEOUT without also
367 // setting NOSIGNAL results in really weird
368 // crashes on my system!
369 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
370
371
372 multi = curl_multi_init ();
373 if (multi == NULL)
374 {
375 curl_easy_cleanup (c);
376 MHD_stop_daemon (d);
377 return 512;
378 }
379 mret = curl_multi_add_handle (multi, c);
380 if (mret != CURLM_OK)
381 {
382 curl_multi_cleanup (multi);
383 curl_easy_cleanup (c);
384 MHD_stop_daemon (d);
385 return 1024;
386 }
387 start = time (NULL);
388 while ((time (NULL) - start < 5) && (multi != NULL))
389 {
390 max = 0;
391 FD_ZERO (&rs);
392 FD_ZERO (&ws);
393 FD_ZERO (&es);
394 curl_multi_perform (multi, &running);
395 mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
396 if (mret != CURLM_OK)
397 {
398 curl_multi_remove_handle (multi, c);
399 curl_multi_cleanup (multi);
400 curl_easy_cleanup (c);
401 MHD_stop_daemon (d);
402 return 2048;
403 }
404 if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
405 {
406 curl_multi_remove_handle (multi, c);
407 curl_multi_cleanup (multi);
408 curl_easy_cleanup (c);
409 MHD_stop_daemon (d);
410 return 4096;
411 }
412 tv.tv_sec = 0;
413 tv.tv_usec = 1000;
414 select (max + 1, &rs, &ws, &es, &tv);
415 curl_multi_perform (multi, &running);
416 if (running == 0)
417 {
418 msg = curl_multi_info_read (multi, &running);
419 if (msg == NULL)
420 break;
421 if (msg->msg == CURLMSG_DONE)
422 {
423 if (msg->data.result != CURLE_OK)
424 printf ("%s failed at %s:%d: `%s'\n",
425 "curl_multi_perform",
426 __FILE__,
427 __LINE__, curl_easy_strerror (msg->data.result));
428 curl_multi_remove_handle (multi, c);
429 curl_multi_cleanup (multi);
430 curl_easy_cleanup (c);
431 c = NULL;
432 multi = NULL;
433 }
434 }
435 MHD_run (d);
436 }
437 if (multi != NULL)
438 {
439 curl_multi_remove_handle (multi, c);
440 curl_easy_cleanup (c);
441 curl_multi_cleanup (multi);
442 }
443 MHD_stop_daemon (d);
444 if (cbc.pos != strlen ("/hello_world"))
445 return 8192;
446 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
447 return 16384;
448 return 0;
449}
450
451
452static int
453ahc_cancel (void *cls,
454 struct MHD_Connection *connection,
455 const char *url,
456 const char *method,
457 const char *version,
458 const char *upload_data, size_t *upload_data_size,
459 void **unused)
460{
461 struct MHD_Response *response;
462 int ret;
463
464 if (0 != strcmp ("POST", method))
465 {
466 fprintf (stderr,
467 "Unexpected method `%s'\n", method);
468 return MHD_NO;
469 }
470
471 if (*unused == NULL)
472 {
473 *unused = "wibble";
474 /* We don't want the body. Send a 500. */
475 response = MHD_create_response_from_buffer (0, NULL,
476 MHD_RESPMEM_PERSISTENT);
477 ret = MHD_queue_response(connection, 500, response);
478 if (ret != MHD_YES)
479 fprintf(stderr, "Failed to queue response\n");
480 MHD_destroy_response(response);
481 return ret;
482 }
483 else
484 {
485 fprintf(stderr,
486 "In ahc_cancel again. This should not happen.\n");
487 return MHD_NO;
488 }
489}
490
491struct CRBC
492{
493 const char *buffer;
494 size_t size;
495 size_t pos;
496};
497
498
499static size_t
500readBuffer(void *p, size_t size, size_t nmemb, void *opaque)
501{
502 struct CRBC *data = opaque;
503 size_t required = size * nmemb;
504 size_t left = data->size - data->pos;
505
506 if (required > left)
507 required = left;
508
509 memcpy(p, data->buffer + data->pos, required);
510 data->pos += required;
511
512 return required/size;
513}
514
515
516static size_t
517slowReadBuffer(void *p, size_t size, size_t nmemb, void *opaque)
518{
519 sleep(1);
520 return readBuffer(p, size, nmemb, opaque);
521}
522
523
524#define FLAG_EXPECT_CONTINUE 1
525#define FLAG_CHUNKED 2
526#define FLAG_FORM_DATA 4
527#define FLAG_SLOW_READ 8
528#define FLAG_COUNT 16
529
530
531static int
532testMultithreadedPostCancelPart(int flags)
533{
534 struct MHD_Daemon *d;
535 CURL *c;
536 char buf[2048];
537 struct CBC cbc;
538 CURLcode errornum;
539 struct curl_slist *headers = NULL;
540 long response_code;
541 CURLcode cc;
542 int result = 0;
543 struct CRBC crbc;
544
545 /* Don't test features that aren't available with HTTP/1.0 in
546 * HTTP/1.0 mode. */
547 if (!oneone && (flags & (FLAG_EXPECT_CONTINUE | FLAG_CHUNKED)))
548 return 0;
549
550 cbc.buf = buf;
551 cbc.size = 2048;
552 cbc.pos = 0;
553 d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
554 1081, NULL, NULL, &ahc_cancel, NULL,
555 MHD_OPTION_END);
556 if (d == NULL)
557 return 32768;
558
559 crbc.buffer = "Test content";
560 crbc.size = strlen(crbc.buffer);
561 crbc.pos = 0;
562
563 c = curl_easy_init ();
564 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
565 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
566 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
567 curl_easy_setopt (c, CURLOPT_READFUNCTION, (flags & FLAG_SLOW_READ) ? &slowReadBuffer : &readBuffer);
568 curl_easy_setopt (c, CURLOPT_READDATA, &crbc);
569 curl_easy_setopt (c, CURLOPT_POSTFIELDS, NULL);
570 curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, crbc.size);
571 curl_easy_setopt (c, CURLOPT_POST, 1L);
572 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
573 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
574 if (oneone)
575 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
576 else
577 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
578 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
579 // NOTE: use of CONNECTTIMEOUT without also
580 // setting NOSIGNAL results in really weird
581 // crashes on my system!
582 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
583
584 if (flags & FLAG_CHUNKED)
585 headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
586 if (!(flags & FLAG_FORM_DATA))
587 headers = curl_slist_append(headers, "Content-Type: application/octet-stream");
588 if (flags & FLAG_EXPECT_CONTINUE)
589 headers = curl_slist_append(headers, "Expect: 100-Continue");
590 curl_easy_setopt(c, CURLOPT_HTTPHEADER, headers);
591
592 if (CURLE_HTTP_RETURNED_ERROR != (errornum = curl_easy_perform (c)))
593 {
594 fprintf (stderr,
595 "flibbet curl_easy_perform didn't fail as expected: `%s' %d\n",
596 curl_easy_strerror (errornum), errornum);
597 curl_easy_cleanup (c);
598 MHD_stop_daemon (d);
599 curl_slist_free_all(headers);
600 return 65536;
601 }
602
603 if (CURLE_OK != (cc = curl_easy_getinfo(c, CURLINFO_RESPONSE_CODE, &response_code)))
604 {
605 fprintf(stderr, "curl_easy_getinfo failed: '%s'\n", curl_easy_strerror(errornum));
606 result = 65536;
607 }
608
609 if (!result && (response_code != 500))
610 {
611 fprintf(stderr, "Unexpected response code: %ld\n", response_code);
612 result = 131072;
613 }
614
615 if (!result && (cbc.pos != 0))
616 result = 262144;
617
618 curl_easy_cleanup (c);
619 MHD_stop_daemon (d);
620 curl_slist_free_all(headers);
621 return result;
622}
623
624
625static int
626testMultithreadedPostCancel()
627{
628 int result = 0;
629 int flags;
630 for(flags = 0; flags < FLAG_COUNT; ++flags)
631 result |= testMultithreadedPostCancelPart(flags);
632 return result;
633}
634
635
636int
637main (int argc, char *const *argv)
638{
639 unsigned int errorCount = 0;
640
641 oneone = (NULL != strrchr (argv[0], (int) '/')) ?
642 (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
643 if (0 != curl_global_init (CURL_GLOBAL_WIN32))
644 return 2;
645 errorCount += testMultithreadedPostCancel ();
646 errorCount += testInternalPost ();
647 errorCount += testMultithreadedPost ();
648 errorCount += testMultithreadedPoolPost ();
649 errorCount += testExternalPost ();
650 if (errorCount != 0)
651 fprintf (stderr, "Error (code: %u)\n", errorCount);
652 curl_global_cleanup ();
653 return errorCount != 0; /* 0 == pass */
654}