diff options
author | cflip <cflip@cflip.net> | 2023-05-08 21:25:21 -0600 |
---|---|---|
committer | cflip <cflip@cflip.net> | 2023-05-08 21:25:21 -0600 |
commit | 7f1d6bbc335288df1e24e7c8f305c32afe6b050a (patch) | |
tree | 3ca1784ab73315e44dd9e03b2f0e244a59158fbc /src | |
parent | 83fb0b96c94e7f596f81d5bc346150904457ed64 (diff) |
Begin rewriting cfws in C
Diffstat (limited to 'src')
-rw-r--r-- | src/CGIScript.cpp | 69 | ||||
-rw-r--r-- | src/CGIScript.h | 25 | ||||
-rw-r--r-- | src/ClientConnection.cpp | 47 | ||||
-rw-r--r-- | src/ClientConnection.h | 18 | ||||
-rw-r--r-- | src/HttpRequest.cpp | 28 | ||||
-rw-r--r-- | src/HttpRequest.h | 16 | ||||
-rw-r--r-- | src/HttpResponse.cpp | 55 | ||||
-rw-r--r-- | src/HttpResponse.h | 30 | ||||
-rw-r--r-- | src/ServerConnection.cpp | 46 | ||||
-rw-r--r-- | src/ServerConnection.h | 13 | ||||
-rw-r--r-- | src/main.cpp | 156 |
11 files changed, 0 insertions, 503 deletions
diff --git a/src/CGIScript.cpp b/src/CGIScript.cpp deleted file mode 100644 index 3ef1f3f..0000000 --- a/src/CGIScript.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "CGIScript.h" - -#include <cstdlib> -#include <filesystem> -#include <iostream> -#include <sstream> -#include <string> -#include <unistd.h> - -CGIScript::CGIScript(const std::string& script_path) - : m_script_path(script_path) -{ - set_environment("SCRIPT_NAME", script_path.c_str()); - set_environment("SCRIPT_FILENAME", script_path.c_str()); - set_environment("SERVER_PROTOCOL", "HTTP/1.1"); - set_environment("SERVER_SOFTWARE", "cfws"); -} - -CGIScript::~CGIScript() -{ - pclose(m_pipe); - - for (const auto* key : m_environment_variables) - unsetenv(key); -} - -void CGIScript::set_environment(const char* key, const char* value) -{ - m_environment_variables.push_back(key); - setenv(key, value, 1); -} - -bool CGIScript::open() -{ - m_pipe = popen(m_script_path.c_str(), "r"); - if (m_pipe == nullptr) { - perror("cfws: popen"); - return false; - } - - m_is_open = true; - return true; -} - -std::string CGIScript::read_output() -{ - std::stringstream sstream; - - char ch = 0; - while ((ch = fgetc(m_pipe)) != EOF) - sstream << ch; - - return sstream.str(); -} - -void CGIScript::validate_path(const std::string& script_path) -{ - namespace fs = std::filesystem; - - if (!fs::exists(script_path)) { - std::cerr << "cfws: Script not found: " << script_path << std::endl; - exit(1); - } - - if (access(script_path.c_str(), X_OK)) { - std::cerr << "cfws: Script does not have execute permissions: " << script_path << std::endl; - exit(1); - } -} diff --git a/src/CGIScript.h b/src/CGIScript.h deleted file mode 100644 index 3ac00d9..0000000 --- a/src/CGIScript.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include <cstdio> -#include <string> -#include <vector> - -class CGIScript { -public: - CGIScript(const std::string& script_path); - ~CGIScript(); - - void set_environment(const char* key, const char* value); - bool open(); - - std::string read_output(); - - static void validate_path(const std::string& path); - -private: - FILE* m_pipe {}; - const std::string& m_script_path; - bool m_is_open { false }; - - std::vector<const char*> m_environment_variables; -}; diff --git a/src/ClientConnection.cpp b/src/ClientConnection.cpp deleted file mode 100644 index c434212..0000000 --- a/src/ClientConnection.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "ClientConnection.h" - -#include <cstring> -#include <iostream> -#include <sstream> -#include <unistd.h> - -ClientConnection::ClientConnection(int socket) - : m_socket_fd(socket) -{ -} - -HttpRequest ClientConnection::read_request() const -{ - // TODO: Clean up this code to ensure it works with multiple lines - // and not risk a buffer overflow. - constexpr int BUFFER_SIZE = 4096; - char buffer[BUFFER_SIZE + 1]; - int n = 0; - - memset(buffer, 0, BUFFER_SIZE); - while ((n = read(m_socket_fd, buffer, BUFFER_SIZE - 1)) > 0) { - if (buffer[n - 1] == '\n') - break; - - memset(buffer, 0, BUFFER_SIZE); - } - - return HttpRequest(buffer); -} - -bool ClientConnection::send(const HttpResponse& response) const -{ - if (!m_is_open) - return false; - - std::string result = response.to_string(); - write(m_socket_fd, result.c_str(), result.length()); - - return true; -} - -void ClientConnection::close_connection() -{ - m_is_open = false; - close(m_socket_fd); -} diff --git a/src/ClientConnection.h b/src/ClientConnection.h deleted file mode 100644 index fee0aeb..0000000 --- a/src/ClientConnection.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "HttpRequest.h" -#include "HttpResponse.h" - -class ClientConnection { -public: - ClientConnection(int socket); - - HttpRequest read_request() const; - - bool send(const HttpResponse&) const; - void close_connection(); - -private: - int m_socket_fd; - bool m_is_open { true }; -}; diff --git a/src/HttpRequest.cpp b/src/HttpRequest.cpp deleted file mode 100644 index 37c85fd..0000000 --- a/src/HttpRequest.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "HttpRequest.h" - -#include <iostream> - -HttpRequest::HttpRequest(const std::string& request_string) -{ - size_t pos = 0; - std::string s = request_string; - std::string line; - while ((pos = s.find("\r\n")) != std::string::npos) { - line = s.substr(0, pos); - - if (line.find("GET ") != std::string::npos) { - m_uri = s.substr(4, line.find(' ', 5) - 4); - std::cout << m_uri << std::endl; - } - - // If the line contains a colon, we assume it's a header. - // TODO: This may not always be the case. - size_t delim_pos = 0; - if ((delim_pos = line.find(':')) != std::string::npos) { - std::string header_key = s.substr(0, delim_pos); - std::string header_value = s.substr(delim_pos + 2, s.find("\r\n") - delim_pos - 2); - m_headers[header_key] = header_value; - } - s.erase(0, pos + 2); - } -} diff --git a/src/HttpRequest.h b/src/HttpRequest.h deleted file mode 100644 index cb98bea..0000000 --- a/src/HttpRequest.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include <map> -#include <string> - -class HttpRequest { -public: - HttpRequest(const std::string& request_string); - - std::string uri() const { return m_uri; } - std::string header(const std::string& header_key) const { return m_headers.at(header_key); }; - -private: - std::map<std::string, std::string> m_headers; - std::string m_uri; -}; diff --git a/src/HttpResponse.cpp b/src/HttpResponse.cpp deleted file mode 100644 index c8afc63..0000000 --- a/src/HttpResponse.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "HttpResponse.h" - -#include <sstream> - -void HttpResponse::add_headers_and_content(const std::string& input) -{ - bool is_parsing_headers = true; - - size_t pos = 0; - std::string s = input; - std::string line; - while ((pos = s.find('\n')) != std::string::npos) { - line = s.substr(0, pos + 1); - - if (is_parsing_headers) { - size_t delim_pos = 0; - if ((delim_pos = line.find(':')) != std::string::npos) { - std::string header_key = s.substr(0, delim_pos); - std::string header_value = s.substr(delim_pos + 2, s.find('\n') - delim_pos - 2); - m_headers[header_key] = header_value; - } else { - is_parsing_headers = false; - } - } else { - m_content += line; - } - s.erase(0, pos + 1); - } -} - -static std::string status_code_string(HttpStatusCode status_code) -{ - switch (status_code) { - case HttpStatusCode::OK: - return "200 OK"; - case HttpStatusCode::Forbidden: - return "403 Forbidden"; - case HttpStatusCode::NotFound: - return "404 Not Found"; - case HttpStatusCode::InternalServerError: - return "500 Internal Server Error"; - } -} - -std::string HttpResponse::to_string() const -{ - std::stringstream string_stream; - string_stream << "HTTP/1.0 " << status_code_string(m_status_code) << "\r\n"; - for (const auto& header : m_headers) - string_stream << header.first << ": " << header.second << "\r\n"; - string_stream << "\r\n" - << m_content; - - return string_stream.str(); -} diff --git a/src/HttpResponse.h b/src/HttpResponse.h deleted file mode 100644 index b55e01a..0000000 --- a/src/HttpResponse.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include <map> -#include <string> - -enum class HttpStatusCode { - OK = 200, - Forbidden = 403, - NotFound = 404, - InternalServerError = 500, -}; - -class HttpResponse { -public: - void add_header(const std::string& header, const std::string& value) - { - m_headers[header] = value; - } - - void set_status_code(HttpStatusCode status_code) { m_status_code = status_code; } - void set_content(const std::string& content) { m_content = content; } - void add_headers_and_content(const std::string&); - - std::string to_string() const; - -private: - HttpStatusCode m_status_code; - std::map<std::string, std::string> m_headers; - std::string m_content; -}; diff --git a/src/ServerConnection.cpp b/src/ServerConnection.cpp deleted file mode 100644 index 64692b6..0000000 --- a/src/ServerConnection.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "ServerConnection.h" - -#include <arpa/inet.h> -#include <csignal> -#include <netdb.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <sys/time.h> -#include <sys/types.h> -#include <unistd.h> - -#include "ClientConnection.h" - -static void error_and_die(const char* message) -{ - perror(message); - exit(1); -} - -ServerConnection::ServerConnection(int port) -{ - sockaddr_in address {}; - int socket_options = 1; - - if ((m_socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) - error_and_die("Failed to create socket"); - - if (setsockopt(m_socket_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &socket_options, sizeof(socket_options)) != 0) - error_and_die("setsockopt"); - - address.sin_family = AF_INET; - address.sin_addr.s_addr = htonl(INADDR_ANY); - address.sin_port = htons(port); - - if ((bind(m_socket_fd, (sockaddr*)&address, sizeof(address))) < 0) - error_and_die("bind"); - - if ((listen(m_socket_fd, 10)) < 0) - error_and_die("listen"); -} - -ClientConnection ServerConnection::accept_client_connection() const -{ - int client_socket = accept(m_socket_fd, (sockaddr*)nullptr, nullptr); - return { client_socket }; -} diff --git a/src/ServerConnection.h b/src/ServerConnection.h deleted file mode 100644 index 8f0af70..0000000 --- a/src/ServerConnection.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -class ClientConnection; - -class ServerConnection { -public: - ServerConnection(int port); - - ClientConnection accept_client_connection() const; - -private: - int m_socket_fd; -}; diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index c0c9bb3..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,156 +0,0 @@ -#include <getopt.h> - -#include <filesystem> -#include <fstream> -#include <iostream> -#include <sstream> - -#include "CGIScript.h" -#include "ClientConnection.h" -#include "HttpRequest.h" -#include "HttpResponse.h" -#include "ServerConnection.h" - -static std::string content_type_for_extension(const std::filesystem::path& extension) -{ - if (extension == ".html") - return "text/html"; - if (extension == ".css") - return "text/css"; - if (extension == ".gif") - return "image/gif"; - if (extension == ".png") - return "image/png"; - if (extension == ".webp") - return "image/webp"; - return "text/plain"; -} - -static HttpResponse serve_from_filesystem(const HttpRequest& request) -{ - namespace fs = std::filesystem; - - HttpResponse response; - response.add_header("Server", "cfws"); - - // Remove leading slash from the path if it exists - std::string relative_request_path = request.uri(); - while (*relative_request_path.begin() == '/') - relative_request_path.erase(0, 1); - - fs::path request_path = fs::current_path() / relative_request_path; - - // Look for an index.html if the requested path is a directory - if (fs::is_directory(request_path)) { - request_path /= "index.html"; - } - - if (fs::exists(request_path)) { - std::string file_path = request_path.string(); - std::ifstream input_file(file_path); - if (!input_file.is_open()) { - std::cerr << "Failed to open file " << file_path << std::endl; - } - - std::string file_contents((std::istreambuf_iterator<char>(input_file)), std::istreambuf_iterator<char>()); - - response.set_status_code(HttpStatusCode::OK); - response.add_header("Content-Type", content_type_for_extension(request_path.extension())); - response.set_content(file_contents); - } else { - response.set_status_code(HttpStatusCode::NotFound); - response.add_header("Content-Type", "text/plain"); - response.set_content("Page not found!"); - } - - return response; -} - -static HttpResponse serve_from_cgi(const std::string& script_path, const HttpRequest& request) -{ - HttpResponse response; - response.add_header("Server", "cfws"); - - // Split URI between the path and the query parameter string - std::stringstream uri_stream(request.uri()); - std::string segment; - std::vector<std::string> segment_list; - - while (std::getline(uri_stream, segment, '?')) { - segment_list.push_back(segment); - } - - CGIScript script(script_path); - script.set_environment("REQUEST_METHOD", "GET"); - script.set_environment("REQUEST_URI", request.uri().c_str()); - script.set_environment("PATH_INFO", segment_list[0].c_str()); - if (segment_list.size() > 1) - script.set_environment("QUERY_STRING", segment_list[1].c_str()); - script.set_environment("CONTENT_LENGTH", "0"); - - if (!script.open()) { - response.set_status_code(HttpStatusCode::InternalServerError); - response.add_header("Content-Type", "text/plain"); - response.set_content("Failed to open CGI script!"); - return response; - } - - response.set_status_code(HttpStatusCode::OK); - response.add_headers_and_content(script.read_output()); - return response; -} - -static option long_options[] = { - { "cgi", required_argument, nullptr, 'c' }, - { "port", required_argument, nullptr, 'p' }, -}; - -int main(int argc, char** argv) -{ - int port = 8080; - bool in_cgi_mode = false; - std::string cgi_program_name; - - int c = 0; - int option_index = 0; - while ((c = getopt_long(argc, argv, "c:p:", long_options, &option_index)) != -1) { - switch (c) { - case 'c': - in_cgi_mode = true; - cgi_program_name = optarg; - break; - case 'p': - port = atoi(optarg); - if (port == 0) { - std::cerr << "cfws: Specified port is not a valid number" << std::endl; - exit(1); - } - break; - default: - break; - } - } - - // Check the script path to ensure that it is a valid executable - // script before attempting to start the server. - if (in_cgi_mode) - CGIScript::validate_path(cgi_program_name); - - ServerConnection server(port); - std::cout << "Serving a " << (in_cgi_mode ? "CGI script" : "directory") << " on port " << port << std::endl; - - while (true) { - ClientConnection client = server.accept_client_connection(); - HttpRequest request = client.read_request(); - HttpResponse response; - - if (in_cgi_mode) { - response = serve_from_cgi(cgi_program_name, request); - } else { - response = serve_from_filesystem(request); - } - - client.send(response); - client.close_connection(); - } -} |