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

Contents of /trunk/freehttpd/freehttpd.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 45 - (show annotations)
Sun Aug 11 17:35:46 2024 UTC (7 months, 2 weeks ago) by rakinar2
File MIME type: text/x-c
File size: 18480 byte(s)
feat(freehttpd): file serving and directory indexing

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26