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, ©Buffer);
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, ©Buffer);
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, ©Buffer);
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