/[osn-commons]/trunk/freehttpd/freehttpd.c
ViewVC logotype

Contents of /trunk/freehttpd/freehttpd.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 46 - (show annotations)
Mon Aug 12 16:55:32 2024 UTC (7 months, 2 weeks ago) by rakinar2
File MIME type: text/x-c
File size: 20341 byte(s)
feat(freehttpd): better error handling
1 #include "freehttpd.h"
2 #include "http_error.h"
3 #include "log.h"
4 #include "protocol.h"
5 #include "request.h"
6 #include "response.h"
7
8 #include <arpa/inet.h>
9 #include <dirent.h>
10 #include <errno.h>
11 #include <magic.h>
12 #include <stdarg.h>
13 #include <stdbool.h>
14 #include <stdint.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <sys/socket.h>
19 #include <sys/stat.h>
20 #include <sys/types.h>
21 #include <time.h>
22 #include <unistd.h>
23
24 struct freehttpd
25 {
26 int sockfd;
27 magic_t magic;
28 freehttpd_config_t *config;
29 freehttpd_errdoc_tbl_t *errdoc_tbl;
30 };
31
32 static freehttpd_config_t *
33 freehttpd_config_init ()
34 {
35 struct freehttpd_config *config = calloc (1, sizeof (freehttpd_config_t));
36
37 if (config == NULL)
38 return NULL;
39
40 config->port = 80;
41 config->addr = NULL;
42 config->max_listen_queue = 5;
43 config->max_method_len = 16;
44 config->max_uri_len = 8192;
45 config->max_version_len = 16;
46 config->docroot = NULL;
47
48 return config;
49 }
50
51 static void
52 freehttpd_config_free (freehttpd_config_t *config)
53 {
54 if (config == NULL)
55 return;
56
57 free (config->docroot);
58 free (config->addr);
59 free (config);
60 }
61
62 freehttpd_t *
63 freehttpd_init (magic_t magic)
64 {
65 freehttpd_t *freehttpd = calloc (1, sizeof (freehttpd_t));
66
67 if (freehttpd == NULL)
68 return NULL;
69
70 freehttpd->sockfd = -1;
71 freehttpd->magic = magic;
72 freehttpd->config = freehttpd_config_init ();
73 freehttpd->errdoc_tbl = freehttpd_error_document_tbl_init ();
74
75 freehttpd_error_document_load_defaults (freehttpd->errdoc_tbl);
76 return freehttpd;
77 }
78
79 ecode_t
80 freehttpd_setopt (freehttpd_t *freehttpd, freehttpd_opt_t opt, void *value)
81 {
82 switch (opt)
83 {
84 case FREEHTTPD_CONFIG_PORT:
85 freehttpd->config->port = *(unsigned int *) value;
86 break;
87
88 case FREEHTTPD_CONFIG_ADDR:
89 freehttpd->config->addr
90 = value == NULL ? NULL : strdup ((const char *) value);
91 break;
92
93 case FREEHTTPD_CONFIG_MAX_LISTEN_QUEUE:
94 freehttpd->config->max_listen_queue = *(unsigned int *) value;
95 break;
96
97 case FREEHTTPD_CONFIG_MAX_METHOD_LEN:
98 freehttpd->config->max_method_len = *(size_t *) value;
99 break;
100
101 case FREEHTTPD_CONFIG_MAX_URI_LEN:
102 freehttpd->config->max_uri_len = *(size_t *) value;
103 break;
104
105 case FREEHTTPD_CONFIG_MAX_VERSION_LEN:
106 freehttpd->config->max_version_len = *(size_t *) value;
107 break;
108
109 case FREEHTTPD_CONFIG_DOCROOT:
110 freehttpd->config->docroot
111 = value == NULL ? NULL : strdup ((const char *) value);
112
113 if (value != NULL)
114 freehttpd->config->_docroot_length
115 = strlen (freehttpd->config->docroot);
116 break;
117
118 default:
119 return E_UNKNOWN_OPT;
120 }
121
122 return E_OK;
123 }
124
125 void
126 freehttpd_free (freehttpd_t *freehttpd)
127 {
128 if (freehttpd == NULL)
129 return;
130
131 freehttpd_error_document_tbl_free (freehttpd->errdoc_tbl);
132 freehttpd_config_free (freehttpd->config);
133 free (freehttpd);
134 }
135
136 static ecode_t
137 freehttpd_create_socket (freehttpd_t *freehttpd)
138 {
139 int sockfd = socket (AF_INET, SOCK_STREAM, 0);
140
141 if (sockfd < 0)
142 return E_SYSCALL_SOCKET;
143
144 int opt = 1;
145
146 if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)) < 0)
147 return E_SYSCALL_SETSOCKOPT;
148
149 freehttpd->sockfd = sockfd;
150 return E_OK;
151 }
152
153 static struct sockaddr_in
154 freehttpd_setup_addrinfo (freehttpd_t *freehttpd)
155 {
156 struct sockaddr_in addr = { 0 };
157 const char *addr_host = freehttpd->config->addr;
158
159 addr.sin_family = AF_INET;
160 addr.sin_port = htons (freehttpd->config->port);
161 addr.sin_addr.s_addr
162 = addr_host == NULL ? INADDR_ANY : inet_addr (addr_host);
163
164 return addr;
165 }
166
167 static ecode_t
168 freehttpd_bind (freehttpd_t *freehttpd, struct sockaddr_in *addr_in)
169 {
170 if (bind (freehttpd->sockfd, (struct sockaddr *) addr_in, sizeof (*addr_in))
171 < 0)
172 return E_SYSCALL_BIND;
173
174 return E_OK;
175 }
176
177 static ecode_t
178 freehttpd_listen (freehttpd_t *freehttpd)
179 {
180 unsigned int max_listen_queue = freehttpd->config->max_listen_queue;
181
182 if (listen (freehttpd->sockfd, (int) max_listen_queue) < 0)
183 return E_SYSCALL_LISTEN;
184
185 return E_OK;
186 }
187
188 static ecode_t
189 freehttpd_send_error (freehttpd_t *freehttpd, FILE *stream, int sockfd,
190 freehttpd_status_t status, const char *version)
191 {
192 freehttpd_response_t *response = freehttpd_response_init (
193 stream, version == NULL ? HTTP1_1 : version, 3, status);
194 ecode_t ret = E_OK;
195
196 if (response == NULL)
197 return E_LIBC_MALLOC;
198
199 if (stream == NULL)
200 stream = fdopen (sockfd, "w");
201
202 if (stream == NULL)
203 {
204 freehttpd_response_free (response);
205 return E_LIBC_FDOPEN;
206 }
207
208 freehttpd_response_add_default_headers (response);
209
210 const freehttpd_errdoc_t *doc
211 = freehttpd_error_document_get (freehttpd->errdoc_tbl, status);
212
213 if (doc == NULL)
214 {
215 freehttpd_response_add_header (response, "Content-Length", 0, "0");
216 freehttpd_response_head_send (response);
217 freehttpd_response_begin_end (response);
218 return E_OK;
219 }
220
221 freehttpd_response_add_header (response, "Content-Type", 0,
222 "text/html; charset=\"utf-8\"");
223 freehttpd_response_add_header (response, "Content-Length", 0, "%lu",
224 doc->document_length);
225
226 ret = freehttpd_response_head_send (response);
227
228 if (ret != E_OK)
229 log_err (LOG_ERR "failed to send error response headers: %i\n", ret);
230
231 ret = freehttpd_response_begin_body (response);
232
233 if (ret != E_OK)
234 log_err (LOG_ERR "failed to start sending response: %i\n", ret);
235
236 if (freehttpd_response_write (response, doc->document, 1,
237 doc->document_length)
238 != doc->document_length)
239 log_err (LOG_ERR "failed to write error response: %i\n", ret);
240
241 freehttpd_response_free (response);
242 return ret;
243 }
244
245 static long
246 fsize (FILE *file)
247 {
248 long size;
249 long pos = ftell (file);
250 fseek (file, 0, SEEK_END);
251 size = ftell (file);
252 fseek (file, pos, SEEK_SET);
253 return size;
254 }
255
256 static void
257 iasprintf (char **strp, const char *fmt, ...)
258 {
259 va_list ap;
260 va_start (ap, fmt);
261 (void) (vasprintf (strp, fmt, ap) + 1);
262 va_end (ap);
263 }
264
265 /*
266 static ecode_t
267 freehttpd_respond_dindex (freehttpd_t *freehttpd, freehttpd_request_t *request,
268 freehttpd_response_t *response, FILE *stream,
269 const char *rpath)
270 {
271 freehttpd_response_set_status (response, FREEHTTPD_STATUS_OK);
272 ecode_t code = freehttpd_response_head_send (response);
273
274 if (code != E_OK)
275 return code;
276
277 bool is_root = strcmp (freehttpd->config->docroot, rpath) == 0;
278 bool http1_0 = strcmp (request->version, "1.0") == 0;
279
280 fprintf (stream, "Content-Type: text/html; charset=\"utf-8\"\r\n");
281
282 if (http1_0)
283 fprintf (stream, "Content-Length: -1\r\n");
284 else
285 fprintf (stream, "Transfer-Encoding: chunked\r\n");
286
287 fprintf (stream, "\r\n");
288
289 DIR *dir = opendir (rpath);
290
291 if (dir == NULL)
292 return E_SYSCALL_READ;
293
294 struct dirent *entry = NULL;
295 char *out_buf = NULL;
296
297 iasprintf (
298 &out_buf,
299 "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 3.2//EN\">\r\n"
300 "<html>\r\n"
301 "<head>\r\n"
302 "<title>Index of %s</title>\r\n"
303 "</head>\r\n"
304 "<body>\r\n"
305 "<h1>Index of %s</h1>\r\n"
306 "<table>\r\n"
307 "<tr>\r\n"
308 " <th>\r\n"
309 " <img\r\n"
310 " src=\"https://httpd.apache.org/icons/blank.gif\"\r\n"
311 " alt=\"[ICO]\"\r\n"
312 " />\r\n"
313 " </th>\r\n"
314 " <th><a href=\"?C=N;O=D\">Name</a></th>\r\n"
315 " <th><a href=\"?C=M;O=A\">Last modified</a></th>\r\n"
316 " <th><a href=\"?C=S;O=A\">Size</a></th>\r\n"
317 " <th><a href=\"?C=D;O=A\">Description</a></th>\r\n"
318 "</tr>\r\n"
319 "<tr>\r\n"
320 " <th colspan=\"5\"><hr /></th>\r\n"
321 "</tr>\r\n",
322 request->uri, request->uri);
323
324 fprintf (stream, "%lx\r\n%s\r\n", strlen (out_buf), out_buf);
325 free (out_buf);
326
327 if (!is_root)
328 {
329 iasprintf (&out_buf,
330 "<tr>\r\n"
331 "<td valign=\"top\">\r\n"
332 "<img\r\n"
333 "src=\"https://httpd.apache.org/icons/back.gif\"\r\n"
334 "alt=\"[DIR]\"\r\n"
335 "/>\r\n"
336 "</td>\r\n"
337 "<td><a href=\"..\">Parent Directory</a></td>\r\n"
338 "<td>&nbsp;</td>\r\n"
339 "<td align=\"right\"> - </td>\r\n"
340 "<td>&nbsp;</td>\r\n"
341 "</tr>\r\n");
342
343 fprintf (stream, "%lx\r\n%s\r\n", strlen (out_buf), out_buf);
344 free (out_buf);
345 }
346
347 while ((entry = readdir (dir)) != NULL)
348 {
349 if (strcmp (entry->d_name, ".") == 0
350 || strcmp (entry->d_name, "..") == 0)
351 continue;
352
353 struct stat st = { 0 };
354 char path[PATH_MAX] = { 0 };
355 char mtime_str[64] = { 0 };
356
357 strcpy (path, rpath);
358 strcat (path, "/");
359 strcat (path, entry->d_name);
360
361 if (lstat (path, &st) < 0)
362 continue;
363
364 struct tm *mtime = localtime (&st.st_mtime);
365 strftime (mtime_str, sizeof (mtime_str), "%Y-%m-%d %H:%M", mtime);
366
367 iasprintf (&out_buf,
368 "<tr>\r\n"
369 "<td valign=\"top\">\r\n"
370 "<img\r\n"
371 "src=\"https://httpd.apache.org/icons/%s.gif\"\r\n"
372 "alt=\"[DIR]\"\r\n"
373 "/>\r\n"
374 "</td>\r\n",
375 S_ISDIR (st.st_mode) ? "folder" : "unknown");
376
377 fprintf (stream, "%lx\r\n%s\r\n", strlen (out_buf), out_buf);
378 free (out_buf);
379
380 const char *leading_slash = S_ISDIR (st.st_mode) ? "/" : "";
381 iasprintf (&out_buf,
382 "<td><a href=\"%s%s%s%s\">%s%s</a></td>\r\n"
383 "<td align=\"right\">%s</td>\r\n"
384 "<td align=\"right\"> - </td>\r\n"
385 "</tr>\r\n",
386 request->uri,
387 request->uri[request->uri_length - 1] == '/' ? "" : "/",
388 entry->d_name, leading_slash, entry->d_name,
389 leading_slash, mtime_str);
390
391 fprintf (stream, "%lx\r\n%s\r\n", strlen (out_buf), out_buf);
392 free (out_buf);
393 }
394
395 iasprintf (&out_buf,
396 "<tr>\r\n"
397 " <th colspan=\"5\"><hr /></th>\r\n"
398 "</tr>\r\n"
399 "</table><address>freehttpd/1.0.0-beta.1 (Ubuntu 24.04 "
400 "LTS) Server at localhost Port 8080</address>\r\n"
401 "</body>\r\n"
402 "</html>\r\n");
403
404 fprintf (stream, "%lx\r\n%s\r\n", strlen (out_buf), out_buf);
405 free (out_buf);
406
407 fprintf (stream, "0\r\n\r\n");
408 closedir (dir);
409 return E_OK;
410 }
411 */
412
413 static ecode_t
414 freehttpd_respond_http1x (freehttpd_t *freehttpd, freehttpd_request_t *request,
415 freehttpd_response_t *response)
416 {
417 return freehttpd_send_error (freehttpd, response->stream, 0,
418 FREEHTTPD_STATUS_NOT_IMPLEMENTED,
419 request->version);
420 // return E_OK;
421 }
422
423 /*
424 static ecode_t
425 freehttpd_respond (freehttpd_t *freehttpd, freehttpd_request_t *request,
426 freehttpd_response_t *response, FILE *stream)
427 {
428 freehttpd_response_add_default_headers (response);
429
430 const char *docroot = freehttpd->config->docroot;
431 char *rpath = NULL;
432 freehttpd_status_t status = FREEHTTPD_STATUS_OK;
433
434 if (docroot == NULL)
435 {
436 status = FREEHTTPD_STATUS_INTERNAL_SERVER_ERROR;
437 goto freehttpd_respond_error;
438 }
439
440 char *path = request->path;
441
442 if (path == NULL)
443 {
444 status = FREEHTTPD_STATUS_INTERNAL_SERVER_ERROR;
445 goto freehttpd_respond_error;
446 }
447
448 char *fs_path = NULL;
449
450 if (asprintf (&fs_path, "%s%s", docroot, path) < 0)
451 {
452 status = FREEHTTPD_STATUS_INTERNAL_SERVER_ERROR;
453 return E_LIBC_MALLOC;
454 }
455
456 rpath = realpath (fs_path, NULL);
457
458 if (rpath == NULL)
459 {
460 status = FREEHTTPD_STATUS_NOT_FOUND;
461 goto freehttpd_respond_error;
462 }
463
464 if (strncmp (docroot, rpath, freehttpd->config->_docroot_length) != 0)
465 {
466 status = FREEHTTPD_STATUS_FORBIDDEN;
467 goto freehttpd_respond_error;
468 }
469
470 log_msg (LOG_DEBUG "rpath: %s\n", rpath);
471 struct stat st = { 0 };
472
473 if (lstat (rpath, &st) < 0)
474 {
475 status = FREEHTTPD_STATUS_INTERNAL_SERVER_ERROR;
476 goto freehttpd_respond_error;
477 }
478
479 if (S_ISDIR (st.st_mode))
480 {
481 ecode_t code = freehttpd_respond_dindex (freehttpd, request,
482 response, stream, rpath);
483
484 free (rpath);
485 free (fs_path);
486 return code;
487 }
488
489 FILE *file = fopen (rpath, "r");
490
491 if (file == NULL)
492 {
493 switch (errno)
494 {
495 case EACCES:
496 case EISDIR:
497 status = FREEHTTPD_STATUS_FORBIDDEN;
498 break;
499 case ENOENT:
500 status = FREEHTTPD_STATUS_NOT_FOUND;
501 break;
502 default:
503 status = FREEHTTPD_STATUS_INTERNAL_SERVER_ERROR;
504 break;
505 }
506
507 goto freehttpd_respond_error;
508 }
509
510 const char *content_type = NULL;
511 size_t rpath_len = strlen (rpath);
512
513 if (rpath_len > 4 && strcmp (rpath + rpath_len - 4, ".css") == 0)
514 content_type = "text/css";
515 else if (rpath_len > 5 && strcmp (rpath + rpath_len - 5, ".html") == 0)
516 content_type = "text/html";
517 else if (rpath_len > 4 && strcmp (rpath + rpath_len - 3, ".js") == 0)
518 content_type = "application/javascript";
519 else
520 {
521 content_type = magic_descriptor (freehttpd->magic, fileno (file));
522
523 if (content_type == NULL)
524 content_type = "application/octet-stream";
525 }
526
527 fseek (file, 0, SEEK_SET);
528
529 long file_size = fsize (file);
530 char buffer[1024] = { 0 };
531
532 if (file_size == -1)
533 {
534 status = FREEHTTPD_STATUS_INTERNAL_SERVER_ERROR;
535 goto freehttpd_respond_error;
536 }
537
538 freehttpd_response_set_status (response, status);
539 ecode_t code = freehttpd_response_head_send (response);
540
541 if (code != E_OK)
542 {
543 free (rpath);
544 free (fs_path);
545 fclose (file);
546 return code;
547 }
548
549 char etag[128] = { 0 };
550 bool http1_0 = strcmp (request->version, "1.0") == 0;
551 snprintf (etag, sizeof (etag), "\"%lx-%lx\"", st.st_mtime, file_size);
552
553 fprintf (stream, "Content-Type: %s\r\n", content_type);
554
555 if (http1_0)
556 fprintf (stream, "Content-Length: %ld\r\n", file_size);
557 else
558 fprintf (stream, "Transfer-Encoding: chunked\r\n");
559
560 fprintf (stream, "ETag: %s\r\n", etag);
561 fprintf (stream, "\r\n");
562
563 while (file_size > 0)
564 {
565 size_t read_size = 0;
566
567 errno = 0;
568
569 if ((read_size = fread (buffer, 1, sizeof (buffer), file)) == 0
570 && errno != 0)
571 {
572 freehttpd_response_set_status (
573 response, FREEHTTPD_STATUS_INTERNAL_SERVER_ERROR);
574 free (rpath);
575 free (fs_path);
576 fclose (file);
577 return E_SYSCALL_READ;
578 }
579
580 if (!http1_0)
581 fprintf (stream, "%lx\r\n", read_size);
582
583 if (fwrite (buffer, 1, read_size, stream) != read_size)
584 {
585 free (rpath);
586 free (fs_path);
587 fclose (file);
588 return E_SYSCALL_WRITE;
589 }
590
591 if (!http1_0)
592 fprintf (stream, "\r\n");
593
594 if (file_size >= (long int) read_size)
595 file_size -= (long int) read_size;
596 else
597 file_size = 0;
598 }
599
600 if (!http1_0)
601 fprintf (stream, "0\r\n\r\n");
602
603 fclose (file);
604 freehttpd_respond_error:
605 freehttpd_response_set_status (response, status);
606
607 if (status != FREEHTTPD_STATUS_OK)
608 freehttpd_send_error (stream, 0, status, request->version);
609
610 free (rpath);
611 free (fs_path);
612 return E_OK;
613 }
614 */
615
616 static ecode_t
617 freehttpd_loop (freehttpd_t *freehttpd)
618 {
619 while (true)
620 {
621 struct sockaddr_in client_addr = { 0 };
622 socklen_t client_addr_len = sizeof (client_addr);
623 int client_sockfd
624 = accept (freehttpd->sockfd, (struct sockaddr *) &client_addr,
625 &client_addr_len);
626
627 if (client_sockfd < 0)
628 return E_SYSCALL_ACCEPT;
629
630 ecode_t code = E_OK;
631 freehttpd_request_t *request
632 = freehttpd_request_parse (freehttpd, client_sockfd, &code);
633
634 if (code != E_OK)
635 {
636 log_err (LOG_ERR "failed to parse request: %i\n", code);
637 freehttpd_send_error (freehttpd, NULL, client_sockfd,
638 FREEHTTPD_STATUS_BAD_REQUEST,
639 HTTP1_1);
640 continue;
641 }
642
643 FILE *stream = fdopen (client_sockfd, "w");
644
645 if (stream == NULL)
646 {
647 log_err (LOG_ERR "failed to open stream\n");
648 freehttpd_send_error (
649 freehttpd, NULL, client_sockfd,
650 FREEHTTPD_STATUS_INTERNAL_SERVER_ERROR,
651 request->version);
652 freehttpd_request_free (request);
653 close (client_sockfd);
654 continue;
655 }
656
657 freehttpd_response_t *response = freehttpd_response_init (
658 stream, request->version, request->version_length,
659 FREEHTTPD_STATUS_OK);
660
661 if (response == NULL)
662 {
663 log_err (LOG_ERR "failed to init response\n");
664 freehttpd_send_error (
665 freehttpd, NULL, client_sockfd,
666 FREEHTTPD_STATUS_INTERNAL_SERVER_ERROR,
667 request->version);
668 freehttpd_request_free (request);
669 close (client_sockfd);
670 continue;
671 }
672
673 code = freehttpd_respond_http1x (freehttpd, request, response);
674
675 log_msg (LOG_INFO "%s %s HTTP/%s - %d %s\n", request->method,
676 request->uri, request->version, response->status.code,
677 response->status.text);
678
679 if (code != E_OK)
680 log_err (LOG_ERR "failed to send response: %i\n", code);
681
682 fclose (stream);
683 freehttpd_response_free (response);
684 freehttpd_request_free (request);
685 }
686
687 return E_OK;
688 }
689
690 ecode_t
691 freehttpd_start (freehttpd_t *restrict freehttpd)
692 {
693 ecode_t code = E_OK;
694 struct sockaddr_in addr_in;
695
696 if ((code = freehttpd_create_socket (freehttpd)) != E_OK)
697 return code;
698
699 addr_in = freehttpd_setup_addrinfo (freehttpd);
700
701 if ((code = freehttpd_bind (freehttpd, &addr_in)) != E_OK)
702 return code;
703
704 if ((code = freehttpd_listen (freehttpd)) != E_OK)
705 return code;
706
707 return freehttpd_loop (freehttpd);
708 }
709
710 const freehttpd_config_t *
711 freehttpd_get_config (freehttpd_t *freehttpd)
712 {
713 return freehttpd->config;
714 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26