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 daemontest_put_chunked.c
23 * @brief Testcase for libmicrohttpd PUT operations with chunked encoding
24 *        for the upload data
25 * @author Christian Grothoff
26 */
27
28#include "MHD_config.h"
29#include "platform.h"
30#include <curl/curl.h>
31#include <microhttpd.h>
32#include <stdlib.h>
33#include <string.h>
34#include <time.h>
35
36#ifndef WINDOWS
37#include <unistd.h>
38#endif
39
40#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
41#undef CPU_COUNT
42#endif
43#if !defined(CPU_COUNT)
44#define CPU_COUNT 2
45#endif
46
47struct CBC
48{
49  char *buf;
50  size_t pos;
51  size_t size;
52};
53
54static size_t
55putBuffer (void *stream, size_t size, size_t nmemb, void *ptr)
56{
57  unsigned int *pos = ptr;
58  unsigned int wrt;
59
60  wrt = size * nmemb;
61  if (wrt > 8 - (*pos))
62    wrt = 8 - (*pos);
63  if (wrt > 4)
64    wrt = 4;                    /* only send half at first => force multiple chunks! */
65  memcpy (stream, &("Hello123"[*pos]), wrt);
66  (*pos) += wrt;
67  return wrt;
68}
69
70static size_t
71copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
72{
73  struct CBC *cbc = ctx;
74
75  if (cbc->pos + size * nmemb > cbc->size)
76    return 0;                   /* overflow */
77  memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
78  cbc->pos += size * nmemb;
79  return size * nmemb;
80}
81
82static int
83ahc_echo (void *cls,
84          struct MHD_Connection *connection,
85          const char *url,
86          const char *method,
87          const char *version,
88          const char *upload_data, size_t *upload_data_size,
89          void **unused)
90{
91  int *done = cls;
92  struct MHD_Response *response;
93  int ret;
94  int have;
95
96  if (0 != strcmp ("PUT", method))
97    return MHD_NO;              /* unexpected method */
98  if ((*done) < 8)
99    {
100      have = *upload_data_size;
101      if (have + *done > 8)
102        {
103          printf ("Invalid upload data `%8s'!\n", upload_data);
104          return MHD_NO;
105        }
106      if (0 == memcmp (upload_data, &"Hello123"[*done], have))
107        {
108          *done += have;
109          *upload_data_size = 0;
110        }
111      else
112        {
113          printf ("Invalid upload data `%8s'!\n", upload_data);
114          return MHD_NO;
115        }
116#if 0
117      fprintf (stderr, "Not ready for response: %u/%u\n", *done, 8);
118#endif
119      return MHD_YES;
120    }
121  response = MHD_create_response_from_buffer (strlen (url),
122					      (void *) url,
123					      MHD_RESPMEM_MUST_COPY);
124  ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
125  MHD_destroy_response (response);
126  return ret;
127}
128
129
130static int
131testInternalPut ()
132{
133  struct MHD_Daemon *d;
134  CURL *c;
135  char buf[2048];
136  struct CBC cbc;
137  unsigned int pos = 0;
138  int done_flag = 0;
139  CURLcode errornum;
140
141  cbc.buf = buf;
142  cbc.size = 2048;
143  cbc.pos = 0;
144  d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
145                        11080,
146                        NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
147  if (d == NULL)
148    return 1;
149  c = curl_easy_init ();
150  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:11080/hello_world");
151  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
152  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
153  curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
154  curl_easy_setopt (c, CURLOPT_READDATA, &pos);
155  curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
156  /*
157     // by not giving the file size, we force chunking!
158     curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
159   */
160  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
161  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
162  curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
163  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
164  // NOTE: use of CONNECTTIMEOUT without also
165  //   setting NOSIGNAL results in really weird
166  //   crashes on my system!
167  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
168  if (CURLE_OK != (errornum = curl_easy_perform (c)))
169    {
170      fprintf (stderr,
171               "curl_easy_perform failed: `%s'\n",
172               curl_easy_strerror (errornum));
173      curl_easy_cleanup (c);
174      MHD_stop_daemon (d);
175      return 2;
176    }
177  curl_easy_cleanup (c);
178  MHD_stop_daemon (d);
179  if (cbc.pos != strlen ("/hello_world"))
180    return 4;
181  if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
182    return 8;
183  return 0;
184}
185
186static int
187testMultithreadedPut ()
188{
189  struct MHD_Daemon *d;
190  CURL *c;
191  char buf[2048];
192  struct CBC cbc;
193  unsigned int pos = 0;
194  int done_flag = 0;
195  CURLcode errornum;
196
197  cbc.buf = buf;
198  cbc.size = 2048;
199  cbc.pos = 0;
200  d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
201                        11081,
202                        NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
203  if (d == NULL)
204    return 16;
205  c = curl_easy_init ();
206  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:11081/hello_world");
207  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
208  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
209  curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
210  curl_easy_setopt (c, CURLOPT_READDATA, &pos);
211  curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
212  /*
213     // by not giving the file size, we force chunking!
214     curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
215   */
216  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
217  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
218  curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
219  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
220  // NOTE: use of CONNECTTIMEOUT without also
221  //   setting NOSIGNAL results in really weird
222  //   crashes on my system!
223  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
224  if (CURLE_OK != (errornum = curl_easy_perform (c)))
225    {
226      fprintf (stderr,
227               "curl_easy_perform failed: `%s'\n",
228               curl_easy_strerror (errornum));
229      curl_easy_cleanup (c);
230      MHD_stop_daemon (d);
231      return 32;
232    }
233  curl_easy_cleanup (c);
234  MHD_stop_daemon (d);
235  if (cbc.pos != strlen ("/hello_world"))
236    return 64;
237  if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
238    return 128;
239
240  return 0;
241}
242
243static int
244testMultithreadedPoolPut ()
245{
246  struct MHD_Daemon *d;
247  CURL *c;
248  char buf[2048];
249  struct CBC cbc;
250  unsigned int pos = 0;
251  int done_flag = 0;
252  CURLcode errornum;
253
254  cbc.buf = buf;
255  cbc.size = 2048;
256  cbc.pos = 0;
257  d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
258                        11081,
259                        NULL, NULL, &ahc_echo, &done_flag,
260                        MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END);
261  if (d == NULL)
262    return 16;
263  c = curl_easy_init ();
264  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:11081/hello_world");
265  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
266  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
267  curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
268  curl_easy_setopt (c, CURLOPT_READDATA, &pos);
269  curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
270  /*
271     // by not giving the file size, we force chunking!
272     curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
273   */
274  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
275  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
276  curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
277  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
278  // NOTE: use of CONNECTTIMEOUT without also
279  //   setting NOSIGNAL results in really weird
280  //   crashes on my system!
281  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
282  if (CURLE_OK != (errornum = curl_easy_perform (c)))
283    {
284      fprintf (stderr,
285               "curl_easy_perform failed: `%s'\n",
286               curl_easy_strerror (errornum));
287      curl_easy_cleanup (c);
288      MHD_stop_daemon (d);
289      return 32;
290    }
291  curl_easy_cleanup (c);
292  MHD_stop_daemon (d);
293  if (cbc.pos != strlen ("/hello_world"))
294    return 64;
295  if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
296    return 128;
297
298  return 0;
299}
300
301
302static int
303testExternalPut ()
304{
305  struct MHD_Daemon *d;
306  CURL *c;
307  char buf[2048];
308  struct CBC cbc;
309  CURLM *multi;
310  CURLMcode mret;
311  fd_set rs;
312  fd_set ws;
313  fd_set es;
314  MHD_socket max;
315  int running;
316  struct CURLMsg *msg;
317  time_t start;
318  struct timeval tv;
319  unsigned int pos = 0;
320  int done_flag = 0;
321
322  multi = NULL;
323  cbc.buf = buf;
324  cbc.size = 2048;
325  cbc.pos = 0;
326  d = MHD_start_daemon (MHD_USE_DEBUG,
327                        11082,
328                        NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
329  if (d == NULL)
330    return 256;
331  c = curl_easy_init ();
332  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:11082/hello_world");
333  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
334  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
335  curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
336  curl_easy_setopt (c, CURLOPT_READDATA, &pos);
337  curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
338  /*
339     // by not giving the file size, we force chunking!
340     curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
341   */
342  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
343  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
344  curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
345  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
346  // NOTE: use of CONNECTTIMEOUT without also
347  //   setting NOSIGNAL results in really weird
348  //   crashes on my system!
349  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
350
351
352  multi = curl_multi_init ();
353  if (multi == NULL)
354    {
355      curl_easy_cleanup (c);
356      MHD_stop_daemon (d);
357      return 512;
358    }
359  mret = curl_multi_add_handle (multi, c);
360  if (mret != CURLM_OK)
361    {
362      curl_multi_cleanup (multi);
363      curl_easy_cleanup (c);
364      MHD_stop_daemon (d);
365      return 1024;
366    }
367  start = time (NULL);
368  while ((time (NULL) - start < 5) && (multi != NULL))
369    {
370      max = 0;
371      FD_ZERO (&rs);
372      FD_ZERO (&ws);
373      FD_ZERO (&es);
374      curl_multi_perform (multi, &running);
375      mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
376      if (mret != CURLM_OK)
377        {
378          curl_multi_remove_handle (multi, c);
379          curl_multi_cleanup (multi);
380          curl_easy_cleanup (c);
381          MHD_stop_daemon (d);
382          return 2048;
383        }
384      if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
385        {
386          curl_multi_remove_handle (multi, c);
387          curl_multi_cleanup (multi);
388          curl_easy_cleanup (c);
389          MHD_stop_daemon (d);
390          return 4096;
391        }
392      tv.tv_sec = 0;
393      tv.tv_usec = 1000;
394      select (max + 1, &rs, &ws, &es, &tv);
395      curl_multi_perform (multi, &running);
396      if (running == 0)
397        {
398          msg = curl_multi_info_read (multi, &running);
399          if (msg == NULL)
400            break;
401          if (msg->msg == CURLMSG_DONE)
402            {
403              if (msg->data.result != CURLE_OK)
404                printf ("%s failed at %s:%d: `%s'\n",
405                        "curl_multi_perform",
406                        __FILE__,
407                        __LINE__, curl_easy_strerror (msg->data.result));
408              curl_multi_remove_handle (multi, c);
409              curl_multi_cleanup (multi);
410              curl_easy_cleanup (c);
411              c = NULL;
412              multi = NULL;
413            }
414        }
415      MHD_run (d);
416    }
417  if (multi != NULL)
418    {
419      curl_multi_remove_handle (multi, c);
420      curl_easy_cleanup (c);
421      curl_multi_cleanup (multi);
422    }
423  MHD_stop_daemon (d);
424  if (cbc.pos != strlen ("/hello_world"))
425    return 8192;
426  if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
427    return 16384;
428  return 0;
429}
430
431
432
433int
434main (int argc, char *const *argv)
435{
436  unsigned int errorCount = 0;
437
438  if (0 != curl_global_init (CURL_GLOBAL_WIN32))
439    return 2;
440  errorCount += testInternalPut ();
441  errorCount += testMultithreadedPut ();
442  errorCount += testMultithreadedPoolPut ();
443  errorCount += testExternalPut ();
444  if (errorCount != 0)
445    fprintf (stderr, "Error (code: %u)\n", errorCount);
446  curl_global_cleanup ();
447  return errorCount != 0;       /* 0 == pass */
448}
449