1/*
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_postform.c
23 * @brief  Testcase for libmicrohttpd POST operations using multipart/postform data
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#ifdef HAVE_GCRYPT_H
35#include <gcrypt.h>
36#endif
37
38#ifndef WINDOWS
39#include <unistd.h>
40#endif
41
42#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
43#undef CPU_COUNT
44#endif
45#if !defined(CPU_COUNT)
46#define CPU_COUNT 2
47#endif
48
49static int oneone;
50
51struct CBC
52{
53  char *buf;
54  size_t pos;
55  size_t size;
56};
57
58
59static void
60completed_cb (void *cls,
61	      struct MHD_Connection *connection,
62	      void **con_cls,
63	      enum MHD_RequestTerminationCode toe)
64{
65  struct MHD_PostProcessor *pp = *con_cls;
66
67  if (NULL != pp)
68    MHD_destroy_post_processor (pp);
69  *con_cls = NULL;
70}
71
72
73static size_t
74copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
75{
76  struct CBC *cbc = ctx;
77
78  if (cbc->pos + size * nmemb > cbc->size)
79    return 0;                   /* overflow */
80  memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
81  cbc->pos += size * nmemb;
82  return size * nmemb;
83}
84
85
86/**
87 * Note that this post_iterator is not perfect
88 * in that it fails to support incremental processing.
89 * (to be fixed in the future)
90 */
91static int
92post_iterator (void *cls,
93               enum MHD_ValueKind kind,
94               const char *key,
95               const char *filename,
96               const char *content_type,
97               const char *transfer_encoding,
98               const char *value, uint64_t off, size_t size)
99{
100  int *eok = cls;
101
102#if 0
103  fprintf (stderr, "PI sees %s-%.*s\n", key, size, value);
104#endif
105  if ((0 == strcmp (key, "name")) &&
106      (size == strlen ("daniel")) && (0 == strncmp (value, "daniel", size)))
107    (*eok) |= 1;
108  if ((0 == strcmp (key, "project")) &&
109      (size == strlen ("curl")) && (0 == strncmp (value, "curl", size)))
110    (*eok) |= 2;
111  return MHD_YES;
112}
113
114
115static int
116ahc_echo (void *cls,
117          struct MHD_Connection *connection,
118          const char *url,
119          const char *method,
120          const char *version,
121          const char *upload_data, size_t *upload_data_size,
122          void **unused)
123{
124  static int eok;
125  struct MHD_Response *response;
126  struct MHD_PostProcessor *pp;
127  int ret;
128
129  if (0 != strcmp ("POST", method))
130    {
131      printf ("METHOD: %s\n", method);
132      return MHD_NO;            /* unexpected method */
133    }
134  pp = *unused;
135  if (pp == NULL)
136    {
137      eok = 0;
138      pp = MHD_create_post_processor (connection, 1024, &post_iterator, &eok);
139      if (pp == NULL)
140        abort ();
141      *unused = pp;
142    }
143  MHD_post_process (pp, upload_data, *upload_data_size);
144  if ((eok == 3) && (0 == *upload_data_size))
145    {
146      response = MHD_create_response_from_buffer (strlen (url),
147						  (void *) url,
148						  MHD_RESPMEM_MUST_COPY);
149      ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
150      MHD_destroy_response (response);
151      MHD_destroy_post_processor (pp);
152      *unused = NULL;
153      return ret;
154    }
155  *upload_data_size = 0;
156  return MHD_YES;
157}
158
159static struct curl_httppost *
160make_form ()
161{
162  struct curl_httppost *post = NULL;
163  struct curl_httppost *last = NULL;
164
165  curl_formadd (&post, &last, CURLFORM_COPYNAME, "name",
166                CURLFORM_COPYCONTENTS, "daniel", CURLFORM_END);
167  curl_formadd (&post, &last, CURLFORM_COPYNAME, "project",
168                CURLFORM_COPYCONTENTS, "curl", CURLFORM_END);
169  return post;
170}
171
172
173static int
174testInternalPost ()
175{
176  struct MHD_Daemon *d;
177  CURL *c;
178  char buf[2048];
179  struct CBC cbc;
180  CURLcode errornum;
181  struct curl_httppost *pd;
182
183  cbc.buf = buf;
184  cbc.size = 2048;
185  cbc.pos = 0;
186  d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
187                        1080, NULL, NULL, &ahc_echo, NULL,
188			MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
189			MHD_OPTION_END);
190  if (d == NULL)
191    return 1;
192  c = curl_easy_init ();
193  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1080/hello_world");
194  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
195  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
196  pd = make_form ();
197  curl_easy_setopt (c, CURLOPT_HTTPPOST, pd);
198  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
199  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
200  if (oneone)
201    curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
202  else
203    curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
204  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
205  // NOTE: use of CONNECTTIMEOUT without also
206  //   setting NOSIGNAL results in really weird
207  //   crashes on my system!
208  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
209  if (CURLE_OK != (errornum = curl_easy_perform (c)))
210    {
211      fprintf (stderr,
212               "curl_easy_perform failed: `%s'\n",
213               curl_easy_strerror (errornum));
214      curl_easy_cleanup (c);
215      curl_formfree (pd);
216      MHD_stop_daemon (d);
217      return 2;
218    }
219  curl_easy_cleanup (c);
220  curl_formfree (pd);
221  MHD_stop_daemon (d);
222  if (cbc.pos != strlen ("/hello_world"))
223    return 4;
224  if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
225    return 8;
226  return 0;
227}
228
229static int
230testMultithreadedPost ()
231{
232  struct MHD_Daemon *d;
233  CURL *c;
234  char buf[2048];
235  struct CBC cbc;
236  CURLcode errornum;
237  struct curl_httppost *pd;
238
239  cbc.buf = buf;
240  cbc.size = 2048;
241  cbc.pos = 0;
242  d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
243                        1081, NULL, NULL, &ahc_echo, NULL,
244			MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
245			MHD_OPTION_END);
246  if (d == NULL)
247    return 16;
248  c = curl_easy_init ();
249  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
250  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
251  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
252  pd = make_form ();
253  curl_easy_setopt (c, CURLOPT_HTTPPOST, pd);
254  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
255  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
256  if (oneone)
257    curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
258  else
259    curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
260  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 5L);
261  // NOTE: use of CONNECTTIMEOUT without also
262  //   setting NOSIGNAL results in really weird
263  //   crashes on my system!
264  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
265  if (CURLE_OK != (errornum = curl_easy_perform (c)))
266    {
267      fprintf (stderr,
268               "curl_easy_perform failed: `%s'\n",
269               curl_easy_strerror (errornum));
270      curl_easy_cleanup (c);
271      curl_formfree (pd);
272      MHD_stop_daemon (d);
273      return 32;
274    }
275  curl_easy_cleanup (c);
276  curl_formfree (pd);
277  MHD_stop_daemon (d);
278  if (cbc.pos != strlen ("/hello_world"))
279    return 64;
280  if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
281    return 128;
282  return 0;
283}
284
285static int
286testMultithreadedPoolPost ()
287{
288  struct MHD_Daemon *d;
289  CURL *c;
290  char buf[2048];
291  struct CBC cbc;
292  CURLcode errornum;
293  struct curl_httppost *pd;
294
295  cbc.buf = buf;
296  cbc.size = 2048;
297  cbc.pos = 0;
298  d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
299                        1081, NULL, NULL, &ahc_echo, NULL,
300                        MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT,
301			MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
302			MHD_OPTION_END);
303  if (d == NULL)
304    return 16;
305  c = curl_easy_init ();
306  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
307  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
308  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
309  pd = make_form ();
310  curl_easy_setopt (c, CURLOPT_HTTPPOST, pd);
311  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
312  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
313  if (oneone)
314    curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
315  else
316    curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
317  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 5L);
318  // NOTE: use of CONNECTTIMEOUT without also
319  //   setting NOSIGNAL results in really weird
320  //   crashes on my system!
321  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
322  if (CURLE_OK != (errornum = curl_easy_perform (c)))
323    {
324      fprintf (stderr,
325               "curl_easy_perform failed: `%s'\n",
326               curl_easy_strerror (errornum));
327      curl_easy_cleanup (c);
328      curl_formfree (pd);
329      MHD_stop_daemon (d);
330      return 32;
331    }
332  curl_easy_cleanup (c);
333  curl_formfree (pd);
334  MHD_stop_daemon (d);
335  if (cbc.pos != strlen ("/hello_world"))
336    return 64;
337  if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
338    return 128;
339  return 0;
340}
341
342static int
343testExternalPost ()
344{
345  struct MHD_Daemon *d;
346  CURL *c;
347  char buf[2048];
348  struct CBC cbc;
349  CURLM *multi;
350  CURLMcode mret;
351  fd_set rs;
352  fd_set ws;
353  fd_set es;
354  MHD_socket max;
355  int running;
356  struct CURLMsg *msg;
357  time_t start;
358  struct timeval tv;
359  struct curl_httppost *pd;
360
361  multi = NULL;
362  cbc.buf = buf;
363  cbc.size = 2048;
364  cbc.pos = 0;
365  d = MHD_start_daemon (MHD_USE_DEBUG,
366                        1082, NULL, NULL, &ahc_echo, NULL,
367			MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
368			MHD_OPTION_END);
369  if (d == NULL)
370    return 256;
371  c = curl_easy_init ();
372  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1082/hello_world");
373  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
374  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
375  pd = make_form ();
376  curl_easy_setopt (c, CURLOPT_HTTPPOST, pd);
377  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
378  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
379  if (oneone)
380    curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
381  else
382    curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
383  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
384  // NOTE: use of CONNECTTIMEOUT without also
385  //   setting NOSIGNAL results in really weird
386  //   crashes on my system!
387  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
388
389
390  multi = curl_multi_init ();
391  if (multi == NULL)
392    {
393      curl_easy_cleanup (c);
394      curl_formfree (pd);
395      MHD_stop_daemon (d);
396      return 512;
397    }
398  mret = curl_multi_add_handle (multi, c);
399  if (mret != CURLM_OK)
400    {
401      curl_multi_cleanup (multi);
402      curl_formfree (pd);
403      curl_easy_cleanup (c);
404      MHD_stop_daemon (d);
405      return 1024;
406    }
407  start = time (NULL);
408  while ((time (NULL) - start < 5) && (multi != NULL))
409    {
410      max = 0;
411      FD_ZERO (&rs);
412      FD_ZERO (&ws);
413      FD_ZERO (&es);
414      curl_multi_perform (multi, &running);
415      mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
416      if (mret != CURLM_OK)
417        {
418          curl_multi_remove_handle (multi, c);
419          curl_multi_cleanup (multi);
420          curl_easy_cleanup (c);
421          MHD_stop_daemon (d);
422          curl_formfree (pd);
423          return 2048;
424        }
425      if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
426        {
427          curl_multi_remove_handle (multi, c);
428          curl_multi_cleanup (multi);
429          curl_easy_cleanup (c);
430          curl_formfree (pd);
431          MHD_stop_daemon (d);
432          return 4096;
433        }
434      tv.tv_sec = 0;
435      tv.tv_usec = 1000;
436      select (max + 1, &rs, &ws, &es, &tv);
437      curl_multi_perform (multi, &running);
438      if (running == 0)
439        {
440          msg = curl_multi_info_read (multi, &running);
441          if (msg == NULL)
442            break;
443          if (msg->msg == CURLMSG_DONE)
444            {
445              if (msg->data.result != CURLE_OK)
446                printf ("%s failed at %s:%d: `%s'\n",
447                        "curl_multi_perform",
448                        __FILE__,
449                        __LINE__, curl_easy_strerror (msg->data.result));
450              curl_multi_remove_handle (multi, c);
451              curl_multi_cleanup (multi);
452              curl_easy_cleanup (c);
453              c = NULL;
454              multi = NULL;
455            }
456        }
457      MHD_run (d);
458    }
459  if (multi != NULL)
460    {
461      curl_multi_remove_handle (multi, c);
462      curl_easy_cleanup (c);
463      curl_multi_cleanup (multi);
464    }
465  curl_formfree (pd);
466  MHD_stop_daemon (d);
467  if (cbc.pos != strlen ("/hello_world"))
468    return 8192;
469  if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
470    return 16384;
471  return 0;
472}
473
474
475int
476main (int argc, char *const *argv)
477{
478  unsigned int errorCount = 0;
479
480#ifdef HAVE_GCRYPT_H
481  gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
482#ifdef GCRYCTL_INITIALIZATION_FINISHED
483  gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
484#endif
485#endif
486  oneone = (NULL != strrchr (argv[0], (int) '/')) ?
487    (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
488  if (0 != curl_global_init (CURL_GLOBAL_WIN32))
489    return 2;
490  errorCount += testInternalPost ();
491  errorCount += testMultithreadedPost ();
492  errorCount += testMultithreadedPoolPost ();
493  errorCount += testExternalPost ();
494  if (errorCount != 0)
495    fprintf (stderr, "Error (code: %u)\n", errorCount);
496  curl_global_cleanup ();
497  return errorCount != 0;       /* 0 == pass */
498}
499