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 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#include "socat.c"
41
42struct CBC
43{
44  char *buf;
45  size_t pos;
46  size_t size;
47};
48
49static size_t
50putBuffer (void *stream, size_t size, size_t nmemb, void *ptr)
51{
52  unsigned int *pos = ptr;
53  unsigned int wrt;
54
55  wrt = size * nmemb;
56  if (wrt > 8 - (*pos))
57    wrt = 8 - (*pos);
58  if (wrt > 4)
59    wrt = 4;                    /* only send half at first => force multiple chunks! */
60  memcpy (stream, &("Hello123"[*pos]), wrt);
61  (*pos) += wrt;
62  return wrt;
63}
64
65static size_t
66copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
67{
68  struct CBC *cbc = ctx;
69
70  if (cbc->pos + size * nmemb > cbc->size)
71    return 0;                   /* overflow */
72  memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
73  cbc->pos += size * nmemb;
74  return size * nmemb;
75}
76
77static int
78ahc_echo (void *cls,
79          struct MHD_Connection *connection,
80          const char *url,
81          const char *method,
82          const char *version,
83          const char *upload_data, size_t *upload_data_size,
84          void **unused)
85{
86  int *done = cls;
87  struct MHD_Response *response;
88  int ret;
89  int have;
90
91  if (0 != strcmp ("PUT", method))
92    return MHD_NO;              /* unexpected method */
93  if ((*done) < 8)
94    {
95      have = *upload_data_size;
96      if (have + *done > 8)
97        {
98          return MHD_NO;
99        }
100      if (0 == memcmp (upload_data, &"Hello123"[*done], have))
101        {
102          *done += have;
103          *upload_data_size = 0;
104        }
105      else
106        {
107          return MHD_NO;
108        }
109#if 0
110      fprintf (stderr, "Not ready for response: %u/%u\n", *done, 8);
111#endif
112      return MHD_YES;
113    }
114  response = MHD_create_response_from_buffer (strlen (url),
115					      (void *) url,
116					      MHD_RESPMEM_MUST_COPY);
117  ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
118  MHD_destroy_response (response);
119  return ret;
120}
121
122
123static int
124testInternalPut ()
125{
126  struct MHD_Daemon *d;
127  CURL *c;
128  char buf[2048];
129  struct CBC cbc;
130  unsigned int pos = 0;
131  int done_flag = 0;
132  int i;
133
134  cbc.buf = buf;
135  cbc.size = 2048;
136  cbc.pos = 0;
137  d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
138                        11080,
139                        NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
140  if (d == NULL)
141    return 1;
142  zzuf_socat_start ();
143  for (i = 0; i < LOOP_COUNT; i++)
144    {
145      fprintf (stderr, ".");
146      c = curl_easy_init ();
147      curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11080/hello_world");
148      curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
149      curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
150      curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
151      curl_easy_setopt (c, CURLOPT_READDATA, &pos);
152      curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
153      /*
154         // by not giving the file size, we force chunking!
155         curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
156       */
157      curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
158      curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
159      curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
160      curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
161      // NOTE: use of CONNECTTIMEOUT without also
162      //   setting NOSIGNAL results in really weird
163      //   crashes on my system!
164      curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
165      curl_easy_perform (c);
166      curl_easy_cleanup (c);
167    }
168  fprintf (stderr, "\n");
169  zzuf_socat_stop ();
170  MHD_stop_daemon (d);
171  return 0;
172}
173
174static int
175testMultithreadedPut ()
176{
177  struct MHD_Daemon *d;
178  CURL *c;
179  char buf[2048];
180  struct CBC cbc;
181  unsigned int pos = 0;
182  int done_flag = 0;
183  CURLcode errornum;
184
185  cbc.buf = buf;
186  cbc.size = 2048;
187  cbc.pos = 0;
188  d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
189                        11081,
190                        NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
191  if (d == NULL)
192    return 16;
193  c = curl_easy_init ();
194  curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11081/hello_world");
195  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
196  curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
197  curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
198  curl_easy_setopt (c, CURLOPT_READDATA, &pos);
199  curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
200  /*
201     // by not giving the file size, we force chunking!
202     curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
203   */
204  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
205  curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
206  curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
207  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 15L);
208  // NOTE: use of CONNECTTIMEOUT without also
209  //   setting NOSIGNAL results in really weird
210  //   crashes on my system!
211  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
212  if (CURLE_OK != (errornum = curl_easy_perform (c)))
213    {
214      fprintf (stderr,
215               "curl_easy_perform failed: `%s'\n",
216               curl_easy_strerror (errornum));
217      curl_easy_cleanup (c);
218      MHD_stop_daemon (d);
219      return 32;
220    }
221  curl_easy_cleanup (c);
222  MHD_stop_daemon (d);
223  if (cbc.pos != strlen ("/hello_world"))
224    return 64;
225  if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
226    return 128;
227
228  return 0;
229}
230
231
232static int
233testExternalPut ()
234{
235  struct MHD_Daemon *d;
236  CURL *c;
237  char buf[2048];
238  struct CBC cbc;
239  CURLM *multi;
240  CURLMcode mret;
241  fd_set rs;
242  fd_set ws;
243  fd_set es;
244  int max;
245  int running;
246  time_t start;
247  struct timeval tv;
248  unsigned int pos = 0;
249  int done_flag = 0;
250  int i;
251
252  multi = NULL;
253  cbc.buf = buf;
254  cbc.size = 2048;
255  cbc.pos = 0;
256  d = MHD_start_daemon (MHD_USE_DEBUG,
257                        11082,
258                        NULL, NULL, &ahc_echo, &done_flag, MHD_OPTION_END);
259  if (d == NULL)
260    return 256;
261
262  multi = curl_multi_init ();
263  if (multi == NULL)
264    {
265      MHD_stop_daemon (d);
266      return 512;
267    }
268  zzuf_socat_start ();
269  for (i = 0; i < LOOP_COUNT; i++)
270    {
271      fprintf (stderr, ".");
272      c = curl_easy_init ();
273      curl_easy_setopt (c, CURLOPT_URL, "http://localhost:11082/hello_world");
274      curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
275      curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
276      curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer);
277      curl_easy_setopt (c, CURLOPT_READDATA, &pos);
278      curl_easy_setopt (c, CURLOPT_UPLOAD, 1L);
279      /*
280         // by not giving the file size, we force chunking!
281         curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L);
282       */
283      curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
284      curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, CURL_TIMEOUT);
285      curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
286      curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, CURL_TIMEOUT);
287      // NOTE: use of CONNECTTIMEOUT without also
288      //   setting NOSIGNAL results in really weird
289      //   crashes on my system!
290      curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
291
292
293      mret = curl_multi_add_handle (multi, c);
294      if (mret != CURLM_OK)
295        {
296          curl_multi_cleanup (multi);
297          curl_easy_cleanup (c);
298          zzuf_socat_stop ();
299          MHD_stop_daemon (d);
300          return 1024;
301        }
302      start = time (NULL);
303      while ((time (NULL) - start < 5) && (c != NULL))
304        {
305          max = 0;
306          FD_ZERO (&rs);
307          FD_ZERO (&ws);
308          FD_ZERO (&es);
309          curl_multi_perform (multi, &running);
310          mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
311          if (mret != CURLM_OK)
312            {
313              curl_multi_remove_handle (multi, c);
314              curl_multi_cleanup (multi);
315              curl_easy_cleanup (c);
316              zzuf_socat_stop ();
317              MHD_stop_daemon (d);
318              return 2048;
319            }
320          if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
321            {
322              curl_multi_remove_handle (multi, c);
323              curl_multi_cleanup (multi);
324              curl_easy_cleanup (c);
325              zzuf_socat_stop ();
326              MHD_stop_daemon (d);
327              return 4096;
328            }
329          tv.tv_sec = 0;
330          tv.tv_usec = 1000;
331          select (max + 1, &rs, &ws, &es, &tv);
332          curl_multi_perform (multi, &running);
333          if (running == 0)
334            {
335              curl_multi_info_read (multi, &running);
336              curl_multi_remove_handle (multi, c);
337              curl_easy_cleanup (c);
338              c = NULL;
339            }
340          MHD_run (d);
341        }
342      if (c != NULL)
343        {
344          curl_multi_remove_handle (multi, c);
345          curl_easy_cleanup (c);
346        }
347    }
348  fprintf (stderr, "\n");
349  curl_multi_cleanup (multi);
350  zzuf_socat_stop ();
351  MHD_stop_daemon (d);
352  return 0;
353}
354
355
356
357int
358main (int argc, char *const *argv)
359{
360  unsigned int errorCount = 0;
361
362  if (0 != curl_global_init (CURL_GLOBAL_WIN32))
363    return 2;
364  errorCount += testInternalPut ();
365  errorCount += testMultithreadedPut ();
366  errorCount += testExternalPut ();
367  if (errorCount != 0)
368    fprintf (stderr, "Error (code: %u)\n", errorCount);
369  curl_global_cleanup ();
370  return errorCount != 0;       /* 0 == pass */
371}
372