diff options
Diffstat (limited to 'src/examples/demo.c')
-rw-r--r-- | src/examples/demo.c | 474 |
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 | */ | ||
64 | static struct MHD_Response *file_not_found_response; | ||
65 | |||
66 | /** | ||
67 | * Response returned for internal errors. | ||
68 | */ | ||
69 | static struct MHD_Response *internal_error_response; | ||
70 | |||
71 | /** | ||
72 | * Response returned for '/' (GET) to list the contents of the directory and allow upload. | ||
73 | */ | ||
74 | static struct MHD_Response *cached_directory_response; | ||
75 | |||
76 | /** | ||
77 | * Mutex used when we update the cached directory response object. | ||
78 | */ | ||
79 | static 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 | */ | ||
88 | static void | ||
89 | update_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 | */ | ||
102 | static void | ||
103 | update_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 | */ | ||
168 | struct 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 | */ | ||
206 | static int | ||
207 | process_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 | */ | ||
267 | static void | ||
268 | response_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 | */ | ||
298 | static int | ||
299 | list_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 | */ | ||
331 | static int | ||
332 | generate_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 | */ | ||
434 | int | ||
435 | main (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 */ | ||