diff --git a/.vscode/configurationCache.log b/.vscode/configurationCache.log new file mode 100644 index 0000000..92f7672 --- /dev/null +++ b/.vscode/configurationCache.log @@ -0,0 +1 @@ +{"buildTargets":["cgi.o","clean","controller.o","handle.o","http_response.o","log.o","main.o","server","visitor.o"],"launchTargets":["\\d\\Documents\\http>server()"],"customConfigurationProvider":{"workspaceBrowse":{"browsePath":["\\d\\Documents\\http"],"compilerArgs":["-Wall","-O2","-c","log.cc","-o","log.o"],"compilerPath":"C:\\msys64\\ucrt64\\bin\\g++.exe"},"fileIndex":[["\\d\\Documents\\http\\cgi.c",{"uri":{"$mid":1,"fsPath":"\\d\\Documents\\http\\cgi.c","_sep":1,"path":"/d/Documents/http/cgi.c","scheme":"file"},"configuration":{"defines":[],"standard":"c11","includePath":[],"forcedInclude":[],"intelliSenseMode":"gcc-x64","compilerPath":"C:\\msys64\\ucrt64\\bin\\gcc.exe","compilerArgs":["-Wall","-O2","-c","cgi.c","-o","cgi.o"]}}],["\\d\\Documents\\http\\main.c",{"uri":{"$mid":1,"fsPath":"\\d\\Documents\\http\\main.c","_sep":1,"path":"/d/Documents/http/main.c","scheme":"file"},"configuration":{"defines":[],"standard":"c11","includePath":[],"forcedInclude":[],"intelliSenseMode":"gcc-x64","compilerPath":"C:\\msys64\\ucrt64\\bin\\gcc.exe","compilerArgs":["-Wall","-O2","-c","main.c","-o","main.o"]}}],["\\d\\Documents\\http\\visitor.c",{"uri":{"$mid":1,"fsPath":"\\d\\Documents\\http\\visitor.c","_sep":1,"path":"/d/Documents/http/visitor.c","scheme":"file"},"configuration":{"defines":[],"standard":"c11","includePath":[],"forcedInclude":[],"intelliSenseMode":"gcc-x64","compilerPath":"C:\\msys64\\ucrt64\\bin\\gcc.exe","compilerArgs":["-Wall","-O2","-c","visitor.c","-o","visitor.o"]}}],["\\d\\Documents\\http\\controller.cc",{"uri":{"$mid":1,"fsPath":"\\d\\Documents\\http\\controller.cc","_sep":1,"path":"/d/Documents/http/controller.cc","scheme":"file"},"configuration":{"defines":[],"standard":"c++17","includePath":[],"forcedInclude":[],"intelliSenseMode":"gcc-x64","compilerPath":"C:\\msys64\\ucrt64\\bin\\g++.exe","compilerArgs":["-Wall","-O2","-c","controller.cc","-o","controller.o"]}}],["\\d\\Documents\\http\\handle.cc",{"uri":{"$mid":1,"fsPath":"\\d\\Documents\\http\\handle.cc","_sep":1,"path":"/d/Documents/http/handle.cc","scheme":"file"},"configuration":{"defines":[],"standard":"c++17","includePath":[],"forcedInclude":[],"intelliSenseMode":"gcc-x64","compilerPath":"C:\\msys64\\ucrt64\\bin\\g++.exe","compilerArgs":["-Wall","-O2","-c","handle.cc","-o","handle.o"]}}],["\\d\\Documents\\http\\http_response.cc",{"uri":{"$mid":1,"fsPath":"\\d\\Documents\\http\\http_response.cc","_sep":1,"path":"/d/Documents/http/http_response.cc","scheme":"file"},"configuration":{"defines":[],"standard":"c++17","includePath":[],"forcedInclude":[],"intelliSenseMode":"gcc-x64","compilerPath":"C:\\msys64\\ucrt64\\bin\\g++.exe","compilerArgs":["-Wall","-O2","-c","http_response.cc","-o","http_response.o"]}}],["\\d\\Documents\\http\\log.cc",{"uri":{"$mid":1,"fsPath":"\\d\\Documents\\http\\log.cc","_sep":1,"path":"/d/Documents/http/log.cc","scheme":"file"},"configuration":{"defines":[],"standard":"c++17","includePath":[],"forcedInclude":[],"intelliSenseMode":"gcc-x64","compilerPath":"C:\\msys64\\ucrt64\\bin\\g++.exe","compilerArgs":["-Wall","-O2","-c","log.cc","-o","log.o"]}}]]}} \ No newline at end of file diff --git a/.vscode/dryrun.log b/.vscode/dryrun.log new file mode 100644 index 0000000..260ab91 --- /dev/null +++ b/.vscode/dryrun.log @@ -0,0 +1,16 @@ +make.exe --dry-run --always-make --keep-going --print-directory +make: Entering directory '/d/Documents/http' +gcc -Wall -O2 -c cgi.c -o cgi.o + +gcc -Wall -O2 -c main.c -o main.o +gcc -Wall -O2 -c visitor.c -o visitor.o + +g++ -Wall -O2 -c controller.cc -o controller.o + +g++ -Wall -O2 -c handle.cc -o handle.o +g++ -Wall -O2 -c http_response.cc -o http_response.o + +g++ -Wall -O2 -c log.cc -o log.o +g++ -Wall -O2 cgi.o main.o visitor.o controller.o handle.o http_response.o log.o -o server -s -lpthread +make: Leaving directory '/d/Documents/http' + diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..65e1ec0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "makefile.extensionOutputFolder": "./.vscode" +} \ No newline at end of file diff --git a/.vscode/targets.log b/.vscode/targets.log new file mode 100644 index 0000000..219de8d --- /dev/null +++ b/.vscode/targets.log @@ -0,0 +1,403 @@ +make.exe all --print-data-base --no-builtin-variables --no-builtin-rules --question +make: *** No rule to make target 'all'. Stop. + +# GNU Make 4.3 +# Built for x86_64-pc-msys +# Copyright (C) 1988-2020 Free Software Foundation, Inc. +# License GPLv3+: GNU GPL version 3 or later +# This is free software: you are free to change and redistribute it. +# There is NO WARRANTY, to the extent permitted by law. + + +# Make data base, printed on Fri Nov 12 21:43:11 2021 + +# Variables + +# environment +SYSTEMDRIVE = C: +# environment +JAVA_HOME = C:\Program Files\Microsoft\jdk-17.0.0.35-hotspot\ +# environment +LC_ALL = C +# environment + +USERPROFILE = C:\Users\everything411 +# environment +LOCALAPPDATA = C:\Users\everything411\AppData\Local +# environment +VSCODE_IPC_HOOK_EXTHOST = \\.\pipe\vscode-ipc-b5caae71-6895-42e0-9659-2e5ed667d015-sock +# environment +VSCODE_CWD = C:\Users\everything411\AppData\Local\Programs\Microsoft VS Code +# environment +SYSTEMROOT = C:\WINDOWS +# environment +PROCESSOR_LEVEL = 6 +# environment +PSMODULEPATH = C:\Program Files\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules +# default +MAKE_COMMAND := make +# environment +GOPATH = C:\Users\everything411\go +# automatic +@D = $(patsubst %/,%,$(dir $@)) +# environment +CHROME_CRASHPAD_PIPE_NAME = \\.\pipe\crashpad_6948_QJBWIBQYAUFKBJKB +# environment +VSCODE_HANDLES_UNCAUGHT_ERRORS = true +# default +.VARIABLES := +# automatic +%D = $(patsubst %/,%,$(dir $%)) +# automatic +^D = $(patsubst %/,%,$(dir $^)) +# environment +VSCODE_LOG_STACK = false +# automatic +%F = $(notdir $%) +# environment +VSCODE_CODE_CACHE_PATH = C:\Users\everything411\AppData\Roaming\Code\CachedData\3a6960b964327f0e3882ce18fcebd07ed191b316 +# environment +LANG = C +# default +.LOADED := +# environment +TMP = /c/Users/EVERYT~1/AppData/Local/Temp +# default +.INCLUDE_DIRS = /usr/include /usr/include +# makefile +MAKEFLAGS = pqrR +# environment +ONEDRIVE = C:\Users\everything411\OneDrive +# environment +POWERSHELL_DISTRIBUTION_CHANNEL = MSI:Windows 10 Pro for Workstations +# makefile +CURDIR := /d/Documents/http +# environment +VSCODE_PIPE_LOGGING = true +# environment +APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL = true +# automatic +*D = $(patsubst %/,%,$(dir $*)) +# makefile (from 'Makefile', line 3) +CXXSOURCE = $(wildcard *.cc) +# environment +PROGRAMFILES = C:\Program Files +# environment +MFLAGS = -pqrR +# default +.SHELLFLAGS := -c +# environment +COMMONPROGRAMW6432 = C:\Program Files\Common Files +# automatic ++D = $(patsubst %/,%,$(dir $+)) +# makefile (from 'Makefile', line 1) +MAKEFILE_LIST := Makefile +# automatic +@F = $(notdir $@) +# environment +VSCODE_VERBOSE_LOGGING = true +# environment +VSCODE_PID = 6948 +# automatic +?D = $(patsubst %/,%,$(dir $?)) +# automatic +*F = $(notdir $*) +# automatic + +#include +#include +#include +#include +#include +#include +extern char **environ; + +void destroy_cgi_result(struct cgi_result *p) +{ + if (p) + { + if (p->response) + { + free(p->response); + } + free(p); + } +} + +struct cgi_result *do_exec_cgi(struct cgi_request request, const char **extra_env) +{ + char *php_cgi_path = "/usr/bin/php-cgi"; + char *php_cgi_argv[] = {php_cgi_path, NULL}; + int fd[2]; + int fd2[2]; + pid_t pid; + if (pipe(fd) < 0 || pipe(fd2) < 0) + { + perror("pipe"); + exit(1); + } + if ((pid = fork()) < 0) + { + perror("fork"); + exit(1); + } + if (pid) + { + close(fd[0]); + close(fd2[1]); + write(fd[1], request.content, request.content_length); + struct cgi_result *p = malloc(sizeof(struct cgi_result)); + p->response = malloc(102400); + p->length = 0; + int n = 0; + for (;;) + { + n = read(fd2[0], p->response + p->length, 4096); + p->length += n; + if (n <= 0) + { + p->response[p->length] = 0; + break; + } + } + waitpid(pid, NULL, 0); + return p; + } + else + { + close(fd[1]); + close(fd2[0]); + dup2(fd[0], STDIN_FILENO); + dup2(fd2[1], STDOUT_FILENO); + + char query_string[128]; + char request_method[32]; + char content_type[128]; + char content_length[32]; + char script_name[128]; + char document_root[128]; + char script_filename[256]; + char remote_addr[32]; + char remote_port[32]; + + snprintf(query_string, sizeof(query_string), "QUERY_STRING=%s", request.query_string); + snprintf(request_method, sizeof(request_method), "REQUEST_METHOD=%s", request.request_method); + snprintf(content_type, sizeof(content_type), "CONTENT_TYPE=%s", request.content_type); + snprintf(content_length, sizeof(content_length), "CONTENT_LENGTH=%d", request.content_length); + snprintf(query_string, sizeof(query_string), "QUERY_STRING=%s", request.query_string); + snprintf(script_name, sizeof(script_name), "SCRIPT_NAME=%s", request.script_name); + snprintf(document_root, sizeof(document_root), "DOCUMENT_ROOT=%s", request.document_root); + snprintf(script_filename, sizeof(script_filename), "SCRIPT_FILENAME=%s/%s", request.document_root, + request.script_name); + snprintf(remote_addr, sizeof(remote_addr), "REMOTE_ADDR=%s", request.remote_addr); + snprintf(remote_port, sizeof(remote_port), "REMOTE_PORT=%d", request.remote_port); + // snprintf(query_string, sizeof(query_string), "QUERY_STRING=%s", request.query_string); + char *new_environ[] = {query_string, + request_method, + content_type, + content_length, + script_filename, + script_name, + document_root, + remote_addr, + remote_port, + "GATEWAY_INTERFACE=CGI/1.1", + "SERVER_PROTOCOL=HTTP/1.1", + "REQUEST_SCHEME=http", + "SERVER_SOFTWARE=naivehttpd/0.1", + "SERVER_ADDR=127.0.0.1", + "SERVER_NAME=default_server", + "SERVER_PORT=80", + "REDIRECT_STATUS=200", + NULL}; + int c = 0; + while (environ[c]) + { + c++; + } + int c2 = 0; + while (extra_env[c2]) + { + c2++; + } + char **php_cgi_environ = malloc((c + c2) * sizeof(char *) + sizeof(new_environ)); + memcpy(php_cgi_environ, environ, c * sizeof(char *)); + memcpy(php_cgi_environ + c, extra_env, c2 * sizeof(char *)); + memcpy(php_cgi_environ + c + c2, new_environ, sizeof(new_environ)); + execve(php_cgi_path, php_cgi_argv, php_cgi_environ); + printf("Status: 500 Internal Server Error\r\n"); + printf("Content-type: text/plain\r\n\r\nexecve error\n"); + exit(EXIT_FAILURE); + } +} diff --git a/controller.cc b/controller.cc new file mode 100644 index 0000000..8dea0b8 --- /dev/null +++ b/controller.cc @@ -0,0 +1,199 @@ +#include "http.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +http_response *build_http_response(string &resp) +{ + int resp_length = resp.length(); + http_response *p = (http_response *)malloc(sizeof(http_response)); + p->length = resp_length; + p->content = malloc(resp_length + 1 + 4); + memcpy(p->content, resp.c_str(), resp_length); + memcpy((char *)p->content + resp_length, "\r\n\r\n\0", 5); + return p; +} + +extern "C" void destroy_http_response(http_response *p) +{ + if (p) + { + if (p->content) + { + free(p->content); + } + free(p); + } +} + +static bool endsWith(const std::string &str, const std::string &suffix) +{ + return str.size() >= suffix.size() && 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix); +} + +extern "C" http_response *handle_request(const char *http_request, char *remote_addr, int remote_port) +{ + http_context http_context; + http_context.remote_addr = remote_addr; + http_context.remote_port = remote_port; + http_context.raw_request = http_request; + parse_request(http_context, http_request); + + // auto &request_key_map = http_context.headers; + auto &method = http_context.method; + auto &url = http_context.path; + // auto &query_str = http_context.query_string; + string header, content; + string content_type; + int content_len; + string res; + int status; + if (url == "/418") + { + header = HTTP_response_header2(418, 13); + content = "\r\nI'm a teapot\n"; + res = header + content; + status = 418; + } + else if (endsWith(url, ".php")) // run cgi + { + if (method == UNKNOWN) + { + // 400 Bad request + RawContent *ctnt_p = get_raw_by_path("/400.html"); + content = ctnt_p->raw_buf; + content_type = ctnt_p->raw_type; + content_len = ctnt_p->raw_len; + header = HTTP_response_header(400, content_type, content_len); + del_raw(ctnt_p); + status = 400; + } + else + { + cgi_request request; + request.request_method = http_context.raw_method.c_str(); + request.document_root = "./webroot"; + request.query_string = http_context.query_string.c_str(); + request.remote_addr = http_context.remote_addr.c_str(); + request.remote_port = http_context.remote_port; + request.script_name = http_context.path.c_str(); + request.content_length = http_context.body.length(); + request.content = http_context.body.c_str(); + vector vs; + for (auto &i : http_context.headers) + { + stringstream ss; + ss << "HTTP_"; + auto &key = i.first; + auto &value = i.second; + for (auto c : key) + { + if (c == '-') + { + ss << '_'; + } + else + { + ss << (char)toupper(c); + } + } + ss << '='; + ss << value; + vs.push_back(ss.str()); + } + const char **header_env = (const char **)calloc(vs.size() + 1, sizeof(char *)); + for (int i = 0; i < (int)vs.size(); i++) + { + header_env[i] = vs[i].c_str(); + } + auto p = http_context.headers.find("Content-Type"); + if (p != http_context.headers.end()) + { + request.content_type = p->second.c_str(); + } + else + { + request.content_type = ""; + } + + auto result = do_exec_cgi(request, header_env); + free(header_env); + content = result->response; + // puts(result->response); + status = 200; + char *status_ptr = strstr(result->response, "Status:"); + if (status_ptr != NULL) + { + // puts(status_ptr); + sscanf(status_ptr + 7, "%d", &status); + } + char *header_end_ptr = strstr(result->response, "\r\n\r\n"); + if (header_end_ptr != NULL) + { + content_len = result->length - (header_end_ptr - result->response + 4); + } + else + { + content_len = 0; + } + header = HTTP_response_header2(status, content_len); + res = header + content; + destroy_cgi_result(result); + } + } + else // serve static web pages + { + if (method == GET) + { + RawContent *ctnt_p = get_raw_by_path(url.c_str()); + content = ctnt_p->raw_buf; + content_type = ctnt_p->raw_type; + content_len = ctnt_p->raw_len; + if (ctnt_p->raw_exist) + { + header = HTTP_response_header(200, content_type, content_len); + status = 200; + } + else + { + header = HTTP_response_header(404, content_type, content_len); + status = 404; + } + del_raw(ctnt_p); + } + else if (method == HEAD) + { + RawContent *ctnt_p = get_raw_by_path(url.c_str()); + content_type = ctnt_p->raw_type; + content_len = ctnt_p->raw_len; + if (ctnt_p->raw_exist) + { + header = HTTP_response_header(200, content_type, content_len); + status = 200; + } + else + { + header = HTTP_response_header(404, content_type, content_len); + status = 404; + } + del_raw(ctnt_p); + } + else + { + header = HTTP_response_header(405, "text/plain", 23); + content = "405 Method Not Allowed\n"; + status = 405; + } + res = header + content; + } + auto response = build_http_response(res); + log_http_request(http_context, status, content.length()); + return response; +} diff --git a/handle.cc b/handle.cc new file mode 100644 index 0000000..4677358 --- /dev/null +++ b/handle.cc @@ -0,0 +1,128 @@ +#include "http.hpp" +#include +#include +#include +#include +#include + +using namespace std; + +void parse_request(http_context &http_context, const char *buf) +{ + size_t i = 0, j = 0; + char method[8] = {}; + + char *url = new char[1024]; + char *query = new char[1024]; + size_t buflen = strlen(buf); + while (i < buflen && j < 7 && !isspace(buf[i])) + { + method[j++] = buf[i++]; + } + method[j] = '\0'; + http_context.raw_method = method; + if (!strcmp(method, "GET")) + { + http_context.method = GET; + } + else if (!strcmp(method, "POST")) + { + http_context.method = POST; + } + else if (!strcmp(method, "PUT")) + { + http_context.method = PUT; + } + else if (!strcmp(method, "DELETE")) + { + http_context.method = DELETE; + } + else if (!strcmp(method, "PATCH")) + { + http_context.method = PATCH; + } + else if (!strcmp(method, "HEAD")) + { + http_context.method = HEAD; + } + else if (!strcmp(method, "OPTIONS")) + { + http_context.method = OPTIONS; + } + else if (!strcmp(method, "TRACE")) + { + http_context.method = TRACE; + } + else + { + http_context.method = UNKNOWN; + } + + while (i < buflen && isspace(buf[i])) + i++; + + j = 0; + int k = 0; + while (i < buflen && j < 1023 && !isspace(buf[i])) + { + url[j++] = buf[i++]; + if (buf[i] == '?') + { + k = 0; + i++; + while (i < strlen(buf) && k < 1023 && !isspace(buf[i])) + { + query[k++] = buf[i++]; + } + } + } + + url[j] = '\0'; + query[k] = '\0'; + + http_context.path = url; + http_context.query_string = query; + + while (i < buflen && buf[i] != '\n') + { + i++; + } + i++; + while (i < buflen) + { + char *key = new char[64]; + char *value = new char[1024]; + j = 0; + + memset(key, '\0', 64); + memset(value, '\0', 1024); + while (i < buflen && buf[i] != ':') + { + key[j++] = buf[i++]; + } + key[j] = '\0'; + j = 0; + while (i < buflen && (buf[i] == ':' || isspace(buf[i]))) + i++; + + while (i < buflen && buf[i] != '\n') + { + value[j++] = buf[i++]; + } + if (value[j - 1] == '\r') + { + value[j - 1] = 0; + } + value[j] = '\0'; + http_context.headers.insert(pair(key, value)); + + i = i + 1; + delete[] key; + delete[] value; + if (buf[i + 1] == '\n') + break; + } + delete[] url; + delete[] query; + http_context.body = &buf[i + 2]; +} diff --git a/http.h b/http.h new file mode 100644 index 0000000..24509d9 --- /dev/null +++ b/http.h @@ -0,0 +1,44 @@ +#ifndef HTTP_H +#define HTTP_H + +struct http_response +{ + int length; + void *content; +}; +enum http_method +{ + GET, + POST, + PUT, + DELETE, + PATCH, + HEAD, + OPTIONS, + TRACE, + UNKNOWN +}; +struct cgi_request +{ + const char *query_string; + const char *request_method; + int content_length; + const char *content_type; + const void *content; + const char *script_name; + const char *document_root; + const char *remote_addr; + int remote_port; +}; +struct cgi_result +{ + int length; + char *response; +}; +void init_log(); +struct http_response *handle_request(const char *http_request, char *remote_addr, int remote_port); +void destroy_cgi_result(struct cgi_result *p); +void destroy_http_response(struct http_response *p); +struct cgi_result *do_exec_cgi(struct cgi_request request, const char **extra_env); + +#endif diff --git a/http.hpp b/http.hpp new file mode 100644 index 0000000..1b11e13 --- /dev/null +++ b/http.hpp @@ -0,0 +1,31 @@ +#ifndef HTTP_HPP +#define HTTP_HPP + +#include +#include +#include +#include +#include +#include "http_response.h" + +extern "C" +{ +#include "visitor.h" +#include "http.h" +} + +struct http_context +{ + std::string remote_addr; + int remote_port; + std::string body; + std::string raw_method; + enum http_method method; + std::string path; + std::string query_string; + std::map headers; + const char *raw_request; +}; +void log_http_request(http_context &context, int status, int length); +void parse_request(http_context &http_context, const char *buf); +#endif diff --git a/http_response.cc b/http_response.cc new file mode 100644 index 0000000..45e6cc7 --- /dev/null +++ b/http_response.cc @@ -0,0 +1,135 @@ +#include "http_response.h" +#include +#include +using namespace std; +const char *descripions[600]; + +string HTTP_response_status(int status_code) +{ + if (!*descripions) + { + for (int i = 0; i < 600; i++) + { + descripions[i] = ""; + } + descripions[100] = "Continue"; + descripions[101] = "Switching Protocol"; + descripions[200] = "OK"; + descripions[201] = "Created"; + descripions[202] = "Accepted"; + descripions[203] = "Non-Authoritative Information"; + descripions[204] = "No Content"; + descripions[205] = "Reset Content"; + descripions[206] = "Partial Content"; + descripions[300] = "Multiple Choice"; + descripions[301] = "Moved Permanently"; + descripions[302] = "Found"; + descripions[303] = "See Other"; + descripions[304] = "Not Modified"; + descripions[307] = "Temporary Redirect"; + descripions[308] = "Permanent Redirect"; + descripions[400] = "Bad Request"; + descripions[401] = "Unauthorized"; + descripions[403] = "Forbidden"; + descripions[404] = "Not Found"; + descripions[405] = "Method Not Allowed"; + descripions[406] = "Not Acceptable"; + descripions[407] = "Proxy Authentication Required"; + descripions[408] = "Request Timeout"; + descripions[409] = "Conflict"; + descripions[410] = "Gone"; + descripions[411] = "Length Required"; + descripions[412] = "Precondition Failed"; + descripions[413] = "Payload Too Large"; + descripions[414] = "URI Too Long"; + descripions[415] = "Unsupported Media Type"; + descripions[416] = "Range Not Satisfiable"; + descripions[417] = "Expectation Failed"; + descripions[418] = "I'm a teapot"; + descripions[426] = "Upgrade Required"; + descripions[428] = "Precondition Required"; + descripions[429] = "Too Many Requests"; + descripions[431] = "Request Header Fields Too Large"; + descripions[500] = "Internal Server Error"; + descripions[501] = "Not Implemented"; + descripions[502] = "Bad Gateway"; + descripions[503] = "Service Unavailable"; + descripions[504] = "Gateway Timeout"; + descripions[505] = "HTTP Version Not Supported"; + } + string descrip; + switch (status_code) + { + case 200: + descrip = "OK"; + break; + case 400: + descrip = "Bad Request"; + break; + case 403: + descrip = "Forbidden"; + break; + case 404: + descrip = "Not Found"; + break; + default: + descrip = ""; + } + stringstream sstr; + sstr << "HTTP/1.1 " << status_code << " " << descripions[status_code] << "\r\n"; + string res = sstr.str(); + return res; +} + +string HTTP_response_date() +{ + static const char *day_name[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + static const char *month_name[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + time_t rawtime; + struct tm tm; + time(&rawtime); + gmtime_r(&rawtime, &tm); + char *timestr = (char *)malloc(50); + sprintf(timestr, "%s, %02d %s %04d %02d:%02d:%02d GMT", day_name[tm.tm_wday], tm.tm_mday, month_name[tm.tm_mon], + tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec); + stringstream sstr; + sstr << "Date: " << timestr << "\r\n"; + string res = sstr.str(); + free(timestr); + return res; +} + +string HTTP_response_content_info(string content_type, int content_len) +{ + stringstream sstr; + sstr << "Content-Type: " << content_type << "\r\n"; + sstr << "Content-Length: " << content_len << "\r\n"; + sstr << "Connection: keep-alive" + << "\r\n"; + string res = sstr.str(); + return res; +} + +string HTTP_response_header(int status_code, string content_type, int content_len) +{ + string res = ""; + res += HTTP_response_status(status_code); + res += HTTP_response_date(); + res += HTTP_response_content_info(content_type, content_len); + res += "Server: naivehttpd/0.1\r\n"; + res += "\r\n"; + return res; +} +string HTTP_response_header2(int status_code, int content_length) +{ + char str[128]; + snprintf(str, 128, "Content-Length: %d\r\n", content_length); + string res = ""; + res += HTTP_response_status(status_code); + res += HTTP_response_date(); + res += "Connection: keep-alive\r\n"; + res += "Server: naivehttpd/0.1\r\n"; + res += str; + return res; +} diff --git a/http_response.h b/http_response.h new file mode 100644 index 0000000..841e813 --- /dev/null +++ b/http_response.h @@ -0,0 +1,14 @@ +#ifndef HTTP_RESPONSE_H +#define HTTP_RESPONSE_H + +#include +#include +#include +#include + +std::string HTTP_response_status(int status_code); +std::string HTTP_response_date(); +std::string HTTP_response_content_info(std::string content_type, int content_len); +std::string HTTP_response_header(int status_code, std::string content_type, int content_len); +std::string HTTP_response_header2(int status_code, int content_length); +#endif diff --git a/log.cc b/log.cc new file mode 100644 index 0000000..765b00e --- /dev/null +++ b/log.cc @@ -0,0 +1,78 @@ +#include "http.hpp" +#include +#include +#include +#include +#if __GLIBC__ == 2 && __GLIBC_MINOR__ < 30 +#include +pid_t gettid(void) +{ + return syscall(SYS_gettid); +} +#endif +using namespace std; +const char *month_name[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; +const char *day_name[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; +FILE *logfp = stdout; +char *current_time_to_date_str(char *s, int n) +{ + time_t rawtime; + struct tm tm; + time(&rawtime); + gmtime_r(&rawtime, &tm); + snprintf(s, n, "%s, %02d %s %04d %02d:%02d:%02d GMT", day_name[tm.tm_wday], tm.tm_mday, month_name[tm.tm_mon], + tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec); + return s; +} +extern "C" void init_log() +{ + logfp = fopen("webroot/log/access.log", "a"); + if (logfp == NULL) + { + perror("open log file error, use stdout"); + logfp = stdout; + } + else + { + puts("write log to webroot/log/access.log"); + } +} +void do_log(const char *remote_ip, const char *request_firstline, int status, int length, const char *referer, + const char *user_agent) +{ + char date[64]; + current_time_to_date_str(date, 64); + fprintf(logfp, "(%d) %s - - [%s] \"%s\" %d %d \"%s\", \"%s\"\n", gettid(), remote_ip, date, request_firstline, + status, length, referer, user_agent); +} +void log_http_request(http_context &context, int status, int length) +{ + const char *referer, *user_agent; + int i = 0; + referer = user_agent = "(null)"; + auto &headers = context.headers; + auto referer_header = headers.find("Referer"); + + if (referer_header != headers.end()) + { + referer = referer_header->second.c_str(); + } + auto user_agent_header = headers.find("User-Agent"); + if (user_agent_header != headers.end()) + { + user_agent = user_agent_header->second.c_str(); + } + while (context.raw_request[i] && context.raw_request[i] != '\n') + { + i++; + } + char *str = (char *)malloc(i + 1); + memcpy(str, context.raw_request, i); + str[i] = 0; + if (str[i - 1] == '\r') + { + str[i - 1] = 0; + } + do_log(context.remote_addr.c_str(), str, status, length, referer, user_agent); + free(str); +} diff --git a/main.c b/main.c new file mode 100644 index 0000000..ddc2467 --- /dev/null +++ b/main.c @@ -0,0 +1,272 @@ +#define _GNU_SOURCE +#include "http.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define MAX_EVENTS 10 +#define WORKER_COUNT 8 +#define MAX_CONNECTION 1024 +struct connection_context +{ + int bytes_sent; + struct http_response *response; + char ip_str[16]; + int port; +}; +// fd as index +struct connection_context connection_contexts[MAX_CONNECTION + 64]; +struct thread_info +{ + pthread_t thread_id; /* ID returned by pthread_create() */ + int thread_num; /* Application-defined thread # */ +}; +int worker_epollfd[WORKER_COUNT]; + +char *get_ip_str(const struct sockaddr *sa, char *s, size_t maxlen) +{ + switch (sa->sa_family) + { + case AF_INET: + inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr), s, maxlen); + break; + case AF_INET6: + inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr), s, maxlen); + break; + default: + strncpy(s, "Unknown AF", maxlen); + return NULL; + } + return s; +} +void signal_handler() +{ +} +void *worker(void *arg) +{ + struct thread_info *tinfo = arg; + int worker_id = tinfo->thread_num; + int epollfd = worker_epollfd[worker_id]; + struct epoll_event events[MAX_EVENTS]; + int nfds; + char *recv_buffer = malloc(8192); + for (;;) + { + nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); + if (nfds == -1) + { + if (errno == EINTR) + { + close(epollfd); + free(recv_buffer); + pthread_exit(NULL); + } + else + { + perror("epoll_pwait"); + exit(EXIT_FAILURE); + } + } + for (int n = 0; n < nfds; ++n) + { + int fd = events[n].data.fd; + if (events[n].events & EPOLLIN) + { + // printf("EPOLLIN\n"); + int recv_ret; + recv_ret = recv(fd, recv_buffer, 8192, MSG_DONTWAIT); + if (recv_ret == 0 || (recv_ret == -1 && errno != EAGAIN)) + { + close(fd); + } + else + { + recv_buffer[recv_ret] = 0; + struct http_response *p = + handle_request(recv_buffer, connection_contexts[fd].ip_str, connection_contexts[fd].port); + int ret = send(fd, p->content, p->length, MSG_DONTWAIT); + if ((ret != p->length) && errno == EAGAIN) + { + connection_contexts[fd].response = p; + connection_contexts[fd].bytes_sent = ret; + } + else + { + destroy_http_response(p); + connection_contexts[fd].response = NULL; + connection_contexts[fd].bytes_sent = 0; + } + } + } + else if (events[n].events & EPOLLOUT) + { + // printf("EPOLLOUT\n"); + struct http_response *p = connection_contexts[fd].response; + if (!p) + { + // printf("%d enter continue\n", fd); + continue; + } + int bytes_sent = connection_contexts[fd].bytes_sent; + int ret = send(fd, p->content + bytes_sent, p->length - bytes_sent, MSG_DONTWAIT); + if ((ret != p->length - bytes_sent) && errno == EAGAIN) + { + connection_contexts[fd].bytes_sent += ret; + } + else + { + destroy_http_response(p); + connection_contexts[fd].response = NULL; + connection_contexts[fd].bytes_sent = 0; + } + } + } + } +} +int main(int argc, char *argv[]) +{ + init_log(); + printf("%d workers, use round robin\n", WORKER_COUNT); + struct epoll_event ev, events[MAX_EVENTS]; + int listen_sock, conn_sock, nfds, epollfd; + struct sockaddr_in servaddr; + signal(SIGINT, signal_handler); + /* socket(), bind() and listen() */ + listen_sock = socket(AF_INET, SOCK_STREAM, 0); + bzero(&servaddr, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); + servaddr.sin_port = htons(8080); + int flag = 1; + if (-1 == setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag))) + { + perror("setsockopt"); + exit(EXIT_FAILURE); + } + if (bind(listen_sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) + { + perror("bind"); + exit(EXIT_FAILURE); + } + listen(listen_sock, MAX_CONNECTION); + + /* create master epoll */ + epollfd = epoll_create(32); + if (epollfd == -1) + { + perror("epoll_create"); + exit(EXIT_FAILURE); + } + + ev.events = EPOLLIN; + ev.data.fd = listen_sock; + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) + { + perror("epoll_ctl: listen_sock"); + exit(EXIT_FAILURE); + } + + /* create worker epoll */ + for (int i = 0; i < WORKER_COUNT; i++) + { + worker_epollfd[i] = epoll_create(32); + if (worker_epollfd[i] == -1) + { + perror("epoll_create"); + exit(EXIT_FAILURE); + } + } + + /* create worker threads */ + int s, num_threads; + + num_threads = WORKER_COUNT; + struct thread_info *tinfo = calloc(num_threads, sizeof(*tinfo)); + if (tinfo == NULL) + { + perror("calloc"); + exit(EXIT_FAILURE); + } + + for (int tnum = 0; tnum < num_threads; tnum++) + { + tinfo[tnum].thread_num = tnum; + /* The pthread_create() call stores the thread ID into + corresponding element of tinfo[] */ + s = pthread_create(&tinfo[tnum].thread_id, NULL, &worker, &tinfo[tnum]); + if (s != 0) + { + perror("pthread_create"); + exit(EXIT_FAILURE); + } + } + char ip_str[32]; + unsigned int chosen_worker = 0; + for (;;) + { + nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); + if (nfds == -1) + { + /* EINTR */ + if (errno == EINTR) + { + printf("master received SIGINT, stopping workers\n"); + for (int i = 0; i < WORKER_COUNT; ++i) + { + pthread_kill(tinfo[i].thread_id, SIGINT); + } + /* block until all threads complete */ + for (int i = 0; i < WORKER_COUNT; ++i) + { + pthread_join(tinfo[i].thread_id, NULL); + } + close(listen_sock); + close(epollfd); + exit(EXIT_SUCCESS); + } + else + { + perror("epoll_pwait"); + exit(EXIT_FAILURE); + } + } + + for (int n = 0; n < nfds; ++n) + { + struct sockaddr local; + socklen_t addrlen = sizeof(local); + // use accept4 for SOCK_NONBLOCK + conn_sock = accept4(listen_sock, &local, &addrlen, SOCK_NONBLOCK); + // conn_sock = accept(listen_sock, &local, &addrlen); + if (conn_sock == -1) + { + perror("accept"); + exit(EXIT_FAILURE); + } + ev.events = EPOLLIN | EPOLLOUT | EPOLLET; + ev.data.fd = conn_sock; + // round robin scheduling + chosen_worker = (chosen_worker + 1) % WORKER_COUNT; + get_ip_str(&local, ip_str, sizeof(ip_str)); + // printf("[%d] accept %s, count=%d\n", tid, ip_str, c++); + strcpy(connection_contexts[conn_sock].ip_str, ip_str); + connection_contexts[conn_sock].port = ((struct sockaddr_in *)&local)->sin_port; + if (epoll_ctl(worker_epollfd[chosen_worker], EPOLL_CTL_ADD, conn_sock, &ev) == -1) + { + perror("epoll_ctl: conn_sock"); + exit(EXIT_FAILURE); + } + } + } + return 0; +} diff --git a/visitor.c b/visitor.c new file mode 100644 index 0000000..ef56ac2 --- /dev/null +++ b/visitor.c @@ -0,0 +1,276 @@ +#include "visitor.h" +#include +#include +#include +#include +#include + +// #define _DEBUG + +const char raw_buf_404[] = "" + "404 Not Found" + "

Not Found

The requested URL was not found on the server. I" + "f you entered the URL manually please check your spelling and try again.

"; + +const char raw_type_html[] = "text/html"; + +// path of webroot, no last slash '/' +const char webroot_path[] = "./webroot"; + +const char default_document_name[] = "index.html"; + +int del_raw(RawContent *content) +{ + if (content == NULL) + { + return -1; + } + if (content->raw_buf != NULL) + { + free(content->raw_buf); + content->raw_buf = NULL; + } + if (content->raw_type != NULL) + { + free(content->raw_type); + content->raw_type = NULL; + } + free(content); + return 1; +} + +int is_folder(const char *path, int len) +{ + return path[len - 1] == '/'; +} + +int is_cgi(const char *path, int len) +{ + return len >= 4 && strcmp(path + len - 4, ".php") == 0; +} + +int assign_content_type(RawContent *content, const char *type) +{ + if (content->raw_type != NULL) + { + free(content->raw_type); + content->raw_type = NULL; + } + content->raw_type = (char *)malloc((strlen(type) + 1) * sizeof(char)); + strcpy(content->raw_type, type); + return 1; +} + +int get_content_type(const char *path, RawContent *content) +{ + char *pos = strrchr(path, '/'); + if (pos != NULL) + { + char *file_sufx = strrchr(pos, '.'); +#ifdef _DEBUG + printf("[DEBUG] FileSuffix: %s\n", file_sufx); +#endif + if (file_sufx != NULL) + { + if (strcmp(file_sufx, ".html") == 0) + { + assign_content_type(content, "text/html"); + } + else if (strcmp(file_sufx, ".txt") == 0) + { + assign_content_type(content, "text/plain"); + } + else if (strcmp(file_sufx, ".css") == 0) + { + assign_content_type(content, "text/css"); + } + else if (strcmp(file_sufx, ".js") == 0) + { + assign_content_type(content, "application/x-javascript"); + } + else if (strcmp(file_sufx, ".png") == 0) + { + assign_content_type(content, "image/png"); + } + else if (strcmp(file_sufx, ".jpg") == 0) + { + assign_content_type(content, "image/jpeg"); + } + else if (strcmp(file_sufx, ".jpeg") == 0) + { + assign_content_type(content, "image/jpeg"); + } + else if (strcmp(file_sufx, ".gif") == 0) + { + assign_content_type(content, "image/gif"); + } + else + { + assign_content_type(content, "application/octet-stream"); + } + } + else + { +#ifdef _DEBUG + printf("[DEBUG] NoSuffix\n"); +#endif + assign_content_type(content, "application/octet-stream"); + } + return 1; + } + else + { + return -1; + } +} + +int get_raw_by_fp(FILE *const fp, const char *path, RawContent *content) +{ + // get file size + struct stat stat_buf; + stat(path, &stat_buf); + int len_f = stat_buf.st_size; + // malloc space + content->raw_buf = (char *)malloc((len_f + 1) * sizeof(char)); + content->raw_buf[len_f] = '\0'; + content->raw_len = len_f; + // get content + int n_read = fread(content->raw_buf, sizeof(char), len_f, fp); +#ifdef _DEBUG + printf("[DEBUG] n_read len_f: %d %d\n", n_read, len_f); + printf("[DEBUG] get_raw_by_fp():\n%s\n", content->raw_buf); + assert(n_read == len_f); +#endif + if (n_read != len_f) + return -1; + return 1; +} + +/* +get_raw_by_path() +args: + path: file path requested +retn: + content: type RawContent*, a struct made up as follows: + char* raw_buf : buffer of raw content + int raw_len : length of raw_buf + char* raw_type : content-type of resource + int raw_exist : 1 for found, 0 for not found +*/ +RawContent *get_raw_by_path(const char *path) +{ + int len_path = strlen(path); + int webroot_path_len = strlen(webroot_path); + int default_document_name_length = strlen(default_document_name); + +#ifdef _DEBUG + printf("[DEBUG] path: %s\n", path); + + // ensure valid path length + assert(len_path > 0); + assert(path[0] == '/'); +#endif + + // translate into relative path + // "${webroot_path}/${path}[/${default_document_name}]" + char *new_path = + (char *)malloc((webroot_path_len + len_path + default_document_name_length + 1 + 1 + 1) * sizeof(char)); + strcpy(new_path, webroot_path); + strcat(new_path, "/"); + strcat(new_path, path); + len_path = strlen(new_path); + +#ifdef _DEBUG + printf("[DEBUG] new_path: %s\n", new_path); +#endif + + // declare + RawContent *content = (RawContent *)malloc(sizeof(RawContent)); + memset(content, 0, sizeof(RawContent)); + + // parse path and get content-type + if (is_folder(new_path, len_path)) + { +#ifdef _DEBUG + printf("[DEBUG] PathType: folder\n"); +#endif + strcat(new_path, "/"); + strcat(new_path, default_document_name); + assign_content_type(content, raw_type_html); + } + else if (is_cgi(new_path, len_path)) + { +#ifdef _DEBUG + printf("[DEBUG] PathType: cgi\n"); +#endif + // get_cgi_result(new_path); + } + else + { +#ifdef _DEBUG + printf("[DEBUG] PathType: file\n"); +#endif + get_content_type(new_path, content); +#ifdef _DEBUG + printf("[DEBUG] ContentType: %s\n", content->raw_type); +#endif + } + +#ifdef _DEBUG + printf("[DEBUG] ReqrPath: %s\n", new_path); +#endif + + content->raw_exist = 1; + + // try to open file + FILE *fp = fopen(new_path, "rb"); + if (fp == NULL) + { + // file not found + content->raw_exist = 0; + assign_content_type(content, raw_type_html); + char *default_404_path = (char *)malloc((strlen(webroot_path) + 10) * sizeof(char)); + strcpy(default_404_path, webroot_path); + strcat(default_404_path, "/404.html"); +#ifdef _DEBUG + printf("[DEBUG] 404path: %s\n", default_404_path); +#endif + fp = fopen(default_404_path, "rb"); + if (fp == NULL) + { + // 404 file not exist +#ifdef _DEBUG + printf("[DEBUG] FileRet: 404 default\n"); +#endif + // malloc space + int len_buf_404 = strlen(raw_buf_404); + content->raw_buf = (char *)malloc((len_buf_404 + 1) * sizeof(char)); + content->raw_len = len_buf_404; + strcpy(content->raw_buf, raw_buf_404); + } + else + { +#ifdef _DEBUG + printf("[DEBUG] FileRet: 404 manual\n"); +#endif + get_raw_by_fp(fp, default_404_path, content); + fclose(fp); + } + free(default_404_path); + default_404_path = NULL; + } + else + { +#ifdef _DEBUG + printf("[DEBUG] FileRet: file\n"); +#endif + get_raw_by_fp(fp, new_path, content); +#ifdef _DEBUG + printf("[DEBUG] get_raw_by_path():\n%s\n", content->raw_buf); +#endif + fclose(fp); + } + free(new_path); + new_path = NULL; + return content; +} diff --git a/visitor.h b/visitor.h new file mode 100644 index 0000000..3e2de5e --- /dev/null +++ b/visitor.h @@ -0,0 +1,24 @@ +#ifndef VISITOR_H_ +#define VISITOR_H_ + +#include + +typedef struct rawcontent{ + char* raw_buf; + int raw_len; + char* raw_type; + int raw_exist; +} RawContent; + +RawContent* get_raw_by_path(const char*); +int del_raw(RawContent*); + +int is_folder(const char*, int); +int is_cgi(const char*, int); + +int assign_content_type(RawContent*, const char*); +int get_content_type(const char*, RawContent*); + +int get_raw_by_fp(FILE* const, const char*, RawContent*); + +#endif // VISITOR_H_ diff --git a/webroot/400.html b/webroot/400.html new file mode 100644 index 0000000..7b48670 --- /dev/null +++ b/webroot/400.html @@ -0,0 +1,7 @@ + + 400 Bad Request + +

400 Bad Request

+
naivehttpd/0.1
+ + \ No newline at end of file diff --git a/webroot/404.html b/webroot/404.html new file mode 100644 index 0000000..b33cdc2 --- /dev/null +++ b/webroot/404.html @@ -0,0 +1,7 @@ + + 404 Not Found + +

404 Not Found

+
naivehttpd/0.1
+ + \ No newline at end of file diff --git a/webroot/cgi-bin/400.php b/webroot/cgi-bin/400.php new file mode 100644 index 0000000..f6e92ef --- /dev/null +++ b/webroot/cgi-bin/400.php @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/webroot/cgi-bin/adminer.php b/webroot/cgi-bin/adminer.php new file mode 100644 index 0000000..762aaaf --- /dev/null +++ b/webroot/cgi-bin/adminer.php @@ -0,0 +1,1795 @@ +$W){unset($tg[$z][$he]);if(is_array($W)){$tg[$z][stripslashes($he)]=$W;$tg[]=&$tg[$z][stripslashes($he)];}else$tg[$z][stripslashes($he)]=($ad?$W:stripslashes($W));}}}}function +bracket_escape($v,$Na=false){static$ui=array(':'=>':1',']'=>':2','['=>':3','"'=>':4');return +strtr($v,($Na?array_flip($ui):$ui));}function +min_version($Zi,$De="",$h=null){global$g;if(!$h)$h=$g;$nh=$h->server_info;if($De&&preg_match('~([\d.]+)-MariaDB~',$nh,$C)){$nh=$C[1];$Zi=$De;}return(version_compare($nh,$Zi)>=0);}function +charset($g){return(min_version("5.5.3",0,$g)?"utf8mb4":"utf8");}function +script($yh,$ti="\n"){return"$yh$ti";}function +script_src($Ni){return"\n";}function +nonce(){return' nonce="'.get_nonce().'"';}function +target_blank(){return' target="_blank" rel="noreferrer noopener"';}function +h($P){return +str_replace("\0","�",htmlspecialchars($P,ENT_QUOTES,'utf-8'));}function +nl_br($P){return +str_replace("\n","
",$P);}function +checkbox($D,$Y,$db,$me="",$uf="",$hb="",$ne=""){$I="".($uf?script("qsl('input').onclick = function () { $uf };",""):"");return($me!=""||$hb?"$I".h($me)."":$I);}function +optionlist($_f,$gh=null,$Ri=false){$I="";foreach($_f +as$he=>$W){$Af=array($he=>$W);if(is_array($W)){$I.='';$Af=$W;}foreach($Af +as$z=>$X)$I.=''.h($X);if(is_array($W))$I.='';}return$I;}function +html_select($D,$_f,$Y="",$tf=true,$ne=""){if($tf)return"".(is_string($tf)?script("qsl('select').onchange = function () { $tf };",""):"");$I="";foreach($_f +as$z=>$X)$I.="";return$I;}function +select_input($Ia,$_f,$Y="",$tf="",$fg=""){$Yh=($_f?"select":"input");return"<$Yh$Ia".($_f?">