aboutsummaryrefslogtreecommitdiff
path: root/src/examples/demo.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/examples/demo.c')
-rw-r--r--src/examples/demo.c474
1 files changed, 474 insertions, 0 deletions
diff --git a/src/examples/demo.c b/src/examples/demo.c
new file mode 100644
index 00000000..945d384d
--- /dev/null
+++ b/src/examples/demo.c
@@ -0,0 +1,474 @@
1/*
2 This file is part of libmicrohttpd
3 (C) 2013 Christian Grothoff (and other contributing authors)
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18*/
19
20/**
21 * @file demo.c
22 * @brief complex demonstration site: upload, index, download
23 * @author Christian Grothoff
24 */
25#include "platform.h"
26#include <microhttpd.h>
27#include <unistd.h>
28#include <pthread.h>
29#include <sys/types.h>
30#include <dirent.h>
31
32
33/**
34 * Page returned for file-not-found.
35 */
36#define FILE_NOT_FOUND_PAGE "<html><head><title>File not found</title></head><body>File not found</body></html>"
37
38
39/**
40 * Page returned for internal errors.
41 */
42#define INTERNAL_ERROR_PAGE "<html><head><title>Internal error</title></head><body>Internal error</body></html>"
43
44
45/**
46 * Head of index page.
47 */
48#define INDEX_PAGE_HEADER "<html>\n<head><title>Welcome</title></head>\n<body>\n"\
49 "<form method=\"POST\" enctype=\"multipart/form-data\" action=\"/\">"\
50 "Upload: <input type=\"file\" name=\"upload\"/>"\
51 "<input type=\"submit\" value=\"Send\"/>"\
52 "</form>\n"\
53 "<ol>\n"
54
55/**
56 * Footer of index page.
57 */
58#define INDEX_PAGE_FOOTER "</ol>\n</body>\n</html>"
59
60
61/**
62 * Response returned if the requested file does not exist (or is not accessible).
63 */
64static struct MHD_Response *file_not_found_response;
65
66/**
67 * Response returned for internal errors.
68 */
69static struct MHD_Response *internal_error_response;
70
71/**
72 * Response returned for '/' (GET) to list the contents of the directory and allow upload.
73 */
74static struct MHD_Response *cached_directory_response;
75
76/**
77 * Mutex used when we update the cached directory response object.
78 */
79static pthread_mutex_t mutex;
80
81
82/**
83 * Replace the existing 'cached_directory_response' with the
84 * given response.
85 *
86 * @param response new directory response
87 */
88static void
89update_cached_response (struct MHD_Response *response)
90{
91 (void) pthread_mutex_lock (&mutex);
92 if (NULL != cached_directory_response)
93 MHD_destroy_response (cached_directory_response);
94 cached_directory_response = response;
95 (void) pthread_mutex_unlock (&mutex);
96}
97
98
99/**
100 * Re-scan our local directory and re-build the index.
101 */
102static void
103update_directory ()
104{
105 static size_t initial_allocation = 32 * 1024; /* initial size for response buffer */
106 DIR *dir;
107 struct dirent *de;
108 struct MHD_Response *response;
109 char *buf;
110 size_t buf_len;
111 size_t off;
112
113 dir = opendir (".");
114 if (NULL == dir)
115 goto err;
116 buf_len = initial_allocation;
117 buf = malloc (buf_len);
118 if (NULL == buf)
119 {
120 closedir (dir);
121 goto err;
122 }
123 off = snprintf (buf, buf_len,
124 "%s",
125 INDEX_PAGE_HEADER);
126 while (NULL != (de = readdir (dir)))
127 {
128 if ('.' == de->d_name[0])
129 continue;
130 if (off + 1024 > buf_len)
131 {
132 void *r;
133
134 if ( (2 * buf_len + 1024) < buf_len)
135 break; /* more than SIZE_T _index_ size? Too big for us */
136 buf_len = 2 * buf_len + 1024;
137 if (NULL == (r = realloc (buf, buf_len)))
138 break; /* out of memory */
139 buf = r;
140 }
141 off += snprintf (&buf[off], buf_len - off,
142 "<li><a href=\"/%s\">%s</a></li>\n",
143 de->d_name,
144 de->d_name);
145 }
146 /* we ensured always +1k room, filenames are ~256 bytes,
147 so there is always still enough space for the footer
148 without need for a final reallocation check. */
149 off += snprintf (&buf[off], buf_len - off,
150 "%s",
151 INDEX_PAGE_FOOTER);
152 closedir (dir);
153 initial_allocation = buf_len; /* remember for next time */
154 response = MHD_create_response_from_buffer (off,
155 buf,
156 MHD_RESPMEM_MUST_FREE);
157 update_cached_response (response);
158 return;
159 err:
160 /* failed to list directory, use error page */
161 update_cached_response (NULL);
162}
163
164
165/**
166 * Context we keep for an upload.
167 */
168struct UploadContext
169{
170 /**
171 * Handle where we write the uploaded file to.
172 */
173 int fd;
174
175 /**
176 * Post processor we're using to process the upload.
177 */
178 struct MHD_PostProcessor *pp;
179
180 /**
181 * Handle to connection that we're processing the upload for.
182 */
183 struct MHD_Connection *connection;
184};
185
186
187/**
188 * Iterator over key-value pairs where the value
189 * maybe made available in increments and/or may
190 * not be zero-terminated. Used for processing
191 * POST data.
192 *
193 * @param cls user-specified closure
194 * @param kind type of the value, always MHD_POSTDATA_KIND when called from MHD
195 * @param key 0-terminated key for the value
196 * @param filename name of the uploaded file, NULL if not known
197 * @param content_type mime-type of the data, NULL if not known
198 * @param transfer_encoding encoding of the data, NULL if not known
199 * @param data pointer to size bytes of data at the
200 * specified offset
201 * @param off offset of data in the overall value
202 * @param size number of bytes in data available
203 * @return MHD_YES to continue iterating,
204 * MHD_NO to abort the iteration
205 */
206static int
207process_upload_data (void *cls,
208 enum MHD_ValueKind kind,
209 const char *key,
210 const char *filename,
211 const char *content_type,
212 const char *transfer_encoding,
213 const char *data,
214 uint64_t off,
215 size_t size)
216{
217 struct UploadContext *uc = cls;
218
219 if (NULL == filename)
220 {
221 fprintf (stderr, "No filename, aborting upload\n");
222 return MHD_NO; /* no filename, error */
223 }
224 fprintf (stderr, "Got %u bytes of upload data for %s\n",
225 (unsigned int) size, filename);
226 if (-1 == uc->fd)
227 {
228 uc->fd = open (filename,
229 O_CREAT | O_EXCL
230#if O_LARGEFILE
231 | O_LARGEFILE
232#endif
233#if O_NONBLOCK
234 | O_NONBLOCK
235#endif
236 | O_WRONLY,
237 S_IRUSR | S_IWUSR);
238 if (-1 == uc->fd)
239 {
240 // FIXME: generate error page NICELY
241 fprintf (stderr, "Error opening file to write!\n");
242 return MHD_NO;
243 }
244 }
245 if ( (0 != size) &&
246 (size != write (uc->fd, data, size)) )
247 {
248 // FIXME: generate error page NICELY
249 fprintf (stderr, "Error writing to disk!\n");
250 return MHD_NO;
251 }
252 return MHD_YES;
253}
254
255
256/**
257 * Function called whenever a request was completed.
258 * Used to clean up 'struct UploadContext' objects.
259 *
260 * @param cls client-defined closure, NULL
261 * @param connection connection handle
262 * @param con_cls value as set by the last call to
263 * the MHD_AccessHandlerCallback, points to NULL if this was
264 * not an upload
265 * @param toe reason for request termination
266 */
267static void
268response_completed_callback (void *cls,
269 struct MHD_Connection *connection,
270 void **con_cls,
271 enum MHD_RequestTerminationCode toe)
272{
273 struct UploadContext *uc = *con_cls;
274
275 if (NULL == uc)
276 return; /* this request wasn't an upload request */
277 if (NULL != uc->pp)
278 {
279 MHD_destroy_post_processor (uc->pp);
280 uc->pp = NULL;
281 }
282 if (-1 != uc->fd)
283 {
284 close (uc->fd);
285 fprintf (stderr, "Possible upload failure, need to remove file?!\n");
286 /* FIXME: unlink here on error!? */
287 }
288 free (uc);
289}
290
291
292/**
293 * Return the current directory listing.
294 *
295 * @param connection connection to return the directory for
296 * @return MHD_YES on success, MHD_NO on error
297 */
298static int
299list_directory (struct MHD_Connection *connection)
300{
301 int ret;
302
303 (void) pthread_mutex_lock (&mutex);
304 if (NULL == cached_directory_response)
305 ret = MHD_queue_response (connection,
306 MHD_HTTP_INTERNAL_SERVER_ERROR,
307 internal_error_response);
308 else
309 ret = MHD_queue_response (connection,
310 MHD_HTTP_OK,
311 cached_directory_response);
312 (void) pthread_mutex_unlock (&mutex);
313 return ret;
314}
315
316
317
318/**
319 * Main callback from MHD, used to generate the page.
320 *
321 * @param cls NULL
322 * @param connection connection handle
323 * @param url requested URL
324 * @param method GET, PUT, POST, etc.
325 * @param version HTTP version
326 * @param upload_data data from upload (PUT/POST)
327 * @param upload_data_size number of bytes in "upload_data"
328 * @param ptr our context
329 * @return MHD_YES on success, MHD_NO to drop connection
330 */
331static int
332generate_page (void *cls,
333 struct MHD_Connection *connection,
334 const char *url,
335 const char *method,
336 const char *version,
337 const char *upload_data,
338 size_t *upload_data_size, void **ptr)
339{
340 struct MHD_Response *response;
341 int ret;
342 int fd;
343 struct stat buf;
344
345 if (0 != strcmp (url, "/"))
346 {
347 /* should be file download */
348 if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
349 return MHD_NO; /* unexpected method (we're not polite...) */
350 if ( (0 == stat (&url[1], &buf)) &&
351 (NULL == strstr (&url[1], "..")) &&
352 ('/' != url[1]))
353 fd = open (&url[1], O_RDONLY);
354 else
355 fd = -1;
356 if (-1 == fd)
357 return MHD_queue_response (connection,
358 MHD_HTTP_NOT_FOUND,
359 file_not_found_response);
360 if (NULL == (response = MHD_create_response_from_fd (buf.st_size,
361 fd)))
362 {
363 /* internal error (i.e. out of memory) */
364 (void) close (fd);
365 return MHD_NO;
366 }
367 ret = MHD_queue_response (connection,
368 MHD_HTTP_OK,
369 response);
370 MHD_destroy_response (response);
371 return ret;
372 }
373
374 if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
375 {
376 /* upload! */
377 struct UploadContext *uc = *ptr;
378
379 if (NULL == uc)
380 {
381 if (NULL == (uc = malloc (sizeof (struct UploadContext))))
382 return MHD_NO; /* out of memory, close connection */
383 uc->fd = -1;
384 uc->connection = connection;
385 uc->pp = MHD_create_post_processor (connection,
386 32 * 1024 /* buffer size */,
387 &process_upload_data, uc);
388 if (NULL == uc->pp)
389 {
390 /* out of memory, close connection */
391 free (uc);
392 return MHD_NO;
393 }
394 *ptr = uc;
395 return MHD_YES;
396 }
397 if (0 != *upload_data_size)
398 {
399 ret = MHD_post_process (uc->pp,
400 upload_data,
401 *upload_data_size);
402 *upload_data_size = 0;
403 return ret;
404 }
405 /* end of upload, finish it! */
406 MHD_destroy_post_processor (uc->pp);
407 uc->pp = NULL;
408 if (-1 != uc->fd)
409 {
410 close (uc->fd);
411 uc->fd = -1;
412 }
413 update_directory ();
414 return list_directory (connection);
415 }
416 if (0 == strcmp (method, MHD_HTTP_METHOD_GET))
417 return list_directory (connection);
418
419 /* unexpected request, refuse */
420 fprintf (stderr, "Unexpected request, refusing\n");
421 return MHD_NO;
422}
423
424
425/**
426 * Entry point to demo. Note: this HTTP server will make all
427 * files in the current directory and its subdirectories available
428 * to anyone.
429 *
430 * @param argc number of arguments in argv
431 * @param argv first and only argument should be the port number
432 * @return 0 on success
433 */
434int
435main (int argc, char *const *argv)
436{
437 struct MHD_Daemon *d;
438 unsigned int port;
439
440 if ( (argc != 2) ||
441 (1 != sscanf (argv[1], "%u", &port)) ||
442 (UINT16_MAX < port) )
443 {
444 fprintf (stderr,
445 "%s PORT\n", argv[0]);
446 return 1;
447 }
448 (void) pthread_mutex_init (&mutex, NULL);
449 file_not_found_response = MHD_create_response_from_buffer (strlen (FILE_NOT_FOUND_PAGE),
450 (void *) FILE_NOT_FOUND_PAGE,
451 MHD_RESPMEM_PERSISTENT);
452 internal_error_response = MHD_create_response_from_buffer (strlen (INTERNAL_ERROR_PAGE),
453 (void *) INTERNAL_ERROR_PAGE,
454 MHD_RESPMEM_PERSISTENT);
455 update_directory ();
456 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
457 port,
458 NULL, NULL,
459 &generate_page, NULL,
460 MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) 8,
461 MHD_OPTION_NOTIFY_COMPLETED, &response_completed_callback, NULL,
462 MHD_OPTION_END);
463 if (NULL == d)
464 return 1;
465 (void) getc (stdin);
466 MHD_stop_daemon (d);
467 MHD_destroy_response (file_not_found_response);
468 MHD_destroy_response (internal_error_response);
469 update_cached_response (NULL);
470 (void) pthread_mutex_destroy (&mutex);
471 return 0;
472}
473
474/* end of demo.c */