back to scratko.xyz
summaryrefslogtreecommitdiff
path: root/http_server.c
diff options
context:
space:
mode:
authorscratko <m@scratko.xyz>2024-07-17 02:33:49 +0300
committerscratko <m@scratko.xyz>2024-07-17 02:33:49 +0300
commit5d4ea2faee069e94468e14232d09b774903285f1 (patch)
treefcf6fa983c84d526b725c8f94a813d01aa76aa1a /http_server.c
downloadhttp-server-5d4ea2faee069e94468e14232d09b774903285f1.tar.gz
http-server-5d4ea2faee069e94468e14232d09b774903285f1.tar.bz2
http-server-5d4ea2faee069e94468e14232d09b774903285f1.zip
Initial commit
Diffstat (limited to 'http_server.c')
-rw-r--r--http_server.c580
1 files changed, 580 insertions, 0 deletions
diff --git a/http_server.c b/http_server.c
new file mode 100644
index 0000000..e769c09
--- /dev/null
+++ b/http_server.c
@@ -0,0 +1,580 @@
+#include <stdio.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <syslog.h>
+
+
+#ifndef INBUFSIZE
+#define INBUFSIZE 4096
+#endif
+
+#ifndef OUTBUFSIZE
+#define OUTBUFSIZE 4096
+#endif
+
+#ifndef LISTEN_QLEN
+#define LISTEN_QLEN 32
+#endif
+
+#ifndef INIT_SESS_ARR_SIZE
+#define INIT_SESS_ARR_SIZE 32
+#endif
+
+#ifndef FILENAME_SIZE
+#define FILENAME_SIZE 1024
+#endif
+
+enum fsm_states {
+ header_start,
+ waiting_host,
+ empty_line,
+ writing_header,
+ writing_body
+};
+
+struct session {
+ int fd;
+ unsigned long from_ip;
+ unsigned short from_port;
+ char in_buffer[INBUFSIZE];
+ char out_buffer[OUTBUFSIZE];
+ int in_buf_used;
+ int out_buf_used;
+ enum fsm_states state;
+ int rs_fd;
+ int data_transfer_status;
+ char *path;
+ int bad_request;
+ int not_found;
+};
+
+struct server {
+ int listen_socket;
+ struct session **session_array;
+ int session_size;
+ char *host;
+ char *resource_dir;
+};
+
+static void init_session(struct session *new_session, struct sockaddr_in *from,
+ int fd)
+{
+ new_session->fd = fd;
+ new_session->from_ip = ntohl(from->sin_addr.s_addr);
+ new_session->from_port = ntohs(from->sin_port);
+ new_session->in_buf_used = 0;
+ new_session->out_buf_used = 0;
+ new_session->state = header_start;
+ new_session->rs_fd = -1;
+ new_session->data_transfer_status = 0;
+ new_session->path = NULL;
+ new_session->bad_request = 0;
+ new_session->not_found = 0;
+}
+
+static int accept_client(struct server *serv)
+{
+ int fd, i, flags;
+ struct sockaddr_in addr;
+ struct session *new_session;
+ socklen_t len = sizeof(addr);
+ fd = accept(serv->listen_socket, (struct sockaddr*) &addr, &len);
+ if(fd == -1)
+ return 0;
+ /* setting non-blocking mode for socket*/
+ flags = fcntl(fd, F_GETFL);
+ fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+
+ if(fd >= serv->session_size) {
+ int newlen = serv->session_size;
+ while(fd >= newlen)
+ newlen += INIT_SESS_ARR_SIZE;
+ serv->session_array =
+ realloc(serv->session_array, newlen * sizeof(struct session*));
+ for(i = serv->session_size; i < newlen; i++)
+ serv->session_array[i] = NULL;
+ serv->session_size = newlen;
+ }
+ new_session = malloc(sizeof(struct session));
+ serv->session_array[fd] = new_session;
+ init_session(new_session, &addr, fd);
+ return 1;
+}
+
+static int host_search_condition(const char *line)
+{
+ return !(strncmp(line, "Host:", strlen("Host:")) &&
+ strncmp(line, "host:", strlen("host:")));
+}
+
+static int match_hosts(struct server *serv, const char *line, int length)
+{
+ int i, last_idx;
+
+ if(strlen("host:") > length)
+ return 0;
+ if(!host_search_condition(line))
+ return 0;
+ for(i = strlen("host:"); i < length; ++i) {
+ if(line[i] == ' ')
+ continue;
+ break;
+ }
+ if(i+strlen(serv->host) > length)
+ return 0;
+ if(strncmp(line+i, serv->host, strlen(serv->host)))
+ return 0;
+ for(last_idx = i+strlen(serv->host); last_idx < length; ++last_idx) {
+ if(line[last_idx] == ' ')
+ continue;
+ else
+ break;
+ }
+ if(line[last_idx] == '\r'|| line[last_idx] == '\n')
+ return 1;
+ return 0;
+}
+
+static int match_protocols(const char *line, int length)
+{
+ int i;
+ /* skip extract path */
+ for(i = 4; i < length && line[i] != ' '; ++i)
+ {}
+ if(i >= length)
+ return 0;
+ ++i;
+ if(i+strlen("HTTP/1.1") > length)
+ return 0;
+ if(strncmp(line+i, "HTTP/1.1", strlen("HTTP/1.1")))
+ return 0;
+ for(i += strlen("HTTP/1.1"); i < length; ++i) {
+ if(line[i] == ' ')
+ continue;
+ if(line[i] == '\r' || line[i] == '\n')
+ return 1;
+ return 0;
+ }
+ return 0;
+}
+
+static char* extract_path(const char *line, int length)
+{
+ int i;
+ char *path;
+
+ for(i = 4; i < length && line[i] != ' '; ++i)
+ {}
+ if(i >= length)
+ return NULL;
+
+ length = i-4+1 ;
+ path = malloc(length);
+ memcpy(path, line+4, length-1);
+ path[length-1] = '\0';
+ return path;
+}
+
+static int command_processing(struct server *serv, struct session *cur_session,
+ const char *line, int length)
+{
+ switch(cur_session->state) {
+ case header_start:
+ if(length > strlen("GET") && !strncmp(line, "GET", strlen("GET"))) {
+ cur_session->path = extract_path(line, length);
+ if(cur_session->path == NULL)
+ return 0;
+ if(!match_protocols(line, length))
+ return 0;
+ cur_session->state = waiting_host;
+ return 1;
+ }
+ case waiting_host:
+ if(!match_hosts(serv, line, length))
+ return 0;
+ cur_session->state = empty_line;
+ return 1;
+ case empty_line:
+ if(line == NULL) {
+ cur_session->data_transfer_status = 1;
+ cur_session->state = writing_header;
+ return 1;
+ }
+ default:
+ return 0;
+ }
+}
+
+/*
+ * 0 - close current session
+ * 1 - continue
+ */
+static int read_in_session(struct server *serv, struct session *cur_session)
+{
+ int read_res, i, end_idx;
+ char *line = NULL;
+ end_idx = -1;
+
+ if(!cur_session->in_buf_used)
+ read_res = read(cur_session->fd, cur_session->in_buffer, INBUFSIZE);
+ if(read_res <= 0)
+ return 0;
+
+ cur_session->in_buf_used += read_res;
+
+ do {
+ for(i = 0; i < cur_session->in_buf_used; ++i) {
+ if(cur_session->in_buffer[i] == '\n') {
+ end_idx = i;
+ break;
+ }
+ }
+ if(end_idx == -1) {
+ if(cur_session->in_buf_used == INBUFSIZE)
+ return 0;
+ return 1;
+ }
+ if(cur_session->state != empty_line) {
+ line = malloc(end_idx+1);
+ memcpy(line, cur_session->in_buffer, end_idx+1);
+ }
+ cur_session->in_buf_used -= i+1;
+ memmove(cur_session->in_buffer, cur_session->in_buffer+end_idx+1,
+ cur_session->in_buf_used);
+
+ /* skip the remaining data in the header */
+ if(cur_session->state == empty_line &&
+ strncmp(cur_session->in_buffer, "\r\n", 2))
+ continue;
+ else if(cur_session->state == empty_line &&
+ !strncmp(cur_session->in_buffer, "\r\n", 2))
+ cur_session->in_buf_used = 0;
+
+ if(!command_processing(serv, cur_session, line, end_idx)) {
+ cur_session->bad_request = 1;
+ cur_session->data_transfer_status = 1;
+ cur_session->state = writing_header;
+ }
+ if(line) {
+ free(line);
+ line = NULL;
+ }
+ if(cur_session->bad_request)
+ return 1;
+ } while(cur_session->in_buf_used);
+ return 1;
+}
+
+static void print_header(struct session *cur_session, char *status)
+{
+ write(cur_session->fd, status, strlen(status));
+}
+
+static int is_index_page(char *path)
+{
+ int i;
+ for(i = strlen(path)-1; i >= 0 && path[i] != '/'; --i) {
+ if(path[i] == '.')
+ return 0;
+ }
+ return 1;
+}
+
+static void remove_slash(char *root)
+{
+ int last_idx = strlen(root);
+ --last_idx;
+ if(root[last_idx] == '/')
+ root[last_idx] = '\0';
+}
+
+static int open_file(char *root, struct session *cur_session)
+{
+ int i, j;
+ int add_index_page = 0;
+ char *given_path = cur_session->path;
+ char *str_index_page = "index.html";
+ char new_path[FILENAME_SIZE];
+
+ remove_slash(root);
+ strncpy(new_path, root, strlen(root));
+ add_index_page = is_index_page(given_path) ? 1 : 0;
+ for(i = strlen(root), j = 0; i < FILENAME_SIZE &&
+ j < strlen(given_path); ++i, ++j)
+ new_path[i] = given_path[j];
+ if(add_index_page) {
+ for(j = 0; i < FILENAME_SIZE && j < strlen(str_index_page); ++i, ++j)
+ new_path[i] = str_index_page[j];
+ }
+ if(i < FILENAME_SIZE)
+ new_path[i] = '\0';
+ cur_session->rs_fd = open(new_path, O_RDONLY);
+ if(cur_session->rs_fd == -1)
+ return 0;
+ else return 1;
+}
+
+static void change_out_buffer(char *out_buffer, int *out_buf_used,
+ int writed_size)
+{
+ memmove(out_buffer, out_buffer+writed_size, (*out_buffer)-writed_size);
+ *out_buf_used -= writed_size;
+}
+
+static void write_error_page(struct session *cur_session)
+{
+ int read_result;
+
+ read_result = read(cur_session->rs_fd, cur_session->out_buffer,
+ OUTBUFSIZE);
+ write(cur_session->fd, cur_session->out_buffer, read_result);
+}
+
+static int file_open_condition(struct session *cur_session)
+{
+ return !cur_session->bad_request && !cur_session->not_found &&
+ cur_session->rs_fd == -1;
+}
+
+static char* get_page_error_path(struct server *serv, char *page)
+{
+ char *new_path;
+
+ new_path = malloc(strlen(serv->resource_dir) + strlen(page));
+ strcpy(new_path, serv->resource_dir);
+ strcat(new_path, page);
+ return new_path;
+}
+
+static int write_in_session(struct server *serv, struct session *cur_session)
+{
+ int read_result, write_result, open_result;
+ char *page_error_path = NULL;
+
+ if(file_open_condition(cur_session)) {
+ open_result = open_file(serv->resource_dir, cur_session);
+ if(!open_result) {
+ cur_session->state = writing_header;
+ cur_session->not_found = 1;
+ }
+ }
+ switch(cur_session->state) {
+ case writing_header:
+ if(cur_session->bad_request)
+ print_header(cur_session, "HTTP/1.1 400 Bad Request\n\n");
+ else if(cur_session->not_found)
+ print_header(cur_session, "HTTP/1.1 404 Not Found\n\n");
+ else
+ print_header(cur_session, "HTTP/1.1 200 OK\n\n");
+ cur_session->state = writing_body;
+ return 1;
+ case writing_body:
+ /* ---------- error page write ---------- */
+ if(cur_session->bad_request)
+ page_error_path = get_page_error_path(serv, "/400.html");
+ else if(cur_session->not_found)
+ page_error_path = get_page_error_path(serv, "/404.html");
+
+ if(cur_session->bad_request || cur_session->not_found) {
+ cur_session->rs_fd = open(page_error_path, O_RDONLY);
+ if(cur_session->rs_fd == -1)
+ syslog(LOG_ERR, "can't find error page");
+ free(page_error_path);
+ write_error_page(cur_session);
+ return 0;
+ }
+ /* ---------- normal page write ---------- */
+
+ /* if buffer doesn't contain any data*/
+ if(!cur_session->out_buf_used) {
+ read_result = read(cur_session->rs_fd, cur_session->out_buffer,
+ OUTBUFSIZE);
+ /* end of file */
+ if(!read_result) {
+ close(cur_session->rs_fd);
+ cur_session->rs_fd = -1;
+ cur_session->data_transfer_status = 0;
+ cur_session->state = header_start;
+ return 0;
+ }
+ cur_session->out_buf_used += read_result;
+ }
+ write_result = write(cur_session->fd, cur_session->out_buffer,
+ cur_session->out_buf_used);
+
+ /* writed remaining some of data */
+ if(write_result < cur_session->out_buf_used)
+ change_out_buffer(cur_session->out_buffer,
+ &cur_session->out_buf_used,
+ write_result);
+ /* writed all data from out_buffer */
+ else
+ cur_session->out_buf_used = 0;
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static void close_session(struct server *serv, int fd)
+{
+ close(fd);
+ serv->session_array[fd]->fd = -1;
+ if(serv->session_array[fd]->path)
+ free(serv->session_array[fd]->path);
+ if(serv->session_array[fd]->rs_fd)
+ close(serv->session_array[fd]->rs_fd);
+ serv->session_array[fd] = NULL;
+}
+
+static int reading_condition(struct session *cur_session)
+{
+ return cur_session && !cur_session->data_transfer_status;
+}
+
+static int writing_condition(struct session *cur_session)
+{
+ return cur_session && cur_session->data_transfer_status;
+}
+
+int main_loop(struct server *serv)
+{
+ int i, maxfd, select_result, accept_result, read_result, write_result;
+ fd_set readfds, writefds;
+
+ for(;;) {
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+ FD_SET(serv->listen_socket, &readfds);
+ maxfd = serv->listen_socket;
+ for(i = 0; i < serv->session_size; ++i) {
+ if(reading_condition(serv->session_array[i])) {
+ FD_SET(i, &readfds);
+ if(i > maxfd)
+ maxfd = i;
+ }
+ if(writing_condition(serv->session_array[i])) {
+ FD_SET(i, &writefds);
+ if(i > maxfd)
+ maxfd = i;
+ }
+ }
+
+ select_result = select(maxfd + 1, &readfds, &writefds, NULL, NULL);
+ if(!select_result) {
+ syslog(LOG_ERR, "select error");
+ return 3;
+ }
+
+ if(FD_ISSET(serv->listen_socket, &readfds)) {
+ accept_result = accept_client(serv);
+ if(!accept_result) {
+ syslog(LOG_ERR, "accept error");
+ return 4;
+ }
+ }
+
+ for(i = 0; i < serv->session_size; ++i) {
+ if(serv->session_array[i] && FD_ISSET(i, &readfds)) {
+ read_result = read_in_session(serv, serv->session_array[i]);
+ /* normal connection closure (by user or full of in_buffer) */
+ if(!read_result)
+ close_session(serv, i);
+ }
+ if(serv->session_array[i] && FD_ISSET(i, &writefds)) {
+ write_result = write_in_session(serv, serv->session_array[i]);;
+ /*
+ * Closing the connection if all data is written or
+ * error 400 or 404 occurs
+ */
+ if(!write_result)
+ close_session(serv, i);
+ }
+ }
+ }
+}
+
+/*
+ * 0 - error
+ * 1 - success
+ */
+static int init_server(struct server *serv, int port, char *host,
+ char *resource_dir)
+{
+ int opt, i;
+ struct sockaddr_in addr;
+
+ serv->host = host;
+ serv->resource_dir = resource_dir;
+
+ serv->listen_socket = socket(AF_INET, SOCK_STREAM, 0);
+ if(serv->listen_socket == -1)
+ return 0;
+
+ opt = 1;
+ setsockopt(serv->listen_socket, SOL_SOCKET, SO_REUSEADDR, &opt,
+ sizeof(opt));
+
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ addr.sin_port = htons(port);
+
+ if(bind(serv->listen_socket, (struct sockaddr*) &addr, sizeof(addr)) == -1)
+ return 0;
+
+ serv->session_array = malloc(sizeof(serv->session_array) *
+ INIT_SESS_ARR_SIZE);
+ serv->session_size = INIT_SESS_ARR_SIZE;
+ for(i = 0; i < INIT_SESS_ARR_SIZE; i++)
+ serv->session_array[i] = NULL;
+
+ listen(serv->listen_socket, LISTEN_QLEN);
+ return 1;
+}
+
+static void demonization()
+{
+ int pid;
+
+ close(0);
+ close(1);
+ close(2);
+ open("/dev/null", O_RDONLY);
+ open("/dev/null", O_WRONLY);
+ open("/dev/null", O_WRONLY);
+ chdir("/");
+ pid = fork();
+ if(pid)
+ exit(0);
+ setsid();
+ pid = fork();
+ if(pid)
+ exit(0);
+}
+
+int main(int argc, char **argv)
+{
+ unsigned short port;
+ struct server serv;
+
+ if(argc != 4) {
+ fprintf(stderr,
+ "Usage: <port> <host> <full_path/to/host_files\n");
+ return 1;
+ }
+ demonization();
+ openlog("scratko's http server", 0, LOG_USER);
+ syslog(LOG_INFO, "daemon started");
+ port = strtol(argv[1], NULL, 10);
+
+ if(!init_server(&serv, port, argv[2], argv[3])) {
+ syslog(LOG_ERR, "server initialization error");
+ return 2;
+ }
+ return main_loop(&serv);
+ syslog(LOG_INFO, "server terminated");
+}