1 /*
2      This file is part of libmicrohttpd
3      Copyright (C) 2007, 2008 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 test_post_form.c
23  * @brief  Testcase for libmicrohttpd POST operations using multipart/postform data
24  * @author Christian Grothoff
25  */
26 
27 #include "MHD_config.h"
28 #include "platform.h"
29 #include <curl/curl.h>
30 #include <microhttpd.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <time.h>
34 
35 #ifndef WINDOWS
36 #include <unistd.h>
37 #endif
38 
39 
40 #include "socat.c"
41 
42 static int oneone;
43 
44 struct CBC
45 {
46   char *buf;
47   size_t pos;
48   size_t size;
49 };
50 
51 
52 static void
completed_cb(void * cls,struct MHD_Connection * connection,void ** con_cls,enum MHD_RequestTerminationCode toe)53 completed_cb (void *cls,
54 	      struct MHD_Connection *connection,
55 	      void **con_cls,
56 	      enum MHD_RequestTerminationCode toe)
57 {
58   struct MHD_PostProcessor *pp = *con_cls;
59 
60   if (NULL != pp)
61     MHD_destroy_post_processor (pp);
62   *con_cls = NULL;
63 }
64 
65 
66 static size_t
copyBuffer(void * ptr,size_t size,size_t nmemb,void * ctx)67 copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
68 {
69   struct CBC *cbc = ctx;
70 
71   if (cbc->pos + size * nmemb > cbc->size)
72     return 0;                   /* overflow */
73   memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
74   cbc->pos += size * nmemb;
75   return size * nmemb;
76 }
77 
78 /**
79  * Note that this post_iterator is not perfect
80  * in that it fails to support incremental processing.
81  * (to be fixed in the future)
82  */
83 static int
post_iterator(void * cls,enum MHD_ValueKind kind,const char * key,const char * filename,const char * content_type,const char * transfer_encoding,const char * value,uint64_t off,size_t size)84 post_iterator (void *cls,
85                enum MHD_ValueKind kind,
86                const char *key,
87                const char *filename,
88                const char *content_type,
89                const char *transfer_encoding,
90                const char *value, uint64_t off, size_t size)
91 {
92   int *eok = cls;
93 
94   if (key == NULL)
95     return MHD_YES;
96 #if 0
97   fprintf (stderr, "PI sees %s-%.*s\n", key, size, value);
98 #endif
99   if ((0 == strcmp (key, "name")) &&
100       (size == strlen ("daniel")) && (0 == strncmp (value, "daniel", size)))
101     (*eok) |= 1;
102   if ((0 == strcmp (key, "project")) &&
103       (size == strlen ("curl")) && (0 == strncmp (value, "curl", size)))
104     (*eok) |= 2;
105   return MHD_YES;
106 }
107 
108 
109 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)110 ahc_echo (void *cls,
111           struct MHD_Connection *connection,
112           const char *url,
113           const char *method,
114           const char *version,
115           const char *upload_data, size_t *upload_data_size,
116           void **unused)
117 {
118   static int eok;
119   struct MHD_Response *response;
120   struct MHD_PostProcessor *pp;
121   int ret;
122 
123   if (0 != strcmp ("POST", method))
124     {
125       return MHD_NO;            /* unexpected method */
126     }
127   pp = *unused;
128   if (pp == NULL)
129     {
130       eok = 0;
131       pp = MHD_create_post_processor (connection, 1024, &post_iterator, &eok);
132       if (pp == NULL)
133         return MHD_NO;
134       *unused = pp;
135     }
136   MHD_post_process (pp, upload_data, *upload_data_size);
137   if ((eok == 3) && (0 == *upload_data_size))
138     {
139       response = MHD_create_response_from_buffer (strlen (url),
140 						  (void *) url,
141 						  MHD_RESPMEM_MUST_COPY);
142       ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
143       MHD_destroy_response (response);
144       MHD_destroy_post_processor (pp);
145       *unused = NULL;
146       return ret;
147     }
148   *upload_data_size = 0;
149   return MHD_YES;
150 }
151 
152 static struct curl_httppost *
make_form()153 make_form ()
154 {
155   struct curl_httppost *post = NULL;
156   struct curl_httppost *last = NULL;
157 
158   curl_formadd (&post, &last, CURLFORM_COPYNAME, "name",
159                 CURLFORM_COPYCONTENTS, "daniel", CURLFORM_END);
160   curl_formadd (&post, &last, CURLFORM_COPYNAME, "project",
161                 CURLFORM_COPYCONTENTS, "curl", CURLFORM_END);
162   return post;
163 }
164 
165 
166 static int
testInternalPost()167 testInternalPost ()
168 {
169   struct MHD_Daemon *d;
170   CURL *c;
171   char buf[2048];
172   struct CBC cbc;
173   int i;
174   struct curl_httppost *pd;
175 
176   cbc.buf = buf;
177   cbc.size = 2048;
178   cbc.pos = 0;
179   d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY /* | MHD_USE_DEBUG */ ,
180                         11080, NULL, NULL, &ahc_echo, NULL,
181 			MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
182 			MHD_OPTION_END);
183   if (d == NULL)
184     return 1;
185   zzuf_socat_start ();
186   for (i = 0; i < LOOP_COUNT; i++)
187     {
188       fprintf (stderr, ".");
189       c = curl_easy_init ();
190       curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
191       curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
192       curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
193       pd = make_form ();
194       curl_easy_setopt (c, CURLOPT_HTTPPOST, pd);
195       curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
196       curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
197       if (oneone)
198         curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
199       else
200         curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
201       curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
202       // NOTE: use of CONNECTTIMEOUT without also
203       //   setting NOSIGNAL results in really weird
204       //   crashes on my system!
205       curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
206       curl_easy_perform (c);
207       curl_easy_cleanup (c);
208       curl_formfree (pd);
209     }
210   fprintf (stderr, "\n");
211   zzuf_socat_stop ();
212   MHD_stop_daemon (d);
213   return 0;
214 }
215 
216 
217 static int
testMultithreadedPost()218 testMultithreadedPost ()
219 {
220   struct MHD_Daemon *d;
221   CURL *c;
222   char buf[2048];
223   struct CBC cbc;
224   int i;
225   struct curl_httppost *pd;
226 
227   cbc.buf = buf;
228   cbc.size = 2048;
229   cbc.pos = 0;
230   d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION /* | MHD_USE_DEBUG */ ,
231                         11080, NULL, NULL, &ahc_echo, NULL,
232 			MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
233 			MHD_OPTION_END);
234   if (d == NULL)
235     return 16;
236   zzuf_socat_start ();
237   for (i = 0; i < LOOP_COUNT; i++)
238     {
239       fprintf (stderr, ".");
240       c = curl_easy_init ();
241       curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
242       curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
243       curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
244       pd = make_form ();
245       curl_easy_setopt (c, CURLOPT_HTTPPOST, pd);
246       curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
247       curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
248       if (oneone)
249         curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
250       else
251         curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
252       curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
253       // NOTE: use of CONNECTTIMEOUT without also
254       //   setting NOSIGNAL results in really weird
255       //   crashes on my system!
256       curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
257       curl_easy_perform (c);
258       curl_easy_cleanup (c);
259       curl_formfree (pd);
260     }
261   fprintf (stderr, "\n");
262   zzuf_socat_stop ();
263   MHD_stop_daemon (d);
264   return 0;
265 }
266 
267 
268 static int
testExternalPost()269 testExternalPost ()
270 {
271   struct MHD_Daemon *d;
272   CURL *c;
273   char buf[2048];
274   struct CBC cbc;
275   CURLM *multi;
276   CURLMcode mret;
277   fd_set rs;
278   fd_set ws;
279   fd_set es;
280   int max;
281   int running;
282   time_t start;
283   struct timeval tv;
284   struct curl_httppost *pd;
285   int i;
286 
287   multi = NULL;
288   cbc.buf = buf;
289   cbc.size = 2048;
290   cbc.pos = 0;
291   d = MHD_start_daemon (MHD_NO_FLAG /* | MHD_USE_DEBUG */ ,
292                         1082, NULL, NULL, &ahc_echo, NULL,
293 			MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
294 			MHD_OPTION_END);
295   if (d == NULL)
296     return 256;
297   multi = curl_multi_init ();
298   if (multi == NULL)
299     {
300       MHD_stop_daemon (d);
301       return 512;
302     }
303   zzuf_socat_start ();
304   for (i = 0; i < LOOP_COUNT; i++)
305     {
306       fprintf (stderr, ".");
307 
308       c = curl_easy_init ();
309       curl_easy_setopt (c, CURLOPT_URL, "http://localhost:1082/hello_world");
310       curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
311       curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
312       pd = make_form ();
313       curl_easy_setopt (c, CURLOPT_HTTPPOST, pd);
314       curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
315       curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
316       if (oneone)
317         curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
318       else
319         curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
320       curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 15L);
321       // NOTE: use of CONNECTTIMEOUT without also
322       //   setting NOSIGNAL results in really weird
323       //   crashes on my system!
324       curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
325 
326 
327       mret = curl_multi_add_handle (multi, c);
328       if (mret != CURLM_OK)
329         {
330           curl_multi_cleanup (multi);
331           curl_formfree (pd);
332           curl_easy_cleanup (c);
333           zzuf_socat_stop ();
334           MHD_stop_daemon (d);
335           return 1024;
336         }
337       start = time (NULL);
338       while ((time (NULL) - start < 5) && (c != 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               zzuf_socat_stop ();
352               MHD_stop_daemon (d);
353               curl_formfree (pd);
354               return 2048;
355             }
356           if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
357             {
358               curl_multi_remove_handle (multi, c);
359               curl_multi_cleanup (multi);
360               curl_easy_cleanup (c);
361               curl_formfree (pd);
362               zzuf_socat_stop ();
363               MHD_stop_daemon (d);
364               return 4096;
365             }
366           tv.tv_sec = 0;
367           tv.tv_usec = 1000;
368           select (max + 1, &rs, &ws, &es, &tv);
369           curl_multi_perform (multi, &running);
370           if (running == 0)
371             {
372               curl_multi_info_read (multi, &running);
373               curl_multi_remove_handle (multi, c);
374               curl_easy_cleanup (c);
375               c = NULL;
376             }
377           MHD_run (d);
378         }
379       if (c != NULL)
380         {
381           curl_multi_remove_handle (multi, c);
382           curl_easy_cleanup (c);
383         }
384       curl_formfree (pd);
385     }
386   fprintf (stderr, "\n");
387   zzuf_socat_stop ();
388 
389   MHD_stop_daemon (d);
390   return 0;
391 }
392 
393 
394 int
main(int argc,char * const * argv)395 main (int argc, char *const *argv)
396 {
397   unsigned int errorCount = 0;
398 
399   oneone = (NULL != strrchr (argv[0], (int) '/')) ?
400     (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
401   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
402     return 2;
403   errorCount += testInternalPost ();
404   errorCount += testMultithreadedPost ();
405   errorCount += testExternalPost ();
406   if (errorCount != 0)
407     fprintf (stderr, "Error (code: %u)\n", errorCount);
408   curl_global_cleanup ();
409   return errorCount != 0;       /* 0 == pass */
410 }
411