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 
50 struct CBC
51 {
52   char *buf;
53   size_t pos;
54   size_t size;
55 };
56 
57 static size_t
copyBuffer(void * ptr,size_t size,size_t nmemb,void * ctx)58 copyBuffer (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  */
73 static ssize_t
crc(void * cls,uint64_t pos,char * buf,size_t max)74 crc (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  */
92 static void
crcf(void * ptr)93 crcf (void *ptr)
94 {
95   free (ptr);
96 }
97 
98 static int
ahc_echo(void * cls,struct MHD_Connection * connection,const char * url,const char * method,const char * version,const char * upload_data,size_t * upload_data_size,void ** ptr)99 ahc_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 
132 static int
validate(struct CBC cbc,int ebase)133 validate (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 
155 static int
testInternalGet()156 testInternalGet ()
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 
197 static int
testMultithreadedGet()198 testMultithreadedGet ()
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 
239 static int
testMultithreadedPoolGet()240 testMultithreadedPoolGet ()
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 
282 static int
testExternalGet()283 testExternalGet ()
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 
399 int
main(int argc,char * const * argv)400 main (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