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_get_chunked.c
23 * @brief  Testcase for libmicrohttpd GET operations with chunked content encoding
24 *         TODO:
25 *         - how to test that chunking was actually used?
26 *         - use CURLOPT_HEADERFUNCTION to validate
27 *           footer was sent
28 * @author Christian Grothoff
29 */
30
31#include "MHD_config.h"
32#include "platform.h"
33#include <curl/curl.h>
34#include <microhttpd.h>
35#include <stdlib.h>
36#include <string.h>
37#include <time.h>
38
39#ifndef WINDOWS
40#include <unistd.h>
41#endif
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
50struct CBC
51{
52  char *buf;
53  size_t pos;
54  size_t size;
55};
56
57static size_t
58copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
59{
60  struct CBC *cbc = ctx;
61
62  if (cbc->pos + size * nmemb > cbc->size)
63    return 0;                   /* overflow */
64  memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
65  cbc->pos += size * nmemb;
66  return size * nmemb;
67}
68
69/**
70 * MHD content reader callback that returns
71 * data in chunks.
72 */
73static ssize_t
74crc (void *cls, uint64_t pos, char *buf, size_t max)
75{
76  struct MHD_Response **responseptr = cls;
77
78  if (pos == 128 * 10)
79    {
80      MHD_add_response_header (*responseptr, "Footer", "working");
81      return MHD_CONTENT_READER_END_OF_STREAM;
82    }
83  if (max < 128)
84    abort ();                   /* should not happen in this testcase... */
85  memset (buf, 'A' + (pos / 128), 128);
86  return 128;
87}
88
89/**
90 * Dummy function that does nothing.
91 */
92static void
93crcf (void *ptr)
94{
95  free (ptr);
96}
97
98static int
99ahc_echo (void *cls,
100          struct MHD_Connection *connection,
101          const char *url,
102          const char *method,
103          const char *version,
104          const char *upload_data, size_t *upload_data_size, void **ptr)
105{
106  static int aptr;
107  const char *me = cls;
108  struct MHD_Response *response;
109  struct MHD_Response **responseptr;
110  int ret;
111
112  if (0 != strcmp (me, method))
113    return MHD_NO;              /* unexpected method */
114  if (&aptr != *ptr)
115    {
116      /* do never respond on first call */
117      *ptr = &aptr;
118      return MHD_YES;
119    }
120  responseptr = malloc (sizeof (struct MHD_Response *));
121  if (responseptr == NULL)
122    return MHD_NO;
123  response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN,
124                                                1024,
125                                                &crc, responseptr, &crcf);
126  *responseptr = response;
127  ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
128  MHD_destroy_response (response);
129  return ret;
130}
131
132static int
133validate (struct CBC cbc, int ebase)
134{
135  int i;
136  char buf[128];
137
138  if (cbc.pos != 128 * 10)
139    return ebase;
140
141  for (i = 0; i < 10; i++)
142    {
143      memset (buf, 'A' + i, 128);
144      if (0 != memcmp (buf, &cbc.buf[i * 128], 128))
145        {
146          fprintf (stderr,
147                   "Got  `%.*s'\nWant `%.*s'\n",
148                   128, buf, 128, &cbc.buf[i * 128]);
149          return ebase * 2;
150        }
151    }
152  return 0;
153}
154
155static int
156testInternalGet ()
157{
158  struct MHD_Daemon *d;
159  CURL *c;
160  char buf[2048];
161  struct CBC cbc;
162  CURLcode errornum;
163
164  cbc.buf = buf;
165  cbc.size = 2048;
166  cbc.pos = 0;
167  d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
168                        1080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
169  if (d == NULL)
170    return 1;
171  c = curl_easy_init ();
172  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1080/hello_world");
173  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
174  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
175  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
176  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
177  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
178  curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
179  // NOTE: use of CONNECTTIMEOUT without also
180  //   setting NOSIGNAL results in really weird
181  //   crashes on my system!
182  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
183  if (CURLE_OK != (errornum = curl_easy_perform (c)))
184    {
185      fprintf (stderr,
186               "curl_easy_perform failed: `%s'\n",
187               curl_easy_strerror (errornum));
188      curl_easy_cleanup (c);
189      MHD_stop_daemon (d);
190      return 2;
191    }
192  curl_easy_cleanup (c);
193  MHD_stop_daemon (d);
194  return validate (cbc, 4);
195}
196
197static int
198testMultithreadedGet ()
199{
200  struct MHD_Daemon *d;
201  CURL *c;
202  char buf[2048];
203  struct CBC cbc;
204  CURLcode errornum;
205
206  cbc.buf = buf;
207  cbc.size = 2048;
208  cbc.pos = 0;
209  d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
210                        1081, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
211  if (d == NULL)
212    return 16;
213  c = curl_easy_init ();
214  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
215  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
216  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
217  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
218  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
219  curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
220  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
221  // NOTE: use of CONNECTTIMEOUT without also
222  //   setting NOSIGNAL results in really weird
223  //   crashes on my system!
224  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
225  if (CURLE_OK != (errornum = curl_easy_perform (c)))
226    {
227      fprintf (stderr,
228               "curl_easy_perform failed: `%s'\n",
229               curl_easy_strerror (errornum));
230      curl_easy_cleanup (c);
231      MHD_stop_daemon (d);
232      return 32;
233    }
234  curl_easy_cleanup (c);
235  MHD_stop_daemon (d);
236  return validate (cbc, 64);
237}
238
239static int
240testMultithreadedPoolGet ()
241{
242  struct MHD_Daemon *d;
243  CURL *c;
244  char buf[2048];
245  struct CBC cbc;
246  CURLcode errornum;
247
248  cbc.buf = buf;
249  cbc.size = 2048;
250  cbc.pos = 0;
251  d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
252                        1081, NULL, NULL, &ahc_echo, "GET",
253                        MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END);
254  if (d == NULL)
255    return 16;
256  c = curl_easy_init ();
257  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world");
258  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
259  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
260  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
261  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
262  curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
263  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
264  // NOTE: use of CONNECTTIMEOUT without also
265  //   setting NOSIGNAL results in really weird
266  //   crashes on my system!
267  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
268  if (CURLE_OK != (errornum = curl_easy_perform (c)))
269    {
270      fprintf (stderr,
271               "curl_easy_perform failed: `%s'\n",
272               curl_easy_strerror (errornum));
273      curl_easy_cleanup (c);
274      MHD_stop_daemon (d);
275      return 32;
276    }
277  curl_easy_cleanup (c);
278  MHD_stop_daemon (d);
279  return validate (cbc, 64);
280}
281
282static int
283testExternalGet ()
284{
285  struct MHD_Daemon *d;
286  CURL *c;
287  char buf[2048];
288  struct CBC cbc;
289  CURLM *multi;
290  CURLMcode mret;
291  fd_set rs;
292  fd_set ws;
293  fd_set es;
294  MHD_socket max;
295  int running;
296  struct CURLMsg *msg;
297  time_t start;
298  struct timeval tv;
299
300  multi = NULL;
301  cbc.buf = buf;
302  cbc.size = 2048;
303  cbc.pos = 0;
304  d = MHD_start_daemon (MHD_USE_DEBUG,
305                        1082, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
306  if (d == NULL)
307    return 256;
308  c = curl_easy_init ();
309  curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1082/hello_world");
310  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
311  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
312  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
313  curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
314  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
315  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 5L);
316  // NOTE: use of CONNECTTIMEOUT without also
317  //   setting NOSIGNAL results in really weird
318  //   crashes on my system!
319  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
320
321
322  multi = curl_multi_init ();
323  if (multi == NULL)
324    {
325      curl_easy_cleanup (c);
326      MHD_stop_daemon (d);
327      return 512;
328    }
329  mret = curl_multi_add_handle (multi, c);
330  if (mret != CURLM_OK)
331    {
332      curl_multi_cleanup (multi);
333      curl_easy_cleanup (c);
334      MHD_stop_daemon (d);
335      return 1024;
336    }
337  start = time (NULL);
338  while ((time (NULL) - start < 5) && (multi != NULL))
339    {
340      max = 0;
341      FD_ZERO (&rs);
342      FD_ZERO (&ws);
343      FD_ZERO (&es);
344      curl_multi_perform (multi, &running);
345      mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
346      if (mret != CURLM_OK)
347        {
348          curl_multi_remove_handle (multi, c);
349          curl_multi_cleanup (multi);
350          curl_easy_cleanup (c);
351          MHD_stop_daemon (d);
352          return 2048;
353        }
354      if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
355        {
356          curl_multi_remove_handle (multi, c);
357          curl_multi_cleanup (multi);
358          curl_easy_cleanup (c);
359          MHD_stop_daemon (d);
360          return 4096;
361        }
362      tv.tv_sec = 0;
363      tv.tv_usec = 1000;
364      select (max + 1, &rs, &ws, &es, &tv);
365      curl_multi_perform (multi, &running);
366      if (running == 0)
367        {
368          msg = curl_multi_info_read (multi, &running);
369          if (msg == NULL)
370            break;
371          if (msg->msg == CURLMSG_DONE)
372            {
373              if (msg->data.result != CURLE_OK)
374                printf ("%s failed at %s:%d: `%s'\n",
375                        "curl_multi_perform",
376                        __FILE__,
377                        __LINE__, curl_easy_strerror (msg->data.result));
378              curl_multi_remove_handle (multi, c);
379              curl_multi_cleanup (multi);
380              curl_easy_cleanup (c);
381              c = NULL;
382              multi = NULL;
383            }
384        }
385      MHD_run (d);
386    }
387  if (multi != NULL)
388    {
389      curl_multi_remove_handle (multi, c);
390      curl_easy_cleanup (c);
391      curl_multi_cleanup (multi);
392    }
393  MHD_stop_daemon (d);
394  return validate (cbc, 8192);
395}
396
397
398
399int
400main (int argc, char *const *argv)
401{
402  unsigned int errorCount = 0;
403
404  if (0 != curl_global_init (CURL_GLOBAL_WIN32))
405    return 2;
406  errorCount += testInternalGet ();
407  errorCount += testMultithreadedGet ();
408  errorCount += testMultithreadedPoolGet ();
409  errorCount += testExternalGet ();
410  if (errorCount != 0)
411    fprintf (stderr, "Error (code: %u)\n", errorCount);
412  curl_global_cleanup ();
413  return errorCount != 0;       /* 0 == pass */
414}
415