Compare commits
No commits in common. "master" and "config-3" have entirely different histories.
35 changed files with 138 additions and 2471 deletions
57
Makefile
57
Makefile
|
|
@ -1,57 +0,0 @@
|
||||||
CC = gcc
|
|
||||||
CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic
|
|
||||||
LDFLAGS =
|
|
||||||
LDLIBS =
|
|
||||||
|
|
||||||
CFLAGS_DBG = -g
|
|
||||||
ASAN_DBG_FLAGS = -fsanitize=address
|
|
||||||
|
|
||||||
UTILS_SRCS = src/utils/string/string.c \
|
|
||||||
src/utils/time/fmt_time.c \
|
|
||||||
src/utils/parsing/words.c \
|
|
||||||
src/utils/parsing/blanks.c \
|
|
||||||
src/utils/files/files.c
|
|
||||||
MODULES_SRCS = src/config/config.c \
|
|
||||||
src/server/server.c \
|
|
||||||
src/http/http.c \
|
|
||||||
src/http/headers.c \
|
|
||||||
src/logger/logs.c \
|
|
||||||
src/logger/errors.c \
|
|
||||||
src/daemon/daemon.c
|
|
||||||
SRCS = $(UTILS_SRCS) \
|
|
||||||
$(MODULES_SRCS) \
|
|
||||||
src/main.c
|
|
||||||
|
|
||||||
UTILS_OBJS = ${UTILS_SRCS:.c=.o}
|
|
||||||
MODULES_OBJS = ${MODULES_SRCS:.c=.o}
|
|
||||||
OBJS = ${SRCS:.c=.o}
|
|
||||||
|
|
||||||
TARGET=httpd
|
|
||||||
|
|
||||||
$(TARGET): $(OBJS)
|
|
||||||
$(CC) -o $@ $(OBJS) $(LDFLAGS) $(LDLIBS)
|
|
||||||
|
|
||||||
check: $(TARGET)
|
|
||||||
cp $(TARGET) tests/$(TARGET)
|
|
||||||
cd tests
|
|
||||||
python3 -m venv env
|
|
||||||
env/bin/python -m pip install requests
|
|
||||||
env/bin/python -m pip install pytest
|
|
||||||
env/bin/python -m pip install pytest-timeout
|
|
||||||
- env/bin/pytest
|
|
||||||
|
|
||||||
debug: CFLAGS += $(CFLAGS_DBG)
|
|
||||||
debug: $(OBJS)
|
|
||||||
$(CC) -o $(TARGET) $(OBJS) $(LDFLAGS) $(LDLIBS)
|
|
||||||
|
|
||||||
|
|
||||||
asan: CFLAGS += $(CFLAGS_DBG)
|
|
||||||
asan: LDFLAGS += $(ASAN_DBG_FLAGS)
|
|
||||||
asan: $(OBJS)
|
|
||||||
$(CC) -o $(TARGET) $(OBJS) $(LDFLAGS) $(LDLIBS)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
- pkill -9 $(TARGET)
|
|
||||||
$(RM) tests/$(TARGET) tests/out.log
|
|
||||||
$(RM) $(TARGET)
|
|
||||||
$(RM) $(OBJS)
|
|
||||||
2
.gitignore → httpd/.gitignore
vendored
2
.gitignore → httpd/.gitignore
vendored
|
|
@ -6,5 +6,3 @@
|
||||||
*.log
|
*.log
|
||||||
*.core
|
*.core
|
||||||
httpd
|
httpd
|
||||||
__pycache__
|
|
||||||
env/
|
|
||||||
37
httpd/Makefile
Normal file
37
httpd/Makefile
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
CC = gcc
|
||||||
|
CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic
|
||||||
|
LDFLAGS =
|
||||||
|
LDLIBS =
|
||||||
|
|
||||||
|
CFLAGS_DBG = -g
|
||||||
|
ASAN_DBG_FLAGS = -fsanitize=address
|
||||||
|
|
||||||
|
UTILS_SRCS = src/utils/string/string.c
|
||||||
|
CONFIG_SRCS = src/config/config.c
|
||||||
|
SRCS = $(UTILS_SRCS) $(CONFIG_SRCS) src/main.c
|
||||||
|
|
||||||
|
UTILS_OBJS = ${UTILS_SRCS:.c=.o}
|
||||||
|
CONFIG_OBJS = ${CONFIG_SRCS:.c=.o}
|
||||||
|
OBJS = ${SRCS:.c=.o}
|
||||||
|
|
||||||
|
TARGET=httpd
|
||||||
|
|
||||||
|
$(TARGET): $(OBJS)
|
||||||
|
$(CC) -o $@ $(OBJS) $(LDFLAGS) $(LDLIBS)
|
||||||
|
|
||||||
|
check:
|
||||||
|
dash tests/run.sh
|
||||||
|
|
||||||
|
debug: CFLAGS += $(CFLAGS_DBG)
|
||||||
|
debug: $(OBJS)
|
||||||
|
$(CC) -o $(TARGET) $(OBJS) $(LDFLAGS) $(LDLIBS)
|
||||||
|
|
||||||
|
|
||||||
|
asan: CFLAGS += $(CFLAGS_DBG)
|
||||||
|
asan: LDFLAGS += $(ASAN_DBG_FLAGS)
|
||||||
|
asan: $(OBJS)
|
||||||
|
$(CC) -o $(TARGET) $(OBJS) $(LDFLAGS) $(LDLIBS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) $(TARGET)
|
||||||
|
$(RM) $(OBJS)
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
[global]
|
[global]
|
||||||
log = true
|
log = true
|
||||||
pid_file = /tmp/HTTPd.pid
|
pid_file = /tmp/HTTPd.pid
|
||||||
daemon = start
|
|
||||||
|
|
||||||
[[vhosts]]
|
[[vhosts]]
|
||||||
server_name = my_server
|
server_name = my_server
|
||||||
ip = 127.0.0.1
|
ip = 127.0.0.1
|
||||||
port = 6996
|
port = 6996
|
||||||
root_dir = .
|
root_dir = .
|
||||||
default_file='/dev/null'
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
// #include "../utils/string/string.h"
|
#include "../utils/string/string.h"
|
||||||
// #include "bits/getopt_ext.h"
|
#include "bits/getopt_ext.h"
|
||||||
|
|
||||||
#define ARG_VALID 0
|
#define ARG_VALID 0
|
||||||
#define ARG_INVALID 1
|
#define ARG_INVALID 1
|
||||||
|
|
@ -50,10 +50,6 @@ static void print_help(char *program_name)
|
||||||
// limit imposed by school
|
// limit imposed by school
|
||||||
static int parse_daemon_arg(struct config *cfg)
|
static int parse_daemon_arg(struct config *cfg)
|
||||||
{
|
{
|
||||||
// Multiple contradictory parameters
|
|
||||||
if (cfg->daemon != NO_OPTION)
|
|
||||||
return ARG_INVALID;
|
|
||||||
|
|
||||||
if (strcmp(optarg, "start") == 0)
|
if (strcmp(optarg, "start") == 0)
|
||||||
cfg->daemon = START;
|
cfg->daemon = START;
|
||||||
else if (strcmp(optarg, "stop") == 0)
|
else if (strcmp(optarg, "stop") == 0)
|
||||||
|
|
@ -101,7 +97,7 @@ static int handle_opt(char **argv, char opt, struct config *cfg)
|
||||||
|
|
||||||
// Server name
|
// Server name
|
||||||
case 's':
|
case 's':
|
||||||
cfg->servers->server_name = optarg;
|
cfg->servers->server_name = string_create(optarg, strlen(optarg));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Port
|
// Port
|
||||||
|
|
@ -121,7 +117,7 @@ static int handle_opt(char **argv, char opt, struct config *cfg)
|
||||||
|
|
||||||
// Default file
|
// Default file
|
||||||
case 'D':
|
case 'D':
|
||||||
cfg->servers->default_file = optarg;
|
cfg->servers->ip = optarg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// PID file
|
// PID file
|
||||||
|
|
@ -171,48 +167,28 @@ static void print_arg_error(int err, char **argv, struct option options[],
|
||||||
printf("%s: Invalid value for '--%s'\n", argv[0],
|
printf("%s: Invalid value for '--%s'\n", argv[0],
|
||||||
options[optindex].name);
|
options[optindex].name);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
|
||||||
printf(
|
|
||||||
"%s: An unknown error happened while trying to parse arguments\n",
|
|
||||||
argv[0]);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// static void apply_default_values(struct config *cfg)
|
|
||||||
// {
|
|
||||||
// // Default file
|
|
||||||
// if (cfg->servers->default_file == NULL)
|
|
||||||
// {
|
|
||||||
// char *default_df = DEFAULT_DF;
|
|
||||||
// cfg->servers->default_file =
|
|
||||||
// malloc((strlen(default_df) + 1) * sizeof(char));
|
|
||||||
// // TODO handle error
|
|
||||||
// strcpy(cfg->servers->default_file, default_df);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// == Main functions
|
// == Main functions
|
||||||
|
|
||||||
struct config *parse_configuration(int argc, char *argv[])
|
struct config *parse_configuration(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
struct option options[] = {
|
struct option options[] = { // Global
|
||||||
// Global
|
{ "daemon", required_argument, NULL, 'd' },
|
||||||
{ "daemon", required_argument, NULL, 'd' },
|
{ "help", no_argument, NULL, 'h' },
|
||||||
{ "help", no_argument, NULL, 'h' },
|
// Vhosts
|
||||||
// Vhosts
|
{ "server_name", required_argument, NULL, 's' },
|
||||||
{ "server_name", required_argument, NULL, 's' },
|
{ "port", required_argument, NULL, 'p' },
|
||||||
{ "port", required_argument, NULL, 'p' },
|
{ "ip", required_argument, NULL, 'i' },
|
||||||
{ "ip", required_argument, NULL, 'i' },
|
{ "root_dir", required_argument, NULL, 'r' },
|
||||||
{ "root_dir", required_argument, NULL, 'r' },
|
{ "defaut_file", required_argument, NULL, 'D' },
|
||||||
{ "default_file", required_argument, NULL, 'D' },
|
// Logging
|
||||||
// Logging
|
{ "pid_file", required_argument, NULL, 'P' },
|
||||||
{ "pid_file", required_argument, NULL, 'P' },
|
{ "log_file", required_argument, NULL, 'L' },
|
||||||
{ "log_file", required_argument, NULL, 'L' },
|
{ "log", required_argument, NULL, 'l' },
|
||||||
{ "log", required_argument, NULL, 'l' },
|
// End
|
||||||
// End
|
{ NULL, 0, NULL, 0 }
|
||||||
{ NULL, 0, NULL, 0 }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct config *config = calloc(1, sizeof(struct config));
|
struct config *config = calloc(1, sizeof(struct config));
|
||||||
|
|
@ -240,12 +216,10 @@ struct config *parse_configuration(int argc, char *argv[])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply_default_values(config);
|
|
||||||
|
|
||||||
// Check config validity
|
// Check config validity
|
||||||
if (check_config(config) != 0)
|
if (check_config(config) != 0)
|
||||||
{
|
{
|
||||||
printf("%s: Missing mandatory flags, cannot continue.\n", argv[0]);
|
printf("%s: Missing mandatory flags, cannot continue.", argv[0]);
|
||||||
config_destroy(config);
|
config_destroy(config);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -2,13 +2,9 @@
|
||||||
#define CONFIG_H
|
#define CONFIG_H
|
||||||
|
|
||||||
#define _XOPEN_SOURCE 500
|
#define _XOPEN_SOURCE 500
|
||||||
#define HTTP_VERSION "HTTP/1.1"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
// Default values
|
|
||||||
#define DEFAULT_DF "index.html"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** @brief Enum daemon
|
** @brief Enum daemon
|
||||||
** NO_OPTION if the '--daemon' option is not given
|
** NO_OPTION if the '--daemon' option is not given
|
||||||
|
|
@ -36,7 +32,6 @@ struct config
|
||||||
char *pid_file;
|
char *pid_file;
|
||||||
char *log_file;
|
char *log_file;
|
||||||
bool log;
|
bool log;
|
||||||
char *protocol_version;
|
|
||||||
|
|
||||||
struct server_config *servers;
|
struct server_config *servers;
|
||||||
enum daemon daemon;
|
enum daemon daemon;
|
||||||
|
|
@ -53,7 +48,7 @@ struct config
|
||||||
*/
|
*/
|
||||||
struct server_config
|
struct server_config
|
||||||
{
|
{
|
||||||
char *server_name;
|
struct string *server_name;
|
||||||
char *port;
|
char *port;
|
||||||
char *ip;
|
char *ip;
|
||||||
char *root_dir;
|
char *root_dir;
|
||||||
10
httpd/src/main.c
Normal file
10
httpd/src/main.c
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "config/config.h"
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
parse_configuration(argc, argv);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
70
httpd/src/utils/string/string.c
Normal file
70
httpd/src/utils/string/string.c
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
#include "string.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
struct string *string_create(const char *str, size_t size)
|
||||||
|
{
|
||||||
|
struct string *res = malloc(sizeof(struct string));
|
||||||
|
if (res == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
res->data = malloc(size * sizeof(char));
|
||||||
|
if (res->data == NULL)
|
||||||
|
{
|
||||||
|
free(res);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(res->data, str, size);
|
||||||
|
res->size = size;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int string_compare_n_str(const struct string *str1, const char *str2, size_t n)
|
||||||
|
{
|
||||||
|
size_t i = 0;
|
||||||
|
int res = 0;
|
||||||
|
char reached_str2_end = 0;
|
||||||
|
while (i < n)
|
||||||
|
{
|
||||||
|
if (i < str1->size)
|
||||||
|
res += str1->data[i];
|
||||||
|
|
||||||
|
if (!reached_str2_end)
|
||||||
|
{
|
||||||
|
if (str2[i] == '\0')
|
||||||
|
reached_str2_end = 1;
|
||||||
|
else
|
||||||
|
res -= str2[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void string_concat_str(struct string *str, const char *to_concat, size_t size)
|
||||||
|
{
|
||||||
|
size_t new_size = str->size + size;
|
||||||
|
size_t str_size = str->size;
|
||||||
|
|
||||||
|
if (new_size == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
str->data = realloc(str->data, new_size);
|
||||||
|
if (str->data == NULL)
|
||||||
|
return; // Handle ?
|
||||||
|
|
||||||
|
for (size_t i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
str->data[str_size + i] = to_concat[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void string_destroy(struct string *str)
|
||||||
|
{
|
||||||
|
free(str->data);
|
||||||
|
free(str);
|
||||||
|
}
|
||||||
|
|
@ -42,48 +42,6 @@ int string_compare_n_str(const struct string *str1, const char *str2, size_t n);
|
||||||
*/
|
*/
|
||||||
void string_concat_str(struct string *str, const char *to_concat, size_t size);
|
void string_concat_str(struct string *str, const char *to_concat, size_t size);
|
||||||
|
|
||||||
/*
|
|
||||||
** @brief Similar to string_concat_str but with str at the beginning of the
|
|
||||||
* result string
|
|
||||||
**
|
|
||||||
** @param str
|
|
||||||
** @param to_concat
|
|
||||||
** @param size
|
|
||||||
*/
|
|
||||||
void str_concat_string(const char *str, size_t size, struct string *to_concat);
|
|
||||||
|
|
||||||
/*
|
|
||||||
** @brief Concat a char * with its size in a struct string
|
|
||||||
**
|
|
||||||
** @param str
|
|
||||||
*/
|
|
||||||
void string_to_lowercase(struct string *str);
|
|
||||||
|
|
||||||
/*
|
|
||||||
** @brief Free all string content
|
|
||||||
**
|
|
||||||
** @param str
|
|
||||||
*/
|
|
||||||
void string_destroy(struct string *str);
|
void string_destroy(struct string *str);
|
||||||
|
|
||||||
/*
|
|
||||||
** @brief Converts the string to the native C implementation
|
|
||||||
**
|
|
||||||
** @param str
|
|
||||||
**
|
|
||||||
** @return a pointer to an allocated memory zone containing the string
|
|
||||||
*/
|
|
||||||
char *string_to_charptr(struct string *str);
|
|
||||||
|
|
||||||
/*
|
|
||||||
** @brief TODO
|
|
||||||
**
|
|
||||||
** @param str1
|
|
||||||
** @param str2
|
|
||||||
** @param n
|
|
||||||
**
|
|
||||||
** @return
|
|
||||||
*/
|
|
||||||
int string_compare_strictly_n_str(const struct string *str1, const char *str2,
|
|
||||||
size_t n);
|
|
||||||
#endif /* ! STRING_H */
|
#endif /* ! STRING_H */
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
#include "daemon.h"
|
|
||||||
|
|
||||||
#include <signal.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "../server/server.h"
|
|
||||||
#include "../utils/files/files.h"
|
|
||||||
|
|
||||||
static struct config *config;
|
|
||||||
|
|
||||||
// === Functions
|
|
||||||
|
|
||||||
void daemon_init(struct config *cfg)
|
|
||||||
{
|
|
||||||
config = cfg;
|
|
||||||
}
|
|
||||||
|
|
||||||
int get_pid(void)
|
|
||||||
{
|
|
||||||
FILE *stream = fopen(config->pid_file, "r");
|
|
||||||
if (stream == NULL)
|
|
||||||
return -2;
|
|
||||||
|
|
||||||
char buf[10];
|
|
||||||
size_t nread = fread(buf, sizeof(char), 10, stream);
|
|
||||||
if (nread > 8) // PID is max 8 chars
|
|
||||||
return -3;
|
|
||||||
buf[nread] = '\0';
|
|
||||||
|
|
||||||
int res = atoi(buf);
|
|
||||||
|
|
||||||
fclose(stream);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop_daemon(void)
|
|
||||||
{
|
|
||||||
int pid = get_pid();
|
|
||||||
if (pid > 0)
|
|
||||||
kill(pid, SIGINT);
|
|
||||||
}
|
|
||||||
|
|
||||||
int start_daemon(void)
|
|
||||||
{
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (!pid) // Daemon
|
|
||||||
{
|
|
||||||
start_server(config);
|
|
||||||
}
|
|
||||||
else // Parent
|
|
||||||
{
|
|
||||||
// Write pid
|
|
||||||
int err = write_pid(config->pid_file, pid);
|
|
||||||
if (err != 0)
|
|
||||||
{
|
|
||||||
kill(pid, SIGINT);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int restart_daemon(void)
|
|
||||||
{
|
|
||||||
// Attempt to kill process
|
|
||||||
stop_daemon();
|
|
||||||
|
|
||||||
// Start again
|
|
||||||
return start_daemon();
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
#ifndef DAEMON_H
|
|
||||||
#define DAEMON_H
|
|
||||||
|
|
||||||
#include "../config/config.h"
|
|
||||||
|
|
||||||
/* @brief
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
int get_pid(void);
|
|
||||||
|
|
||||||
/* @brief
|
|
||||||
*/
|
|
||||||
void daemon_init(struct config *cfg);
|
|
||||||
|
|
||||||
/* @brief
|
|
||||||
*/
|
|
||||||
void stop_daemon(void);
|
|
||||||
|
|
||||||
/* @brief
|
|
||||||
*/
|
|
||||||
int start_daemon(void);
|
|
||||||
|
|
||||||
/* @brief
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
int restart_daemon(void);
|
|
||||||
|
|
||||||
#endif // ! DAEMON_H
|
|
||||||
|
|
@ -1,162 +0,0 @@
|
||||||
#include "headers.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "../utils/parsing/words.h"
|
|
||||||
#include "../utils/string/string.h"
|
|
||||||
|
|
||||||
void destroy_headers(struct http_header *headers)
|
|
||||||
{
|
|
||||||
while (headers != NULL)
|
|
||||||
{
|
|
||||||
struct http_header *next = headers->next;
|
|
||||||
string_destroy(headers->field);
|
|
||||||
string_destroy(headers->value);
|
|
||||||
free(headers);
|
|
||||||
headers = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t read_field(struct string *str, size_t offset, struct string **res)
|
|
||||||
{
|
|
||||||
ssize_t nread = read_word_delim(str, offset, res, ":\n");
|
|
||||||
if (nread <= 0)
|
|
||||||
return ERR_HTTP_INVALID_INPUT;
|
|
||||||
|
|
||||||
if (str->size <= offset + nread || str->data[offset + nread] != ':')
|
|
||||||
return ERR_HTTP_INVALID_INPUT;
|
|
||||||
|
|
||||||
string_to_lowercase(*res);
|
|
||||||
|
|
||||||
return nread;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t read_value(struct string *str, size_t offset, struct string **res)
|
|
||||||
{
|
|
||||||
ssize_t nread = read_word_delim(str, offset, res, "\n");
|
|
||||||
if (nread <= 0)
|
|
||||||
return ERR_HTTP_INVALID_INPUT;
|
|
||||||
|
|
||||||
if (str->size <= offset + nread || str->data[offset + nread] != '\n')
|
|
||||||
return ERR_HTTP_INVALID_INPUT;
|
|
||||||
|
|
||||||
// Trim trailing \r
|
|
||||||
if ((*res)->size > 0 && (*res)->data[(*res)->size - 1] == '\r')
|
|
||||||
{
|
|
||||||
(*res)->size--;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nread;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t parse_headers(struct http_request *res, struct string *req,
|
|
||||||
size_t offset)
|
|
||||||
{
|
|
||||||
size_t i = offset;
|
|
||||||
struct http_header *header = NULL;
|
|
||||||
|
|
||||||
// Yes I know I do one useless allocation but I really don't care at this
|
|
||||||
// point
|
|
||||||
while (i < req->size && req->data[i] != '\n'
|
|
||||||
&& req->data[i] != '\r') // ! Blank line
|
|
||||||
{
|
|
||||||
if (header == NULL)
|
|
||||||
{
|
|
||||||
// Init list
|
|
||||||
header = calloc(1, sizeof(struct http_header));
|
|
||||||
res->headers = header;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Append
|
|
||||||
header->next = calloc(1, sizeof(struct http_header));
|
|
||||||
header = header->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check allocation
|
|
||||||
if (header == NULL)
|
|
||||||
return ERR_HTTP_OUT_OF_MEMORY;
|
|
||||||
|
|
||||||
// Read field
|
|
||||||
ssize_t nread = read_field(req, i, &header->field);
|
|
||||||
if (nread <= 0)
|
|
||||||
return nread; // Contains error code when negative
|
|
||||||
|
|
||||||
i += nread + 1;
|
|
||||||
|
|
||||||
// Read value
|
|
||||||
nread = read_value(req, i, &header->value);
|
|
||||||
if (nread <= 0)
|
|
||||||
return nread; // Contains error code when negative
|
|
||||||
|
|
||||||
i += nread + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i < req->size && req->data[i] == '\r')
|
|
||||||
i++;
|
|
||||||
if (i < req->size && req->data[i] == '\n')
|
|
||||||
i++;
|
|
||||||
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct http_header *get_header(struct http_header *headers, const char *field)
|
|
||||||
{
|
|
||||||
while (headers != NULL)
|
|
||||||
{
|
|
||||||
if (string_compare_n_str(headers->field, field, strlen(field)) == 0)
|
|
||||||
return headers;
|
|
||||||
|
|
||||||
headers = headers->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct http_header *create_header(const char *field, const char *value)
|
|
||||||
{
|
|
||||||
struct http_header *res = calloc(1, sizeof(struct http_header));
|
|
||||||
if (res == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
// Field
|
|
||||||
ssize_t field_size = strlen(field);
|
|
||||||
if (field_size < 0)
|
|
||||||
{
|
|
||||||
free(res);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
res->field = string_create(field, field_size);
|
|
||||||
|
|
||||||
// Value
|
|
||||||
ssize_t value_size = strlen(value);
|
|
||||||
if (value_size < 0)
|
|
||||||
{
|
|
||||||
string_destroy(res->field);
|
|
||||||
free(res);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
res->value = string_create(value, value_size);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
void append_header(struct http_header **list, struct http_header *element)
|
|
||||||
{
|
|
||||||
if (list == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (*list == NULL)
|
|
||||||
*list = element;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
struct http_header *cur_elt = *list;
|
|
||||||
while (cur_elt->next != NULL)
|
|
||||||
{
|
|
||||||
cur_elt = cur_elt->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
cur_elt->next = element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
#ifndef HEADERS_H
|
|
||||||
#define HEADERS_H
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include "http.h"
|
|
||||||
|
|
||||||
// === Functions
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief
|
|
||||||
*
|
|
||||||
* @param headers
|
|
||||||
*/
|
|
||||||
void destroy_headers(struct http_header *headers);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief
|
|
||||||
*
|
|
||||||
* @param str
|
|
||||||
* @param offset
|
|
||||||
* @param res
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
ssize_t read_field(struct string *str, size_t offset, struct string **res);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief
|
|
||||||
*
|
|
||||||
* @param str
|
|
||||||
* @param offset
|
|
||||||
* @param res
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
ssize_t read_value(struct string *str, size_t offset, struct string **res);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief
|
|
||||||
*
|
|
||||||
* @param res
|
|
||||||
* @param req
|
|
||||||
* @param offset
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
ssize_t parse_headers(struct http_request *res, struct string *req,
|
|
||||||
size_t offset);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief
|
|
||||||
*
|
|
||||||
* @param headers
|
|
||||||
* @param field
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
struct http_header *get_header(struct http_header *headers, const char *field);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief
|
|
||||||
*
|
|
||||||
* @param field
|
|
||||||
* @param value
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
struct http_header *create_header(const char *field, const char *value);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief
|
|
||||||
*
|
|
||||||
* @param field
|
|
||||||
* @param value
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
void append_header(struct http_header **list, struct http_header *element);
|
|
||||||
|
|
||||||
#endif // ! HEADERS_H
|
|
||||||
511
src/http/http.c
511
src/http/http.c
|
|
@ -1,511 +0,0 @@
|
||||||
#include "http.h"
|
|
||||||
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <limits.h>
|
|
||||||
// #include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/sendfile.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
|
|
||||||
#include "../config/config.h"
|
|
||||||
#include "../logger/logs.h"
|
|
||||||
#include "../utils/files/files.h"
|
|
||||||
#include "../utils/parsing/words.h"
|
|
||||||
#include "../utils/time/fmt_time.h"
|
|
||||||
#include "headers.h"
|
|
||||||
|
|
||||||
// === Static variables
|
|
||||||
|
|
||||||
static struct config *config;
|
|
||||||
|
|
||||||
// === Static functions
|
|
||||||
|
|
||||||
// Parses the status line of req, stores the result in res and returns the
|
|
||||||
// number of read characters or a negative number on error
|
|
||||||
// WARNING res must be pre allocated
|
|
||||||
// (See the header for error codes)
|
|
||||||
static ssize_t parse_reqline(struct http_request *res, struct string *req)
|
|
||||||
{
|
|
||||||
ssize_t i = 0;
|
|
||||||
ssize_t skipped;
|
|
||||||
|
|
||||||
if (res == NULL || req == NULL)
|
|
||||||
return ERR_HTTP_INTERNAL_ERROR;
|
|
||||||
|
|
||||||
// Method
|
|
||||||
if (string_compare_n_str(req, "GET", strlen("GET")) == 0)
|
|
||||||
{
|
|
||||||
res->method = GET;
|
|
||||||
i += strlen("GET");
|
|
||||||
}
|
|
||||||
else if (string_compare_n_str(req, "HEAD", strlen("HEAD")) == 0)
|
|
||||||
{
|
|
||||||
res->method = HEAD;
|
|
||||||
i += strlen("HEAD");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return ERR_HTTP_NOT_IMPLEMENTED;
|
|
||||||
|
|
||||||
// Skip space
|
|
||||||
if (req->data[i++] != ' ')
|
|
||||||
return ERR_HTTP_INVALID_INPUT;
|
|
||||||
|
|
||||||
// Target (path)
|
|
||||||
skipped = read_word(req, i, &res->target);
|
|
||||||
if (skipped <= 0)
|
|
||||||
return ERR_HTTP_INVALID_INPUT;
|
|
||||||
i += skipped;
|
|
||||||
|
|
||||||
// Skip space
|
|
||||||
if (req->data[i++] != ' ')
|
|
||||||
return ERR_HTTP_INVALID_INPUT;
|
|
||||||
|
|
||||||
// Protocol
|
|
||||||
skipped = read_word(req, i, &res->protocol);
|
|
||||||
if (skipped <= 0)
|
|
||||||
return ERR_HTTP_INVALID_INPUT;
|
|
||||||
i += skipped;
|
|
||||||
|
|
||||||
// CRLF (EOL) oh qu'il est casse couilles celui-là
|
|
||||||
ssize_t req_size = req->size; // aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah
|
|
||||||
if (i < req_size && req->data[i] == '\r')
|
|
||||||
i++;
|
|
||||||
if (i >= req_size || req->data[i] != '\n')
|
|
||||||
return ERR_HTTP_INVALID_INPUT;
|
|
||||||
i++;
|
|
||||||
// Donc 2h de debug pour ça là ? Plutot envie de me tirer une balle si vous
|
|
||||||
// voulez mon avis
|
|
||||||
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split target into path and queries
|
|
||||||
static void split_target(struct http_request *req)
|
|
||||||
{
|
|
||||||
size_t i = 0;
|
|
||||||
while (i < req->target->size)
|
|
||||||
{
|
|
||||||
if (req->target->data[i] == '?')
|
|
||||||
{
|
|
||||||
req->queries =
|
|
||||||
string_create(req->target->data + i, req->target->size - i);
|
|
||||||
req->target->size = i;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finds a valid path based on the client input and returns the corresponding
|
|
||||||
// FILES return value (see files.h)
|
|
||||||
static int find_target(struct http_request *req)
|
|
||||||
{
|
|
||||||
// Check filename
|
|
||||||
if (!check_filename(req->target))
|
|
||||||
return ERR_FILES_FORBIDDEN;
|
|
||||||
|
|
||||||
char *target = string_to_charptr(req->target);
|
|
||||||
int err = is_directory(target);
|
|
||||||
free(target);
|
|
||||||
|
|
||||||
if (err == FILES_DIR) // Is a directory
|
|
||||||
{
|
|
||||||
// Append default file if directory
|
|
||||||
if (req->target->data[req->target->size - 1] != '/')
|
|
||||||
string_concat_str(req->target, "/", 1);
|
|
||||||
|
|
||||||
string_concat_str(req->target, config->servers->default_file,
|
|
||||||
strlen(config->servers->default_file));
|
|
||||||
// Recheck
|
|
||||||
target = string_to_charptr(req->target);
|
|
||||||
err = is_directory(target);
|
|
||||||
free(target);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// WARNING allocates result on the heap
|
|
||||||
static char *get_http_method(enum http_method method)
|
|
||||||
{
|
|
||||||
char *res = malloc(8 * sizeof(char));
|
|
||||||
switch (method)
|
|
||||||
{
|
|
||||||
case GET:
|
|
||||||
strcpy(res, "HEAD");
|
|
||||||
return res;
|
|
||||||
case HEAD:
|
|
||||||
strcpy(res, "HEAD");
|
|
||||||
return res;
|
|
||||||
default:
|
|
||||||
free(res);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct string *generate_status_message(int status_code)
|
|
||||||
{
|
|
||||||
char *message;
|
|
||||||
switch (status_code)
|
|
||||||
{
|
|
||||||
case 200:
|
|
||||||
message = "OK";
|
|
||||||
break;
|
|
||||||
case 400:
|
|
||||||
message = "Bad Request";
|
|
||||||
break;
|
|
||||||
case 403:
|
|
||||||
message = "Forbidden";
|
|
||||||
break;
|
|
||||||
case 404:
|
|
||||||
message = "Not Found";
|
|
||||||
break;
|
|
||||||
case 405:
|
|
||||||
message = "Method Not Allowed";
|
|
||||||
break;
|
|
||||||
case 505:
|
|
||||||
message = "HTTP Version Not Supported";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
message = "WTF";
|
|
||||||
}
|
|
||||||
|
|
||||||
return string_create(message, strlen(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void check_req(struct http_request *req, struct http_response *resp)
|
|
||||||
{
|
|
||||||
// Method
|
|
||||||
if (req->method == INVALID_METHOD)
|
|
||||||
resp->status_code = 405;
|
|
||||||
|
|
||||||
// Protocol
|
|
||||||
// Oui il y a plus beau, mais on a le temps ou on l'a pas, moi je l'ai pas,
|
|
||||||
// alors si t'es pas content t'as qu'à le modifier toi même vu que tu as
|
|
||||||
// visiblement le code source. <3
|
|
||||||
if (string_compare_strictly_n_str(req->protocol, "HTTP/", strlen("HTTP/"))
|
|
||||||
!= 0)
|
|
||||||
resp->status_code = 400;
|
|
||||||
else if (string_compare_strictly_n_str(req->protocol, "HTTP/1.1",
|
|
||||||
strlen("HTTP/1.1"))
|
|
||||||
!= 0)
|
|
||||||
resp->status_code = 505;
|
|
||||||
|
|
||||||
// Host
|
|
||||||
if (resp->status_code != 400 && resp->status_code != 505)
|
|
||||||
{
|
|
||||||
int host_count = 0;
|
|
||||||
struct http_header *cur = req->headers;
|
|
||||||
while (cur != NULL)
|
|
||||||
{
|
|
||||||
if (cur->field->size == 4
|
|
||||||
&& string_compare_n_str(cur->field, "host", 4) == 0)
|
|
||||||
{
|
|
||||||
host_count++;
|
|
||||||
if (cur->value == NULL || cur->value->size == 0)
|
|
||||||
{
|
|
||||||
resp->status_code = 400;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cur = cur->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (host_count != 1)
|
|
||||||
resp->status_code = 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
// printf("%s %d\n", req->protocol->data, resp->status_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Functions
|
|
||||||
|
|
||||||
void http_init(struct config *cfg)
|
|
||||||
{
|
|
||||||
config = cfg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO handle logs
|
|
||||||
void handle_request(int client_fd, char *client_ip)
|
|
||||||
{
|
|
||||||
char buffer[BUFFER_SIZE]; // Declared in server.h
|
|
||||||
struct string *str = string_create(NULL, 0);
|
|
||||||
if (str == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Store request
|
|
||||||
ssize_t nread = 0;
|
|
||||||
// This line here is so hard...
|
|
||||||
// When you realise that you have to remake all your project to be able to
|
|
||||||
// parse requests of BUFFER_SIZE size...
|
|
||||||
// Rendez les bijoux de la couronne (svp)
|
|
||||||
while ((nread = recv(client_fd, buffer, BUFFER_SIZE, 0)) == BUFFER_SIZE)
|
|
||||||
{
|
|
||||||
string_concat_str(str, buffer, nread);
|
|
||||||
}
|
|
||||||
if (nread > 0)
|
|
||||||
string_concat_str(str, buffer, nread);
|
|
||||||
|
|
||||||
// Parse request
|
|
||||||
struct http_request *req = parse_request(str);
|
|
||||||
if (req == NULL)
|
|
||||||
{
|
|
||||||
free(str);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
char *method = get_http_method(req->method);
|
|
||||||
char *target = string_to_charptr(req->target);
|
|
||||||
print_log_request(method, target, client_ip);
|
|
||||||
free(method);
|
|
||||||
free(target);
|
|
||||||
|
|
||||||
// Generate response
|
|
||||||
struct http_response *resp = generate_response(req);
|
|
||||||
if (resp == NULL)
|
|
||||||
{
|
|
||||||
free(str);
|
|
||||||
free(req);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format response to string
|
|
||||||
struct string *res = format_response(resp);
|
|
||||||
if (res == NULL)
|
|
||||||
{
|
|
||||||
free(str);
|
|
||||||
free(req);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send response
|
|
||||||
ssize_t nsent;
|
|
||||||
size_t total_sent = 0;
|
|
||||||
while (total_sent < res->size)
|
|
||||||
{
|
|
||||||
nsent =
|
|
||||||
send(client_fd, res->data + total_sent, res->size - total_sent, 0);
|
|
||||||
if (nsent <= 0)
|
|
||||||
break;
|
|
||||||
total_sent += nsent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send file
|
|
||||||
struct http_header *cl_header = get_header(resp->headers, "Content-Length");
|
|
||||||
if (cl_header != NULL)
|
|
||||||
{
|
|
||||||
char *target = string_to_charptr(req->target);
|
|
||||||
char *cl_str = string_to_charptr(cl_header->value);
|
|
||||||
int fd = open(target, O_RDONLY);
|
|
||||||
if (fd > 0)
|
|
||||||
sendfile(client_fd, fd, 0, atoi(cl_str));
|
|
||||||
|
|
||||||
free(target);
|
|
||||||
free(cl_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log response
|
|
||||||
method = get_http_method(req->method);
|
|
||||||
target = string_to_charptr(req->target);
|
|
||||||
print_log_response(resp->status_code, method, target, client_ip);
|
|
||||||
free(method);
|
|
||||||
free(target);
|
|
||||||
|
|
||||||
// Free
|
|
||||||
string_destroy(str);
|
|
||||||
string_destroy(res);
|
|
||||||
destroy_request(req);
|
|
||||||
destroy_response(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO handle logs and free adequately
|
|
||||||
struct http_request *parse_request(struct string *req)
|
|
||||||
{
|
|
||||||
struct http_request *res = calloc(1, sizeof(struct http_request));
|
|
||||||
if (res == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
// Status line
|
|
||||||
size_t i = 0;
|
|
||||||
ssize_t nread = parse_reqline(res, req);
|
|
||||||
if (nread <= 0)
|
|
||||||
{
|
|
||||||
if (nread == ERR_HTTP_NOT_IMPLEMENTED)
|
|
||||||
res->status_code = 501;
|
|
||||||
else
|
|
||||||
res->status_code = 400;
|
|
||||||
|
|
||||||
if (res->target == NULL)
|
|
||||||
res->target = string_create("", 0);
|
|
||||||
if (res->protocol == NULL)
|
|
||||||
res->protocol = string_create("HTTP/1.1", 8);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split path and query
|
|
||||||
split_target(res);
|
|
||||||
|
|
||||||
i += nread;
|
|
||||||
|
|
||||||
// Headers
|
|
||||||
nread = parse_headers(res, req, i);
|
|
||||||
if (nread <= 0)
|
|
||||||
{
|
|
||||||
res->status_code = 400;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO handle logs and free adequately
|
|
||||||
struct http_response *generate_response(struct http_request *req)
|
|
||||||
{
|
|
||||||
// Malloc
|
|
||||||
struct http_response *res = calloc(1, sizeof(struct http_response));
|
|
||||||
if (res == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
// Protocol
|
|
||||||
// char *protocol = HTTP_VERSION;
|
|
||||||
char *protocol = "HTTP/1.1";
|
|
||||||
res->protocol = string_create(protocol, strlen(protocol));
|
|
||||||
|
|
||||||
// Target
|
|
||||||
if (req->status_code == 0)
|
|
||||||
{
|
|
||||||
str_concat_string(config->servers->root_dir,
|
|
||||||
strlen(config->servers->root_dir), req->target);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status code
|
|
||||||
if (req->status_code == 0)
|
|
||||||
{
|
|
||||||
switch (find_target(req))
|
|
||||||
{
|
|
||||||
case FILES_REG:
|
|
||||||
res->status_code = 200;
|
|
||||||
break;
|
|
||||||
case ERR_FILES_NOT_FOUND:
|
|
||||||
res->status_code = 404;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
res->status_code = 403;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
res->status_code = req->status_code;
|
|
||||||
|
|
||||||
// Check protocol and method
|
|
||||||
if (req->status_code == 0)
|
|
||||||
check_req(req, res);
|
|
||||||
|
|
||||||
// Headers
|
|
||||||
char *time = get_time();
|
|
||||||
append_header(&res->headers, create_header("Date", time));
|
|
||||||
free(time); // Yes, the one that completely disapeared this year
|
|
||||||
if (res->status_code == 200)
|
|
||||||
{
|
|
||||||
char buf[21] = { 0 }; // (20 ~= log10(2^64)) + 1 (null byte)
|
|
||||||
char *target = string_to_charptr(req->target);
|
|
||||||
ssize_t cl = get_file_content_size(target);
|
|
||||||
free(target);
|
|
||||||
if (cl >= 0)
|
|
||||||
{
|
|
||||||
sprintf(buf, "%lu", cl);
|
|
||||||
append_header(&res->headers, create_header("Content-Length", buf));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
res->status_code = 403;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
append_header(&res->headers, create_header("Content-Length", "0"));
|
|
||||||
}
|
|
||||||
append_header(&res->headers, create_header("Connection", "close"));
|
|
||||||
|
|
||||||
// Status msg
|
|
||||||
res->status_msg = generate_status_message(res->status_code);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct string *format_response(struct http_response *resp)
|
|
||||||
{
|
|
||||||
// Protocol
|
|
||||||
struct string *res =
|
|
||||||
string_create(resp->protocol->data, resp->protocol->size);
|
|
||||||
|
|
||||||
string_concat_str(res, " ", 1);
|
|
||||||
|
|
||||||
// Status code
|
|
||||||
char buf[4] = { 0 };
|
|
||||||
sprintf(buf, "%d", resp->status_code);
|
|
||||||
string_concat_str(res, buf, strlen(buf));
|
|
||||||
|
|
||||||
string_concat_str(res, " ", 1);
|
|
||||||
|
|
||||||
// Status message
|
|
||||||
char *status_msg = string_to_charptr(resp->status_msg);
|
|
||||||
string_concat_str(res, status_msg, strlen(status_msg));
|
|
||||||
free(status_msg);
|
|
||||||
|
|
||||||
string_concat_str(res, "\r\n", 2);
|
|
||||||
|
|
||||||
// Headers
|
|
||||||
struct http_header *cur_header = resp->headers;
|
|
||||||
while (cur_header != NULL)
|
|
||||||
{
|
|
||||||
string_concat_str(res, cur_header->field->data,
|
|
||||||
cur_header->field->size);
|
|
||||||
|
|
||||||
string_concat_str(res, ": ", 2);
|
|
||||||
|
|
||||||
string_concat_str(res, cur_header->value->data,
|
|
||||||
cur_header->value->size);
|
|
||||||
|
|
||||||
string_concat_str(res, "\r\n", 2);
|
|
||||||
|
|
||||||
cur_header = cur_header->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
string_concat_str(res, "\r\n", 2);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
void destroy_request(struct http_request *req)
|
|
||||||
{
|
|
||||||
if (req != NULL)
|
|
||||||
{
|
|
||||||
if (req->target != NULL)
|
|
||||||
string_destroy(req->target);
|
|
||||||
if (req->protocol != NULL)
|
|
||||||
string_destroy(req->protocol);
|
|
||||||
|
|
||||||
destroy_headers(req->headers);
|
|
||||||
|
|
||||||
free(req);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void destroy_response(struct http_response *resp)
|
|
||||||
{
|
|
||||||
if (resp != NULL)
|
|
||||||
{
|
|
||||||
if (resp->status_msg != NULL)
|
|
||||||
string_destroy(resp->status_msg);
|
|
||||||
if (resp->protocol != NULL)
|
|
||||||
string_destroy(resp->protocol);
|
|
||||||
|
|
||||||
destroy_headers(resp->headers);
|
|
||||||
|
|
||||||
free(resp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
118
src/http/http.h
118
src/http/http.h
|
|
@ -1,118 +0,0 @@
|
||||||
#ifndef HTTP_H
|
|
||||||
#define HTTP_H
|
|
||||||
|
|
||||||
// === Definitions
|
|
||||||
|
|
||||||
// #define _POSIX_C_SOURCE 200809L
|
|
||||||
|
|
||||||
// Error codes
|
|
||||||
#define ERR_HTTP_INVALID_INPUT -1
|
|
||||||
#define ERR_HTTP_NOT_IMPLEMENTED -2
|
|
||||||
#define ERR_HTTP_OUT_OF_MEMORY -4
|
|
||||||
#define ERR_HTTP_INTERNAL_ERROR -5
|
|
||||||
|
|
||||||
// === Includes
|
|
||||||
|
|
||||||
#include "../config/config.h"
|
|
||||||
#include "../utils/string/string.h"
|
|
||||||
|
|
||||||
// === Enums
|
|
||||||
|
|
||||||
enum http_method
|
|
||||||
{
|
|
||||||
INVALID_METHOD,
|
|
||||||
GET,
|
|
||||||
// POST,
|
|
||||||
// PUT,
|
|
||||||
// DELETE,
|
|
||||||
// PATCH,
|
|
||||||
HEAD,
|
|
||||||
// OPTIONS,
|
|
||||||
// CONNECT,
|
|
||||||
// TRACE
|
|
||||||
};
|
|
||||||
|
|
||||||
// === Structures
|
|
||||||
|
|
||||||
struct http_header
|
|
||||||
{
|
|
||||||
struct string *field;
|
|
||||||
struct string *value;
|
|
||||||
struct http_header *next;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct http_request
|
|
||||||
{
|
|
||||||
enum http_method method;
|
|
||||||
struct string *target;
|
|
||||||
struct string *queries;
|
|
||||||
struct string *protocol;
|
|
||||||
struct http_header *headers; // Headers linked list
|
|
||||||
int status_code; // Set by program
|
|
||||||
};
|
|
||||||
|
|
||||||
struct http_response
|
|
||||||
{
|
|
||||||
struct string *protocol;
|
|
||||||
int status_code;
|
|
||||||
struct string *status_msg;
|
|
||||||
struct http_header *headers; // Headers linked list
|
|
||||||
};
|
|
||||||
|
|
||||||
// === Functions
|
|
||||||
|
|
||||||
/* @brief Initializes the HTTP module with the given configuration
|
|
||||||
*
|
|
||||||
* @warn Do no use any other function of this module before calling that one
|
|
||||||
*
|
|
||||||
* @param cfg
|
|
||||||
*/
|
|
||||||
void http_init(struct config *cfg);
|
|
||||||
|
|
||||||
/* @brief Reads, parses the request and responds adequately all-in-one
|
|
||||||
*
|
|
||||||
* @param client_fd
|
|
||||||
*/
|
|
||||||
void handle_request(int client_fd, char *client_ip);
|
|
||||||
|
|
||||||
/* @brief Parses the HTTP request and splits it into a request structure
|
|
||||||
*
|
|
||||||
* @param req
|
|
||||||
*
|
|
||||||
* @return A pointer to the structure containing the request infos on success,
|
|
||||||
* NULL otherwise
|
|
||||||
*/
|
|
||||||
struct http_request *parse_request(struct string *req);
|
|
||||||
|
|
||||||
/* @brief Generates a response to the given request
|
|
||||||
*
|
|
||||||
* @param req
|
|
||||||
*
|
|
||||||
* @return A pointer to the generated response struct on success,
|
|
||||||
* NULL otherwise
|
|
||||||
*/
|
|
||||||
struct http_response *generate_response(struct http_request *req);
|
|
||||||
|
|
||||||
/* @brief Formats the given response structure into a valid HTTP response
|
|
||||||
* string
|
|
||||||
*
|
|
||||||
* @param resp
|
|
||||||
*
|
|
||||||
* @return A pointer to the string containing the response on success,
|
|
||||||
* NULL otherwise
|
|
||||||
*/
|
|
||||||
struct string *format_response(struct http_response *resp);
|
|
||||||
|
|
||||||
/* @brief Free all allocated memory inside req and req itself
|
|
||||||
*
|
|
||||||
* @param req
|
|
||||||
*/
|
|
||||||
void destroy_request(struct http_request *req);
|
|
||||||
|
|
||||||
/* @brief Free all allocated memory inside resp and resp itself
|
|
||||||
*
|
|
||||||
* @param resp
|
|
||||||
*/
|
|
||||||
void destroy_response(struct http_response *resp);
|
|
||||||
|
|
||||||
#endif // ! HTTP_H
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
#define _POSIX_C_SOURCE 200809L
|
|
||||||
|
|
||||||
#include "errors.h"
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "../utils/time/fmt_time.h"
|
|
||||||
#include "logs.h"
|
|
||||||
|
|
||||||
// === Static variable
|
|
||||||
|
|
||||||
static struct logs_config config;
|
|
||||||
|
|
||||||
// === Functions
|
|
||||||
|
|
||||||
void errlog_init(bool enabled, int logfile_fd, struct server_config *serv_cfg)
|
|
||||||
{
|
|
||||||
config.enabled = enabled;
|
|
||||||
if (logfile_fd == STDOUT_FILENO)
|
|
||||||
config.logfile_fd = STDERR_FILENO;
|
|
||||||
config.logfile_fd = logfile_fd;
|
|
||||||
config.server_cfg = serv_cfg;
|
|
||||||
}
|
|
||||||
|
|
||||||
void print_err(void)
|
|
||||||
{
|
|
||||||
print_log_err("%s", get_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
void print_log_err(char *format, ...)
|
|
||||||
{
|
|
||||||
if (!config.enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Log prefix (time and server name)
|
|
||||||
char *time = get_time();
|
|
||||||
dprintf(config.logfile_fd, "%s [%s] ERROR ", time,
|
|
||||||
config.server_cfg->server_name);
|
|
||||||
free(time);
|
|
||||||
|
|
||||||
// Print actual log
|
|
||||||
va_list args;
|
|
||||||
va_start(args, format);
|
|
||||||
vdprintf(config.logfile_fd, format, args);
|
|
||||||
va_end(args);
|
|
||||||
|
|
||||||
// New line
|
|
||||||
dprintf(config.logfile_fd, "\n");
|
|
||||||
|
|
||||||
// Print to stderr
|
|
||||||
fprintf(stderr, "Error: %s", get_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
char *get_err(void)
|
|
||||||
{
|
|
||||||
return strerror(errno);
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
#ifndef ERRORS_H
|
|
||||||
#define ERRORS_H
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#include "../config/config.h"
|
|
||||||
|
|
||||||
/* @brief Initialize the error logging submodule
|
|
||||||
* @warning Do not use 'as is', use log_init() instead
|
|
||||||
*/
|
|
||||||
void errlog_init(bool enabled, int logfile_fd, struct server_config *serv_cfg);
|
|
||||||
|
|
||||||
/* @brief Retrieves the last error with errno and prints the corresponding
|
|
||||||
* error message in the logs and stderr
|
|
||||||
*/
|
|
||||||
void print_err(void);
|
|
||||||
|
|
||||||
/* @brief Prints error logs, just like print_log() but for errors
|
|
||||||
*/
|
|
||||||
void print_log_err(char *format, ...);
|
|
||||||
|
|
||||||
/* @brief Returns the string corresponding to the last error that happened
|
|
||||||
*/
|
|
||||||
char *get_err(void);
|
|
||||||
|
|
||||||
#endif // ! ERRORS_H
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
#define _POSIX_C_SOURCE 200809L
|
|
||||||
|
|
||||||
#include "logs.h"
|
|
||||||
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "../utils/time/fmt_time.h"
|
|
||||||
#include "errors.h"
|
|
||||||
|
|
||||||
// === Static variables
|
|
||||||
|
|
||||||
static struct logs_config config;
|
|
||||||
|
|
||||||
// === Functions
|
|
||||||
|
|
||||||
int log_init(struct config *global_config)
|
|
||||||
{
|
|
||||||
int return_value = 0;
|
|
||||||
config.enabled = global_config->log;
|
|
||||||
config.server_cfg = global_config->servers;
|
|
||||||
if (global_config->log_file != NULL)
|
|
||||||
{
|
|
||||||
config.logfile_fd = open(global_config->log_file, O_WRONLY);
|
|
||||||
if (config.logfile_fd <= 0)
|
|
||||||
{
|
|
||||||
config.enabled = false;
|
|
||||||
return_value = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
config.logfile_fd = STDOUT_FILENO;
|
|
||||||
}
|
|
||||||
|
|
||||||
errlog_init(config.enabled, config.logfile_fd, config.server_cfg);
|
|
||||||
return return_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void print_log(char *format, ...)
|
|
||||||
{
|
|
||||||
if (!config.enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Log prefix (time and server name)
|
|
||||||
char *time = get_time();
|
|
||||||
dprintf(config.logfile_fd, "%s [%s] ", time,
|
|
||||||
config.server_cfg->server_name);
|
|
||||||
free(time);
|
|
||||||
|
|
||||||
// Print actual log
|
|
||||||
va_list args;
|
|
||||||
va_start(args, format);
|
|
||||||
vdprintf(config.logfile_fd, format, args);
|
|
||||||
va_end(args);
|
|
||||||
|
|
||||||
// New line
|
|
||||||
dprintf(config.logfile_fd, "\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void print_log_request(char *request_type, char *target, char *client_ip)
|
|
||||||
{
|
|
||||||
print_log("received %s, on '%s' from %s", request_type, target, client_ip);
|
|
||||||
}
|
|
||||||
|
|
||||||
void print_log_response(int status_code, char *request_type, char *target,
|
|
||||||
char *client_ip)
|
|
||||||
{
|
|
||||||
print_log("responding with %d to %s for %s on '%s'", status_code, client_ip,
|
|
||||||
request_type, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
void log_terminate(void)
|
|
||||||
{
|
|
||||||
if (config.logfile_fd != 0 && config.logfile_fd != STDOUT_FILENO)
|
|
||||||
close(config.logfile_fd);
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
#ifndef LOGS_H
|
|
||||||
#define LOGS_H
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#include "../config/config.h"
|
|
||||||
|
|
||||||
struct logs_config
|
|
||||||
{
|
|
||||||
bool enabled;
|
|
||||||
int logfile_fd;
|
|
||||||
struct server_config *server_cfg;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* @brief Initializes the logging module
|
|
||||||
*
|
|
||||||
* @param config
|
|
||||||
*
|
|
||||||
* @return 0 on success, an error code otherwise
|
|
||||||
*/
|
|
||||||
int log_init(struct config *config);
|
|
||||||
|
|
||||||
/* @brief Prints logs (or not) conformly to the config given by the user.
|
|
||||||
* Works like printf (because it uses it under the hood) with a
|
|
||||||
* formatted string and variadic arguments.
|
|
||||||
*
|
|
||||||
* @param format
|
|
||||||
* @param ...
|
|
||||||
*/
|
|
||||||
void print_log(char *format, ...);
|
|
||||||
|
|
||||||
/* @brief Prints request logs with the adequate format in the logfile
|
|
||||||
*
|
|
||||||
* @param request_type
|
|
||||||
* @param target
|
|
||||||
* @param client_ip
|
|
||||||
*/
|
|
||||||
void print_log_request(char *request_type, char *target, char *client_ip);
|
|
||||||
|
|
||||||
/* @brief Prints response logs with the adequate format in the logfile
|
|
||||||
*
|
|
||||||
* @param status_code
|
|
||||||
* @param request_type
|
|
||||||
* @param target
|
|
||||||
* @param client_ip
|
|
||||||
*/
|
|
||||||
void print_log_response(int status_code, char *request_type, char *target,
|
|
||||||
char *client_ip);
|
|
||||||
|
|
||||||
/* @brief Gracefully exits the logs module
|
|
||||||
*/
|
|
||||||
void log_terminate(void);
|
|
||||||
|
|
||||||
#endif // ! LOGS_H
|
|
||||||
48
src/main.c
48
src/main.c
|
|
@ -1,48 +0,0 @@
|
||||||
#include <getopt.h>
|
|
||||||
|
|
||||||
#include "config/config.h"
|
|
||||||
#include "daemon/daemon.h"
|
|
||||||
#include "http/http.h"
|
|
||||||
#include "logger/logs.h"
|
|
||||||
#include "server/server.h"
|
|
||||||
|
|
||||||
#define ERR_ARG 2
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
// Parse config
|
|
||||||
struct config *config = parse_configuration(argc, argv);
|
|
||||||
if (config == NULL)
|
|
||||||
return ERR_ARG;
|
|
||||||
|
|
||||||
// Initialize modules
|
|
||||||
log_init(config); // Ignore ret val
|
|
||||||
http_init(config);
|
|
||||||
daemon_init(config);
|
|
||||||
|
|
||||||
// Start server
|
|
||||||
switch (config->daemon)
|
|
||||||
{
|
|
||||||
case NO_OPTION:
|
|
||||||
start_server(config);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case START:
|
|
||||||
start_daemon();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RESTART:
|
|
||||||
restart_daemon();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case STOP:
|
|
||||||
stop_daemon();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
config_destroy(config);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,184 +0,0 @@
|
||||||
// === Definitions
|
|
||||||
|
|
||||||
#define _POSIX_C_SOURCE 200112L
|
|
||||||
|
|
||||||
#define BUFFER_SIZE 1024
|
|
||||||
|
|
||||||
// === Includes
|
|
||||||
#include "server.h"
|
|
||||||
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <netdb.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "../http/http.h"
|
|
||||||
#include "../logger/errors.h"
|
|
||||||
#include "../logger/logs.h"
|
|
||||||
// #include "../logger/logs.h"
|
|
||||||
|
|
||||||
// === Static variables
|
|
||||||
|
|
||||||
static int server_socket = 0;
|
|
||||||
static struct config *config;
|
|
||||||
|
|
||||||
// === Static functions
|
|
||||||
|
|
||||||
// Creates and bind the server communication socket
|
|
||||||
static int get_socket(const char *hostname, const char *port)
|
|
||||||
{
|
|
||||||
// set lower-level protocols infos
|
|
||||||
struct addrinfo hints = { 0 };
|
|
||||||
hints.ai_family = AF_INET; // IPv4
|
|
||||||
hints.ai_socktype = SOCK_STREAM; // Socket type (necessary for TCP)
|
|
||||||
hints.ai_protocol = IPPROTO_TCP; // TCP
|
|
||||||
|
|
||||||
// Retrieve node informations
|
|
||||||
struct addrinfo *client_addr = NULL; // Client addresses list
|
|
||||||
int err = getaddrinfo(hostname, port, &hints, &client_addr);
|
|
||||||
if (err != 0)
|
|
||||||
{
|
|
||||||
print_err();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try each address
|
|
||||||
int sock_fd;
|
|
||||||
bool socket_bound = false;
|
|
||||||
struct addrinfo *cur_addr = client_addr;
|
|
||||||
while (cur_addr != NULL && !socket_bound)
|
|
||||||
{
|
|
||||||
// Create socket
|
|
||||||
sock_fd = socket(cur_addr->ai_family, cur_addr->ai_socktype,
|
|
||||||
cur_addr->ai_protocol);
|
|
||||||
if (sock_fd == -1)
|
|
||||||
{
|
|
||||||
cur_addr = cur_addr->ai_next;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... Has something to do with not changing port
|
|
||||||
int sock_opt = -1;
|
|
||||||
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &sock_opt,
|
|
||||||
sizeof(sock_opt));
|
|
||||||
|
|
||||||
// Assign name to socket (bind)
|
|
||||||
err = bind(sock_fd, cur_addr->ai_addr, cur_addr->ai_addrlen);
|
|
||||||
if (err != -1)
|
|
||||||
socket_bound = true;
|
|
||||||
else
|
|
||||||
close(sock_fd);
|
|
||||||
|
|
||||||
cur_addr = cur_addr->ai_next;
|
|
||||||
}
|
|
||||||
|
|
||||||
freeaddrinfo(client_addr);
|
|
||||||
return sock_fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reads and sends back received data to client
|
|
||||||
// For testing purposes
|
|
||||||
// static void send_back(int client_fd)
|
|
||||||
// {
|
|
||||||
// ssize_t nread = 0;
|
|
||||||
// char buffer[BUFFER_SIZE];
|
|
||||||
|
|
||||||
// while ((nread = recv(client_fd, buffer, BUFFER_SIZE, 0)) > 0)
|
|
||||||
// {
|
|
||||||
// ssize_t sent;
|
|
||||||
// while (nread > 0)
|
|
||||||
// {
|
|
||||||
// sent = send(client_fd, buffer, nread, 0);
|
|
||||||
// if (sent == -1) // Send failed
|
|
||||||
// break;
|
|
||||||
|
|
||||||
// nread -= sent;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Retrieves client ipv4 address and stores it in res
|
|
||||||
// WARNING: res must be of size INET_ADDRSTRLEN
|
|
||||||
static void get_ip(struct sockaddr *client_addr, char *res)
|
|
||||||
{
|
|
||||||
// NOTE: exceptionally authorized cast
|
|
||||||
struct in_addr ipAddr = ((struct sockaddr_in *)client_addr)->sin_addr;
|
|
||||||
inet_ntop(AF_INET, &ipAddr, res, INET_ADDRSTRLEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void signal_handler(int signal)
|
|
||||||
{
|
|
||||||
print_log("EVENT Signal received: %d", signal);
|
|
||||||
switch (signal)
|
|
||||||
{
|
|
||||||
case SIGINT: {
|
|
||||||
print_log("STOPPING Stopping server...");
|
|
||||||
stop_server();
|
|
||||||
config_destroy(config);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Functions
|
|
||||||
|
|
||||||
void start_server(struct config *cfg)
|
|
||||||
{
|
|
||||||
config = cfg;
|
|
||||||
const char *host = config->servers->ip;
|
|
||||||
const char *port = config->servers->port;
|
|
||||||
|
|
||||||
server_socket = get_socket(host, port);
|
|
||||||
if (server_socket == -1)
|
|
||||||
// TODO log that
|
|
||||||
return;
|
|
||||||
|
|
||||||
int err = listen(server_socket, SOMAXCONN);
|
|
||||||
if (err == -1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Signal handling
|
|
||||||
struct sigaction siga;
|
|
||||||
siga.sa_flags = 0;
|
|
||||||
siga.sa_handler = signal_handler;
|
|
||||||
// initialize mask
|
|
||||||
if (sigemptyset(&siga.sa_mask) < 0)
|
|
||||||
// TODO log that
|
|
||||||
return;
|
|
||||||
if (sigaction(SIGINT, &siga, NULL) == -1
|
|
||||||
|| sigaction(SIGPIPE, &siga, NULL) == -1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Main loop
|
|
||||||
while (1)
|
|
||||||
{
|
|
||||||
struct sockaddr client_addr;
|
|
||||||
socklen_t client_addr_len = sizeof(struct sockaddr);
|
|
||||||
int client_fd = accept(server_socket, &client_addr, &client_addr_len);
|
|
||||||
if (client_fd == -1)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// TODO handle signals to stop
|
|
||||||
|
|
||||||
// Get ip
|
|
||||||
char client_ip[INET_ADDRSTRLEN];
|
|
||||||
get_ip(&client_addr, client_ip);
|
|
||||||
|
|
||||||
handle_request(client_fd, client_ip);
|
|
||||||
// send_back(client_fd);
|
|
||||||
close(client_fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
stop_server();
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop_server(void)
|
|
||||||
{
|
|
||||||
close(server_socket);
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
#ifndef SERVER_H
|
|
||||||
#define SERVER_H
|
|
||||||
|
|
||||||
#include "../config/config.h"
|
|
||||||
|
|
||||||
/* @brief Starts the HTTP server
|
|
||||||
*
|
|
||||||
* @warn Make sure to initialize modules before calling
|
|
||||||
*
|
|
||||||
* @param hostname
|
|
||||||
* @param port
|
|
||||||
*/
|
|
||||||
void start_server(struct config *cfg);
|
|
||||||
|
|
||||||
/* @brief Stops the currently running HTTP server
|
|
||||||
*/
|
|
||||||
void stop_server(void);
|
|
||||||
|
|
||||||
#endif // ! SERVER_H
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
#define _POSIX_C_SOURCE 200112L
|
|
||||||
|
|
||||||
#include "files.h"
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
// #include "../string/string.h"
|
|
||||||
|
|
||||||
// int file_exists(const char *path)
|
|
||||||
// {}
|
|
||||||
|
|
||||||
int is_directory(const char *path)
|
|
||||||
{
|
|
||||||
struct stat path_stat;
|
|
||||||
if (lstat(path, &path_stat) == 0)
|
|
||||||
{
|
|
||||||
if (S_ISDIR(path_stat.st_mode)) // Directory
|
|
||||||
return FILES_DIR;
|
|
||||||
else if (S_ISREG(path_stat.st_mode)) // Regular file
|
|
||||||
return FILES_REG;
|
|
||||||
else
|
|
||||||
return FILES_OTHER;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (errno == ENOENT) // File not found
|
|
||||||
return ERR_FILES_NOT_FOUND;
|
|
||||||
else // Other errors
|
|
||||||
return ERR_FILES_FORBIDDEN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO handle logging
|
|
||||||
// struct string *get_file_content(const char *path)
|
|
||||||
// {
|
|
||||||
// // Open file
|
|
||||||
// FILE *stream = fopen(path, "r");
|
|
||||||
// if (stream == NULL)
|
|
||||||
// return NULL;
|
|
||||||
|
|
||||||
// // Alloc result
|
|
||||||
// char buf[BUFFER_SIZE];
|
|
||||||
// struct string *res = string_create(NULL, 0);
|
|
||||||
// if (res == NULL)
|
|
||||||
// {
|
|
||||||
// return NULL;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// int nread;
|
|
||||||
// while ((fgets(buf, BUFFER_SIZE, stream)))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TODO not implemented
|
|
||||||
bool check_filename(struct string *path)
|
|
||||||
{
|
|
||||||
if (path == NULL || path->size <= 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t get_file_content_size(const char *path)
|
|
||||||
{
|
|
||||||
FILE *stream = fopen(path, "r");
|
|
||||||
if (stream == NULL)
|
|
||||||
return -2;
|
|
||||||
|
|
||||||
fseek(stream, 0, SEEK_END);
|
|
||||||
ssize_t res = ftell(stream);
|
|
||||||
fclose(stream);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
int write_to_file(const char *path, struct string *buf)
|
|
||||||
{
|
|
||||||
FILE *stream = fopen(path, "w");
|
|
||||||
if (stream == NULL)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
fwrite(buf->data, sizeof(char), buf->size, stream);
|
|
||||||
|
|
||||||
fclose(stream);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int write_pid(const char *filepath, int pid)
|
|
||||||
{
|
|
||||||
FILE *stream = fopen(filepath, "w");
|
|
||||||
if (stream == NULL)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (fprintf(stream, "%d", pid) <= 0)
|
|
||||||
{
|
|
||||||
fclose(stream);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(stream);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
#ifndef FILES_H
|
|
||||||
#define FILES_H
|
|
||||||
|
|
||||||
// === Definitions
|
|
||||||
|
|
||||||
#define BUFFER_SIZE 1024
|
|
||||||
|
|
||||||
// Return codes
|
|
||||||
#define FILES_REG 0
|
|
||||||
#define FILES_DIR 1
|
|
||||||
#define FILES_OTHER 8
|
|
||||||
|
|
||||||
// Errors
|
|
||||||
#define ERR_FILES_NOT_FOUND -1
|
|
||||||
#define ERR_FILES_FORBIDDEN -2
|
|
||||||
|
|
||||||
// === Includes
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include "../string/string.h"
|
|
||||||
|
|
||||||
// === Functions
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief
|
|
||||||
*
|
|
||||||
* @param path
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
// bool file_exists(const char *path);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief
|
|
||||||
*
|
|
||||||
* @param path
|
|
||||||
*
|
|
||||||
* @return Returns the corresponding return value / error code (see header
|
|
||||||
* definitions)
|
|
||||||
*/
|
|
||||||
int is_directory(const char *path);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief
|
|
||||||
*
|
|
||||||
* @param path
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
// char *get_file(const char *path);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief
|
|
||||||
*
|
|
||||||
* @param path
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
struct string *get_file_content(const char *path);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief
|
|
||||||
*
|
|
||||||
* @param path
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
bool check_filename(struct string *path);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief
|
|
||||||
*
|
|
||||||
* @param filename
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
// bool sanitize_filename(struct string *filename);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief
|
|
||||||
*
|
|
||||||
* @param path
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
ssize_t get_file_content_size(const char *path);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief
|
|
||||||
*
|
|
||||||
* @param path
|
|
||||||
*
|
|
||||||
* @return 0 on success, the corresponding error code otherwise
|
|
||||||
*/
|
|
||||||
int write_to_file(const char *path, struct string *buf);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief
|
|
||||||
*
|
|
||||||
* @param path
|
|
||||||
*
|
|
||||||
* @return 0 on success, the corresponding error code otherwise
|
|
||||||
*/
|
|
||||||
int write_pid(const char *filepath, int pid);
|
|
||||||
|
|
||||||
#endif // ! FILES_H
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
#include "blanks.h"
|
|
||||||
|
|
||||||
bool is_space(char c)
|
|
||||||
{
|
|
||||||
switch (c)
|
|
||||||
{
|
|
||||||
case ' ':
|
|
||||||
case '\t':
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_blank(char c)
|
|
||||||
{
|
|
||||||
switch (c)
|
|
||||||
{
|
|
||||||
case '\f':
|
|
||||||
case '\n':
|
|
||||||
case '\r':
|
|
||||||
case '\t':
|
|
||||||
case '\v':
|
|
||||||
case ' ':
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t skip_blanks(struct string *str, size_t offset)
|
|
||||||
{
|
|
||||||
size_t i = offset;
|
|
||||||
while (i < str->size)
|
|
||||||
{
|
|
||||||
if (!is_space(str->data[i]))
|
|
||||||
return i;
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reached EOL
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
#ifndef BLANKS_H
|
|
||||||
#define BLANKS_H
|
|
||||||
|
|
||||||
// === Definitions
|
|
||||||
|
|
||||||
#define BLANKS_LIST "\f\n\r\t\v "
|
|
||||||
|
|
||||||
// === Includes
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include "../string/string.h"
|
|
||||||
|
|
||||||
// === Functions
|
|
||||||
/*
|
|
||||||
* NOTE:
|
|
||||||
* As ispace(3) and isblank(3) functions from stdlib can be confusing and not
|
|
||||||
* always best suited for what I want to do, I decided to reimplement them.
|
|
||||||
* So be warned, they won't have the same behavior as their libc counterparts.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Doc: TODO
|
|
||||||
*/
|
|
||||||
bool is_space(char c);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Doc: TODO
|
|
||||||
*/
|
|
||||||
bool is_blank(char c);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Doc: TODO
|
|
||||||
*/
|
|
||||||
ssize_t skip_blanks(struct string *str, size_t offset);
|
|
||||||
|
|
||||||
#endif // ! BLANKS_H
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
#include "words.h"
|
|
||||||
|
|
||||||
#include "blanks.h"
|
|
||||||
|
|
||||||
bool str_contains(const char *str, char c)
|
|
||||||
{
|
|
||||||
int i = 0;
|
|
||||||
while (str[i] != '\0')
|
|
||||||
{
|
|
||||||
if (str[i] == c)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t read_word(struct string *str, size_t offset, struct string **res)
|
|
||||||
{
|
|
||||||
size_t i = offset;
|
|
||||||
while (i < str->size)
|
|
||||||
{
|
|
||||||
if (is_blank(str->data[i]))
|
|
||||||
break;
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
*res = string_create(str->data + offset, i - offset);
|
|
||||||
if (res == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return i - offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t read_word_delim(struct string *str, size_t offset, struct string **res,
|
|
||||||
const char *delims)
|
|
||||||
{
|
|
||||||
size_t i = offset;
|
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
while (i < str->size && !found)
|
|
||||||
{
|
|
||||||
if (str_contains(delims, str->data[i]))
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
*res = string_create(str->data + offset, i - offset);
|
|
||||||
if (res == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return i - offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t read_word_restrict(struct string *str, size_t offset,
|
|
||||||
struct string **res, const char *restr)
|
|
||||||
{
|
|
||||||
size_t i = offset;
|
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
while (i < str->size && !found)
|
|
||||||
{
|
|
||||||
if (!str_contains(restr, str->data[i]))
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
*res = string_create(str->data + offset, i - offset);
|
|
||||||
if (res == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return i - offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t read_word_predicate(struct string *str, size_t offset,
|
|
||||||
struct string **res, bool (*predicate)(char c))
|
|
||||||
{
|
|
||||||
size_t i = offset;
|
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
while (i < str->size && !found)
|
|
||||||
{
|
|
||||||
if (predicate(str->data[i]))
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
*res = string_create(str->data + offset, i - offset);
|
|
||||||
if (res == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return i - offset;
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
#ifndef WORDS_H
|
|
||||||
#define WORDS_H
|
|
||||||
|
|
||||||
// === Includes
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include "../string/string.h"
|
|
||||||
|
|
||||||
// === Functions
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Doc: TODO
|
|
||||||
*/
|
|
||||||
bool str_contains(const char *str, char c);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Doc: TODO
|
|
||||||
*/
|
|
||||||
ssize_t read_word(struct string *str, size_t offset, struct string **res);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Doc: TODO
|
|
||||||
*/
|
|
||||||
ssize_t read_word_delim(struct string *str, size_t offset, struct string **res,
|
|
||||||
const char *delims);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Doc: TODO
|
|
||||||
*/
|
|
||||||
ssize_t read_word_restrict(struct string *str, size_t offset,
|
|
||||||
struct string **res, const char *restr);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Doc: TODO
|
|
||||||
*/
|
|
||||||
ssize_t read_word_predicate(struct string *str, size_t offset,
|
|
||||||
struct string **res, bool (*predicate)(char c));
|
|
||||||
|
|
||||||
#endif // ! WORDS_H
|
|
||||||
|
|
@ -1,175 +0,0 @@
|
||||||
#include "string.h"
|
|
||||||
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
struct string *string_create(const char *str, size_t size)
|
|
||||||
{
|
|
||||||
struct string *res = calloc(1, sizeof(struct string));
|
|
||||||
if (res == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (size > 0)
|
|
||||||
{
|
|
||||||
res->data = malloc(size * sizeof(char));
|
|
||||||
if (res->data == NULL)
|
|
||||||
{
|
|
||||||
free(res);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(res->data, str, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
res->size = size;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
int string_compare_n_str(const struct string *str1, const char *str2, size_t n)
|
|
||||||
{
|
|
||||||
size_t i = 0;
|
|
||||||
int res = 0;
|
|
||||||
char reached_str2_end = 0;
|
|
||||||
while (i < n)
|
|
||||||
{
|
|
||||||
if (i < str1->size)
|
|
||||||
res += str1->data[i];
|
|
||||||
|
|
||||||
if (!reached_str2_end)
|
|
||||||
{
|
|
||||||
if (str2[i] == '\0')
|
|
||||||
reached_str2_end = 1;
|
|
||||||
else
|
|
||||||
res -= str2[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
void string_concat_str(struct string *str, const char *to_concat, size_t size)
|
|
||||||
{
|
|
||||||
size_t new_size = str->size + size;
|
|
||||||
size_t str_size = str->size;
|
|
||||||
|
|
||||||
if (new_size == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
str->size = new_size;
|
|
||||||
if (str_size == 0)
|
|
||||||
{
|
|
||||||
str->data = malloc(new_size);
|
|
||||||
if (str->data == NULL)
|
|
||||||
return; // Handle ?
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
str->data = realloc(str->data, new_size * sizeof(char));
|
|
||||||
if (str->data == NULL)
|
|
||||||
return; // Handle ?
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < size; i++)
|
|
||||||
{
|
|
||||||
str->data[str_size + i] = to_concat[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void str_concat_string(const char *str, size_t size, struct string *to_concat)
|
|
||||||
{
|
|
||||||
size_t new_size = to_concat->size + size;
|
|
||||||
size_t tmp_size = to_concat->size;
|
|
||||||
|
|
||||||
if (new_size == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
to_concat->size = new_size;
|
|
||||||
if (tmp_size == 0)
|
|
||||||
{
|
|
||||||
to_concat->data = malloc(new_size);
|
|
||||||
if (to_concat->data == NULL)
|
|
||||||
return; // Handle ?
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Temporary buffer
|
|
||||||
char *tmp = malloc(tmp_size * sizeof(char));
|
|
||||||
if (tmp == NULL)
|
|
||||||
return; // Handle ?
|
|
||||||
|
|
||||||
// (Duplicate)
|
|
||||||
memcpy(tmp, to_concat->data, tmp_size);
|
|
||||||
|
|
||||||
// Reallocate string
|
|
||||||
char *new_data = realloc(to_concat->data, new_size * sizeof(char));
|
|
||||||
if (to_concat->data == NULL)
|
|
||||||
{
|
|
||||||
to_concat->size = tmp_size; // Restore (original ptr still valid)
|
|
||||||
return; // Handle ?
|
|
||||||
}
|
|
||||||
to_concat->data = new_data;
|
|
||||||
|
|
||||||
memcpy(to_concat->data, str, size);
|
|
||||||
memcpy(to_concat->data + size, tmp, tmp_size);
|
|
||||||
free(tmp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void string_to_lowercase(struct string *str)
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < str->size; i++)
|
|
||||||
{
|
|
||||||
if (isupper(str->data[i]))
|
|
||||||
str->data[i] += 'a' - 'A';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void string_destroy(struct string *str)
|
|
||||||
{
|
|
||||||
if (str != NULL)
|
|
||||||
{
|
|
||||||
if (str->data != NULL)
|
|
||||||
{
|
|
||||||
free(str->data);
|
|
||||||
}
|
|
||||||
free(str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *string_to_charptr(struct string *str)
|
|
||||||
{
|
|
||||||
if (str == NULL || str->data == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
char *res = calloc(str->size + 1, sizeof(char));
|
|
||||||
if (res == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
memcpy(res, str->data, str->size);
|
|
||||||
|
|
||||||
res[str->size] = '\0';
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// WARNING takes n as valid, will not stop on '\0'
|
|
||||||
int string_compare_strictly_n_str(const struct string *str1, const char *str2,
|
|
||||||
size_t n)
|
|
||||||
{
|
|
||||||
if (str1->size < n)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
size_t i = 0;
|
|
||||||
int res = 0;
|
|
||||||
while (i < n)
|
|
||||||
{
|
|
||||||
res += str1->data[i];
|
|
||||||
res -= str2[i];
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
#include "fmt_time.h"
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
char *get_time(void)
|
|
||||||
{
|
|
||||||
char *buf = malloc(64 * sizeof(char)); // Oui, 64
|
|
||||||
time_t local_ts = time(NULL);
|
|
||||||
struct tm *gmt_time = gmtime(&local_ts);
|
|
||||||
|
|
||||||
// return asctime(gmt_time);
|
|
||||||
strftime(buf, 64, "%a, %d %b %Y %H:%M:%S %Z", gmt_time);
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
#ifndef FMT_TIME_H
|
|
||||||
#define FMT_TIME_H
|
|
||||||
|
|
||||||
/* @brief Calculates the GMT time based on machine's local time
|
|
||||||
* and returns it as a string
|
|
||||||
*
|
|
||||||
* @return A NULL-terminated string containing the fromatted GMT time
|
|
||||||
*/
|
|
||||||
char *get_time();
|
|
||||||
|
|
||||||
#endif // ! FMT_TIME_H
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
<h1> YEAAH </h1>
|
|
||||||
|
|
@ -1,178 +0,0 @@
|
||||||
import subprocess as sp
|
|
||||||
import http
|
|
||||||
import requests
|
|
||||||
import socket
|
|
||||||
import pytest
|
|
||||||
import time
|
|
||||||
|
|
||||||
host = "127.0.0.1"
|
|
||||||
port = "6994"
|
|
||||||
|
|
||||||
executable = "./httpd"
|
|
||||||
|
|
||||||
def spawn_httpd(stdout_filename, args=[]):
|
|
||||||
with open(stdout_filename,"w") as f:
|
|
||||||
httpd_proc = sp.Popen([executable,"--pid_file","/tmp/HTTPd.pid","--ip",host,"--port", port, "--root_dir","test_root_dir/","--server_name","httpd"] if args == [] else [executable] + args, stdout=f,stderr=sp.PIPE,bufsize=0)
|
|
||||||
time.sleep(0.2)
|
|
||||||
|
|
||||||
return httpd_proc
|
|
||||||
|
|
||||||
def kill_httpd(proc):
|
|
||||||
#proc.send_signal(sp.SIGINT)
|
|
||||||
proc.kill()
|
|
||||||
|
|
||||||
# @pytest.mark.timeout(2)
|
|
||||||
def test_bad_config():
|
|
||||||
proc = spawn_httpd("out.log", ["hello","world"])
|
|
||||||
proc.wait(1)
|
|
||||||
try:
|
|
||||||
assert proc.returncode == 2
|
|
||||||
finally:
|
|
||||||
kill_httpd(proc)
|
|
||||||
|
|
||||||
# @pytest.mark.timeout(2)
|
|
||||||
def test_get_index():
|
|
||||||
proc = spawn_httpd("out.log")
|
|
||||||
req = requests.get(f"http://{host}:{port}/index.html")
|
|
||||||
assert req.status_code == 200
|
|
||||||
with open("./test_root_dir/index.html","r") as f:
|
|
||||||
try:
|
|
||||||
assert f.read() == req.text
|
|
||||||
finally:
|
|
||||||
kill_httpd(proc)
|
|
||||||
|
|
||||||
# @pytest.mark.timeout(2)
|
|
||||||
def test_get_default():
|
|
||||||
proc = spawn_httpd("out.log")
|
|
||||||
req = requests.get(f"http://{host}:{port}/")
|
|
||||||
assert req.status_code == 200
|
|
||||||
with open("./test_root_dir/index.html","r") as f:
|
|
||||||
try:
|
|
||||||
assert f.read() == req.text
|
|
||||||
finally:
|
|
||||||
kill_httpd(proc)
|
|
||||||
|
|
||||||
# @pytest.mark.timeout(2)
|
|
||||||
def test_no_file():
|
|
||||||
proc = spawn_httpd("out.log")
|
|
||||||
req = requests.get(f"http://{host}:{port}/notindex.html")
|
|
||||||
assert req.status_code == 404
|
|
||||||
|
|
||||||
# @pytest.mark.timeout(2)
|
|
||||||
def test_bad_request():
|
|
||||||
proc = spawn_httpd("out.log")
|
|
||||||
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
|
||||||
sock.connect((host,int(port)))
|
|
||||||
|
|
||||||
request = f"GET /index.html FTP/1.1\r\nHOST: {host}:{port}\r\nConnection: close\r\n\r\n"
|
|
||||||
|
|
||||||
sock.sendall(request.encode())
|
|
||||||
|
|
||||||
resp = sock.recv(1024)
|
|
||||||
resp_decoded = resp.decode()
|
|
||||||
|
|
||||||
try:
|
|
||||||
assert "400 Bad Request" in resp_decoded
|
|
||||||
finally:
|
|
||||||
kill_httpd(proc)
|
|
||||||
|
|
||||||
# @pytest.mark.timeout(2)
|
|
||||||
def test_invalid_method():
|
|
||||||
proc = spawn_httpd("out.log")
|
|
||||||
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
|
||||||
sock.connect((host,int(port)))
|
|
||||||
|
|
||||||
request = f"PUT /index.html HTTP/1.1\r\nHOST: {host}:{port}\r\nConnection: close\r\n\r\n"
|
|
||||||
|
|
||||||
sock.sendall(request.encode())
|
|
||||||
|
|
||||||
response = http.client.HTTPResponse(sock)
|
|
||||||
response.begin()
|
|
||||||
|
|
||||||
try:
|
|
||||||
assert response.status == 405
|
|
||||||
finally:
|
|
||||||
kill_httpd(proc)
|
|
||||||
|
|
||||||
|
|
||||||
# @pytest.mark.timeout(2)
|
|
||||||
def test_invalid_version():
|
|
||||||
proc = spawn_httpd("out.log")
|
|
||||||
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
|
||||||
sock.connect((host,int(port)))
|
|
||||||
|
|
||||||
request = f"GET /index.html HTTP/1.2\r\nHOST: {host}:{port}\r\nConnection: close\r\n\r\n"
|
|
||||||
|
|
||||||
sock.sendall(request.encode())
|
|
||||||
|
|
||||||
response = http.client.HTTPResponse(sock)
|
|
||||||
response.begin()
|
|
||||||
|
|
||||||
try:
|
|
||||||
assert response.status == 505
|
|
||||||
finally:
|
|
||||||
kill_httpd(proc)
|
|
||||||
|
|
||||||
# @pytest.mark.timeout(2)
|
|
||||||
def test_bad_request():
|
|
||||||
proc = spawn_httpd("out.log")
|
|
||||||
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
|
||||||
sock.connect((host,int(port)))
|
|
||||||
|
|
||||||
request = f"GET /index.html FTP/1.1\r\nHOST: {host}:{port}\r\nConnection: close\r\n\r\n"
|
|
||||||
|
|
||||||
sock.sendall(request.encode())
|
|
||||||
|
|
||||||
response = http.client.HTTPResponse(sock)
|
|
||||||
response.begin()
|
|
||||||
|
|
||||||
try:
|
|
||||||
assert response.status == 400
|
|
||||||
finally:
|
|
||||||
kill_httpd(proc)
|
|
||||||
|
|
||||||
@pytest.mark.timeout(2)
|
|
||||||
def test_head_index():
|
|
||||||
proc = spawn_httpd("out.log")
|
|
||||||
try:
|
|
||||||
req = requests.head(f"http://{host}:{port}/index.html")
|
|
||||||
assert req.status_code == 200
|
|
||||||
assert req.text == ""
|
|
||||||
finally:
|
|
||||||
kill_httpd(proc)
|
|
||||||
|
|
||||||
@pytest.mark.timeout(2)
|
|
||||||
def test_missing_host():
|
|
||||||
proc = spawn_httpd("out.log")
|
|
||||||
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
|
||||||
sock.connect((host,int(port)))
|
|
||||||
|
|
||||||
request = f"GET /index.html HTTP/1.1\r\nConnection: close\r\n\r\n"
|
|
||||||
|
|
||||||
sock.sendall(request.encode())
|
|
||||||
|
|
||||||
response = http.client.HTTPResponse(sock)
|
|
||||||
response.begin()
|
|
||||||
|
|
||||||
try:
|
|
||||||
assert response.status == 400
|
|
||||||
finally:
|
|
||||||
kill_httpd(proc)
|
|
||||||
|
|
||||||
@pytest.mark.timeout(2)
|
|
||||||
def test_directory_traversal():
|
|
||||||
proc = spawn_httpd("out.log")
|
|
||||||
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
|
|
||||||
sock.connect((host,int(port)))
|
|
||||||
|
|
||||||
request = f"GET /../test_suite.py HTTP/1.1\r\nHOST: {host}:{port}\r\nConnection: close\r\n\r\n"
|
|
||||||
|
|
||||||
sock.sendall(request.encode())
|
|
||||||
|
|
||||||
response = http.client.HTTPResponse(sock)
|
|
||||||
response.begin()
|
|
||||||
|
|
||||||
try:
|
|
||||||
assert response.status in [400, 403, 404]
|
|
||||||
finally:
|
|
||||||
kill_httpd(proc)
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Simple test script for HTTP/1.1 Host header compliance
|
|
||||||
# Usage: ./test_host_compliance.sh [IP] [PORT]
|
|
||||||
|
|
||||||
IP=${1:-"127.0.0.1"}
|
|
||||||
PORT=${2:-"6996"}
|
|
||||||
|
|
||||||
echo "Targeting server at $IP:$PORT"
|
|
||||||
|
|
||||||
test_req() {
|
|
||||||
NAME="$1"
|
|
||||||
PAYLOAD="$2"
|
|
||||||
EXPECTED="$3"
|
|
||||||
|
|
||||||
echo -n "Test: $NAME ... "
|
|
||||||
# Send payload, wait max 1s for response
|
|
||||||
RESP=$(printf "$PAYLOAD" | nc -w 1 $IP $PORT 2>/dev/null | head -n 1)
|
|
||||||
|
|
||||||
if echo "$RESP" | grep -q "$EXPECTED"; then
|
|
||||||
echo "PASS"
|
|
||||||
else
|
|
||||||
echo "FAIL (Expected '$EXPECTED', got '$RESP')"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 1. Valid Request
|
|
||||||
test_req "Valid Request" "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n" "200 OK"
|
|
||||||
|
|
||||||
# 2. Missing Host Header
|
|
||||||
test_req "Missing Host" "GET / HTTP/1.1\r\n\r\n" "400 Bad Request"
|
|
||||||
|
|
||||||
# 3. Empty Host Header
|
|
||||||
test_req "Empty Host" "GET / HTTP/1.1\r\nHost:\r\n\r\n" "400 Bad Request"
|
|
||||||
|
|
||||||
# 4. Multiple Host Headers
|
|
||||||
test_req "Multiple Hosts" "GET / HTTP/1.1\r\nHost: a\r\nHost: b\r\n\r\n" "400 Bad Request"
|
|
||||||
|
|
||||||
# 5. Bad Protocol Version (Should be 505 now with the fix)
|
|
||||||
test_req "Bad Protocol (HTTP/1.0)" "GET / HTTP/1.0\r\nHost: localhost\r\n\r\n" "505"
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue