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 
47 struct CBC
48 {
49   char *buf;
50   size_t pos;
51   size_t size;
52 };
53 
54 static size_t
putBuffer(void * stream,size_t size,size_t nmemb,void * ptr)55 putBuffer (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 
70 static size_t
copyBuffer(void * ptr,size_t size,size_t nmemb,void * ctx)71 copyBuffer (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 
82 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 ** unused)83 ahc_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 
130 static int
testInternalPut()131 testInternalPut ()
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 
186 static int
testMultithreadedPut()187 testMultithreadedPut ()
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 
243 static int
testMultithreadedPoolPut()244 testMultithreadedPoolPut ()
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 
302 static int
testExternalPut()303 testExternalPut ()
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 
433 int
main(int argc,char * const * argv)434 main (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