1 /*
2      This file is part of libmicrohttpd
3      Copyright (C) 2007 Christian Grothoff
4      Copyright (C) 2014-2021 Evgeny Grin (Karlson2k)
5 
6      libmicrohttpd is free software; you can redistribute it and/or modify
7      it under the terms of the GNU General Public License as published
8      by the Free Software Foundation; either version 2, or (at your
9      option) any later version.
10 
11      libmicrohttpd is distributed in the hope that it will be useful, but
12      WITHOUT ANY WARRANTY; without even the implied warranty of
13      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14      General Public License for more details.
15 
16      You should have received a copy of the GNU General Public License
17      along with libmicrohttpd; see the file COPYING.  If not, write to the
18      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19      Boston, MA 02110-1301, USA.
20 */
21 
22 /**
23  * @file daemontest_put.c
24  * @brief  Testcase for libmicrohttpd PUT operations
25  * @author Christian Grothoff
26  * @author Karlson2k (Evgeny Grin)
27  */
28 
29 #include "MHD_config.h"
30 #include "platform.h"
31 #include <curl/curl.h>
32 #include <microhttpd.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36 #include "mhd_has_in_name.h"
37 
38 #ifndef WINDOWS
39 #include <unistd.h>
40 #endif
41 
42 #if defined(MHD_CPU_COUNT) && (MHD_CPU_COUNT + 0) < 2
43 #undef MHD_CPU_COUNT
44 #endif
45 #if ! defined(MHD_CPU_COUNT)
46 #define MHD_CPU_COUNT 2
47 #endif
48 
49 static int oneone;
50 
51 struct CBC
52 {
53   char *buf;
54   size_t pos;
55   size_t size;
56 };
57 
58 static size_t
putBuffer(void * stream,size_t size,size_t nmemb,void * ptr)59 putBuffer (void *stream, size_t size, size_t nmemb, void *ptr)
60 {
61   unsigned int *pos = ptr;
62   unsigned int wrt;
63 
64   wrt = size * nmemb;
65   if (wrt > 8 - (*pos))
66     wrt = 8 - (*pos);
67   memcpy (stream, &("Hello123"[*pos]), wrt);
68   (*pos) += wrt;
69   return wrt;
70 }
71 
72 
73 static size_t
copyBuffer(void * ptr,size_t size,size_t nmemb,void * ctx)74 copyBuffer (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 static enum MHD_Result
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)87 ahc_echo (void *cls,
88           struct MHD_Connection *connection,
89           const char *url,
90           const char *method,
91           const char *version,
92           const char *upload_data, size_t *upload_data_size,
93           void **unused)
94 {
95   int *done = cls;
96   struct MHD_Response *response;
97   enum MHD_Result ret;
98   (void) version; (void) unused;   /* Unused. Silent compiler warning. */
99 
100   if (0 != strcmp ("PUT", method))
101     return MHD_NO;              /* unexpected method */
102   if ((*done) == 0)
103   {
104     if (*upload_data_size != 8)
105       return MHD_YES;           /* not yet ready */
106     if (0 == memcmp (upload_data, "Hello123", 8))
107     {
108       *upload_data_size = 0;
109     }
110     else
111     {
112       printf ("Invalid upload data `%8s'!\n", upload_data);
113       return MHD_NO;
114     }
115     *done = 1;
116     return MHD_YES;
117   }
118   response = MHD_create_response_from_buffer (strlen (url), (void *) url,
119                                               MHD_RESPMEM_MUST_COPY);
120   ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
121   MHD_destroy_response (response);
122   return ret;
123 }
124 
125 
126 static int
testInternalPut()127 testInternalPut ()
128 {
129   struct MHD_Daemon *d;
130   CURL *c;
131   char buf[2048];
132   struct CBC cbc;
133   unsigned int pos = 0;
134   int done_flag = 0;
135   CURLcode errornum;
136   int port;
137 
138   if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT))
139     port = 0;
140   else
141   {
142     port = 1450;
143     if (oneone)
144       port += 10;
145   }
146 
147   cbc.buf = buf;
148   cbc.size = 2048;
149   cbc.pos = 0;
150   d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG,
151                         port,
152                         NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
153   if (d == NULL)
154     return 1;
155   if (0 == port)
156   {
157     const union MHD_DaemonInfo *dinfo;
158     dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
159     if ((NULL == dinfo) || (0 == dinfo->port) )
160     {
161       MHD_stop_daemon (d); return 32;
162     }
163     port = (int) dinfo->port;
164   }
165   c = curl_easy_init ();
166   curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world");
167   curl_easy_setopt (c, CURLOPT_PORT, (long) port);
168   curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
169   curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
170   curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
171   curl_easy_setopt (c, CURLOPT_READDATA, &pos);
172   curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
173   curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
174   curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L);
175   curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
176   if (oneone)
177     curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
178   else
179     curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
180   curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
181   /* NOTE: use of CONNECTTIMEOUT without also
182    *   setting NOSIGNAL results in really weird
183    *   crashes on my system! */
184   curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L);
185   if (CURLE_OK != (errornum = curl_easy_perform (c)))
186   {
187     fprintf (stderr,
188              "curl_easy_perform failed: `%s'\n",
189              curl_easy_strerror (errornum));
190     curl_easy_cleanup (c);
191     MHD_stop_daemon (d);
192     return 2;
193   }
194   curl_easy_cleanup (c);
195   MHD_stop_daemon (d);
196   if (cbc.pos != strlen ("/hello_world"))
197     return 4;
198   if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
199     return 8;
200   return 0;
201 }
202 
203 
204 static int
testMultithreadedPut()205 testMultithreadedPut ()
206 {
207   struct MHD_Daemon *d;
208   CURL *c;
209   char buf[2048];
210   struct CBC cbc;
211   unsigned int pos = 0;
212   int done_flag = 0;
213   CURLcode errornum;
214   int port;
215 
216   if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT))
217     port = 0;
218   else
219   {
220     port = 1451;
221     if (oneone)
222       port += 10;
223   }
224 
225   cbc.buf = buf;
226   cbc.size = 2048;
227   cbc.pos = 0;
228   d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION
229                         | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG,
230                         port,
231                         NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
232   if (d == NULL)
233     return 16;
234   if (0 == port)
235   {
236     const union MHD_DaemonInfo *dinfo;
237     dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
238     if ((NULL == dinfo) || (0 == dinfo->port) )
239     {
240       MHD_stop_daemon (d); return 32;
241     }
242     port = (int) dinfo->port;
243   }
244   c = curl_easy_init ();
245   curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world");
246   curl_easy_setopt (c, CURLOPT_PORT, (long) port);
247   curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
248   curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
249   curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
250   curl_easy_setopt (c, CURLOPT_READDATA, &pos);
251   curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
252   curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
253   curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L);
254   curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
255   if (oneone)
256     curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
257   else
258     curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
259   curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
260   /* NOTE: use of CONNECTTIMEOUT without also
261    *   setting NOSIGNAL results in really weird
262    *   crashes on my system! */
263   curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L);
264   if (CURLE_OK != (errornum = curl_easy_perform (c)))
265   {
266     fprintf (stderr,
267              "curl_easy_perform failed: `%s'\n",
268              curl_easy_strerror (errornum));
269     curl_easy_cleanup (c);
270     MHD_stop_daemon (d);
271     return 32;
272   }
273   curl_easy_cleanup (c);
274   MHD_stop_daemon (d);
275   if (cbc.pos != strlen ("/hello_world"))
276     return 64;
277   if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
278     return 128;
279 
280   return 0;
281 }
282 
283 
284 static int
testMultithreadedPoolPut()285 testMultithreadedPoolPut ()
286 {
287   struct MHD_Daemon *d;
288   CURL *c;
289   char buf[2048];
290   struct CBC cbc;
291   unsigned int pos = 0;
292   int done_flag = 0;
293   CURLcode errornum;
294   int port;
295 
296   if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT))
297     port = 0;
298   else
299   {
300     port = 1452;
301     if (oneone)
302       port += 10;
303   }
304 
305   cbc.buf = buf;
306   cbc.size = 2048;
307   cbc.pos = 0;
308   d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG,
309                         port,
310                         NULL, NULL, &ahc_echo, &done_flag,
311                         MHD_OPTION_THREAD_POOL_SIZE, MHD_CPU_COUNT,
312                         MHD_OPTION_END);
313   if (d == NULL)
314     return 16;
315   if (0 == port)
316   {
317     const union MHD_DaemonInfo *dinfo;
318     dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
319     if ((NULL == dinfo) || (0 == dinfo->port) )
320     {
321       MHD_stop_daemon (d); return 32;
322     }
323     port = (int) dinfo->port;
324   }
325   c = curl_easy_init ();
326   curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world");
327   curl_easy_setopt (c, CURLOPT_PORT, (long) port);
328   curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
329   curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
330   curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
331   curl_easy_setopt (c, CURLOPT_READDATA, &pos);
332   curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
333   curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
334   curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L);
335   curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
336   if (oneone)
337     curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
338   else
339     curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
340   curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
341   /* NOTE: use of CONNECTTIMEOUT without also
342    *   setting NOSIGNAL results in really weird
343    *   crashes on my system! */
344   curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L);
345   if (CURLE_OK != (errornum = curl_easy_perform (c)))
346   {
347     fprintf (stderr,
348              "curl_easy_perform failed: `%s'\n",
349              curl_easy_strerror (errornum));
350     curl_easy_cleanup (c);
351     MHD_stop_daemon (d);
352     return 32;
353   }
354   curl_easy_cleanup (c);
355   MHD_stop_daemon (d);
356   if (cbc.pos != strlen ("/hello_world"))
357     return 64;
358   if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
359     return 128;
360 
361   return 0;
362 }
363 
364 
365 static int
testExternalPut()366 testExternalPut ()
367 {
368   struct MHD_Daemon *d;
369   CURL *c;
370   char buf[2048];
371   struct CBC cbc;
372   CURLM *multi;
373   CURLMcode mret;
374   fd_set rs;
375   fd_set ws;
376   fd_set es;
377   MHD_socket maxsock;
378   int maxposixs; /* Max socket number unused on W32 */
379   int running;
380   struct CURLMsg *msg;
381   time_t start;
382   struct timeval tv;
383   unsigned int pos = 0;
384   int done_flag = 0;
385   int port;
386 
387   if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT))
388     port = 0;
389   else
390   {
391     port = 1453;
392     if (oneone)
393       port += 10;
394   }
395 
396   multi = NULL;
397   cbc.buf = buf;
398   cbc.size = 2048;
399   cbc.pos = 0;
400   d = MHD_start_daemon (MHD_USE_ERROR_LOG,
401                         port,
402                         NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
403   if (d == NULL)
404     return 256;
405   if (0 == port)
406   {
407     const union MHD_DaemonInfo *dinfo;
408     dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
409     if ((NULL == dinfo) || (0 == dinfo->port) )
410     {
411       MHD_stop_daemon (d); return 32;
412     }
413     port = (int) dinfo->port;
414   }
415   c = curl_easy_init ();
416   curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world");
417   curl_easy_setopt (c, CURLOPT_PORT, (long) port);
418   curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
419   curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
420   curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
421   curl_easy_setopt (c, CURLOPT_READDATA, &pos);
422   curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
423   curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
424   curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L);
425   curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
426   if (oneone)
427     curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
428   else
429     curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
430   curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
431   /* NOTE: use of CONNECTTIMEOUT without also
432    *   setting NOSIGNAL results in really weird
433    *   crashes on my system! */
434   curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L);
435 
436 
437   multi = curl_multi_init ();
438   if (multi == NULL)
439   {
440     curl_easy_cleanup (c);
441     MHD_stop_daemon (d);
442     return 512;
443   }
444   mret = curl_multi_add_handle (multi, c);
445   if (mret != CURLM_OK)
446   {
447     curl_multi_cleanup (multi);
448     curl_easy_cleanup (c);
449     MHD_stop_daemon (d);
450     return 1024;
451   }
452   start = time (NULL);
453   while ((time (NULL) - start < 5) && (multi != NULL))
454   {
455     maxsock = MHD_INVALID_SOCKET;
456     maxposixs = -1;
457     FD_ZERO (&rs);
458     FD_ZERO (&ws);
459     FD_ZERO (&es);
460     curl_multi_perform (multi, &running);
461     mret = curl_multi_fdset (multi, &rs, &ws, &es, &maxposixs);
462     if (mret != CURLM_OK)
463     {
464       curl_multi_remove_handle (multi, c);
465       curl_multi_cleanup (multi);
466       curl_easy_cleanup (c);
467       MHD_stop_daemon (d);
468       return 2048;
469     }
470     if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &maxsock))
471     {
472       curl_multi_remove_handle (multi, c);
473       curl_multi_cleanup (multi);
474       curl_easy_cleanup (c);
475       MHD_stop_daemon (d);
476       return 4096;
477     }
478 #ifdef MHD_POSIX_SOCKETS
479     if (maxsock > maxposixs)
480       maxposixs = maxsock;
481 #endif /* MHD_POSIX_SOCKETS */
482     tv.tv_sec = 0;
483     tv.tv_usec = 1000;
484     if (-1 == select (maxposixs + 1, &rs, &ws, &es, &tv))
485     {
486 #ifdef MHD_POSIX_SOCKETS
487       if (EINTR != errno)
488       {
489         fprintf (stderr, "Unexpected select() error: %d. Line: %d\n",
490                  (int) errno, __LINE__);
491         fflush (stderr);
492         exit (99);
493       }
494 #else
495       if ((WSAEINVAL != WSAGetLastError ()) ||
496           (0 != rs.fd_count) || (0 != ws.fd_count) || (0 != es.fd_count) )
497       {
498         fprintf (stderr, "Unexpected select() error: %d. Line: %d\n",
499                  (int) WSAGetLastError (), __LINE__);
500         fflush (stderr);
501         exit (99);
502       }
503       Sleep (1);
504 #endif
505     }
506     curl_multi_perform (multi, &running);
507     if (0 == running)
508     {
509       int pending;
510       int curl_fine = 0;
511       while (NULL != (msg = curl_multi_info_read (multi, &pending)))
512       {
513         if (msg->msg == CURLMSG_DONE)
514         {
515           if (msg->data.result == CURLE_OK)
516             curl_fine = 1;
517           else
518           {
519             fprintf (stderr,
520                      "%s failed at %s:%d: `%s'\n",
521                      "curl_multi_perform",
522                      __FILE__,
523                      __LINE__, curl_easy_strerror (msg->data.result));
524             abort ();
525           }
526         }
527       }
528       if (! curl_fine)
529       {
530         fprintf (stderr, "libcurl haven't returned OK code\n");
531         abort ();
532       }
533       curl_multi_remove_handle (multi, c);
534       curl_multi_cleanup (multi);
535       curl_easy_cleanup (c);
536       c = NULL;
537       multi = NULL;
538     }
539     MHD_run (d);
540   }
541   if (multi != NULL)
542   {
543     curl_multi_remove_handle (multi, c);
544     curl_easy_cleanup (c);
545     curl_multi_cleanup (multi);
546   }
547   MHD_stop_daemon (d);
548   if (cbc.pos != strlen ("/hello_world"))
549     return 8192;
550   if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
551     return 16384;
552   return 0;
553 }
554 
555 
556 int
main(int argc,char * const * argv)557 main (int argc, char *const *argv)
558 {
559   unsigned int errorCount = 0;
560   (void) argc;   /* Unused. Silent compiler warning. */
561 
562   if ((NULL == argv) || (0 == argv[0]))
563     return 99;
564   oneone = has_in_name (argv[0], "11");
565   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
566     return 2;
567   if (MHD_YES == MHD_is_feature_supported (MHD_FEATURE_THREADS))
568   {
569     errorCount += testInternalPut ();
570     errorCount += testMultithreadedPut ();
571     errorCount += testMultithreadedPoolPut ();
572   }
573   errorCount += testExternalPut ();
574   if (errorCount != 0)
575     fprintf (stderr, "Error (code: %u)\n", errorCount);
576   curl_global_cleanup ();
577   return (0 == errorCount) ? 0 : 1;       /* 0 == pass */
578 }
579