summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CGIScript.cpp69
-rw-r--r--src/CGIScript.h25
-rw-r--r--src/ClientConnection.cpp47
-rw-r--r--src/ClientConnection.h18
-rw-r--r--src/HttpRequest.cpp28
-rw-r--r--src/HttpRequest.h16
-rw-r--r--src/HttpResponse.cpp55
-rw-r--r--src/HttpResponse.h30
-rw-r--r--src/ServerConnection.cpp46
-rw-r--r--src/ServerConnection.h13
-rw-r--r--src/main.cpp156
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();
- }
-}