diff --git a/Base/www/index.html b/Base/www/index.html new file mode 100644 index 00000000000..de0f06a9f3e --- /dev/null +++ b/Base/www/index.html @@ -0,0 +1,18 @@ + + + WebServer start page! + +

SerenityOS WebServer start page!

+

+ Congratulations! +

+

+ If you are reading this in a browser, I think we may have + succeeded! Unless of course you just loaded the local file + in your browser (cheater!) :^) +

+

+ Try this link to another page! +

+ + diff --git a/Base/www/other.html b/Base/www/other.html new file mode 100644 index 00000000000..c508ccefc1a --- /dev/null +++ b/Base/www/other.html @@ -0,0 +1,16 @@ + + + WebServer other page! + +

SerenityOS WebServer other page!

+

+ Holy moly! +

+

+ This is not even index.html, how neat is that! :^) +

+

+ Maybe you want to go back to index.html? +

+ + diff --git a/Kernel/build-root-filesystem.sh b/Kernel/build-root-filesystem.sh index d1177f5a818..837b8d04d99 100755 --- a/Kernel/build-root-filesystem.sh +++ b/Kernel/build-root-filesystem.sh @@ -148,6 +148,7 @@ cp ../Servers/AudioServer/AudioServer mnt/bin/AudioServer cp ../Servers/TTYServer/TTYServer mnt/bin/TTYServer cp ../Servers/TelnetServer/TelnetServer mnt/bin/TelnetServer cp ../Servers/ProtocolServer/ProtocolServer mnt/bin/ProtocolServer +cp ../Servers/WebServer/WebServer mnt/bin/WebServer cp ../Shell/Shell mnt/bin/Shell cp ../MenuApplets/Audio/Audio.MenuApplet mnt/bin/ cp ../MenuApplets/CPUGraph/CPUGraph.MenuApplet mnt/bin/ @@ -185,6 +186,7 @@ ln -s Browser mnt/bin/br ln -s HackStudio mnt/bin/hs ln -s SystemMonitor mnt/bin/sm ln -s ProfileViewer mnt/bin/pv +ln -s WebServer mnt/bin/ws echo "done" mkdir -p mnt/boot/ diff --git a/Kernel/run b/Kernel/run index b5f8c9e8b48..7ff8e0ed430 100755 --- a/Kernel/run +++ b/Kernel/run @@ -111,7 +111,7 @@ else $SERENITY_COMMON_QEMU_ARGS \ $SERENITY_KVM_ARG \ $SERENITY_PACKET_LOGGING_ARG \ - -netdev user,id=breh,hostfwd=tcp:127.0.0.1:8888-10.0.2.15:8888,hostfwd=tcp:127.0.0.1:8823-10.0.2.15:23 \ + -netdev user,id=breh,hostfwd=tcp:127.0.0.1:8888-10.0.2.15:8888,hostfwd=tcp:127.0.0.1:8823-10.0.2.15:23,hostfwd=tcp:127.0.0.1:8000-10.0.2.15:8000 \ -device e1000,netdev=breh \ -kernel kernel \ -append "${SERENITY_KERNEL_CMDLINE}" diff --git a/Servers/WebServer/Client.cpp b/Servers/WebServer/Client.cpp new file mode 100644 index 00000000000..90f9eeeb6d8 --- /dev/null +++ b/Servers/WebServer/Client.cpp @@ -0,0 +1,101 @@ +#include "Client.h" +#include +#include +#include +#include +#include +#include + +namespace WebServer { + +Client::Client(NonnullRefPtr socket, Core::Object* parent) + : Core::Object(parent) + , m_socket(socket) +{ +} + +void Client::die() +{ + remove_from_parent(); +} + +void Client::start() +{ + m_socket->on_ready_to_read = [this] { + auto raw_request = m_socket->read_all(); + if (raw_request.is_null()) { + die(); + return; + } + + dbg() << "Got raw request: '" << String::copy(raw_request) << "'"; + + handle_request(move(raw_request)); + die(); + }; +} + +void Client::handle_request(ByteBuffer raw_request) +{ + auto request_or_error = Core::HttpRequest::from_raw_request(raw_request); + if (!request_or_error.has_value()) + return; + auto& request = request_or_error.value(); + + dbg() << "Got HTTP request: " << request.method_name() << " " << request.resource(); + for (auto& header : request.headers()) { + dbg() << " " << header.name << " => " << header.value; + } + + if (request.method() != Core::HttpRequest::Method::GET) { + send_error_response(403, "Forbidden, bro!", request); + return; + } + + FileSystemPath canonical_path(request.resource()); + dbg() << "Requested canonical path: '" << canonical_path.string() << "'"; + + StringBuilder path_builder; + path_builder.append("/www/"); + path_builder.append(canonical_path.string()); + + if (Core::File::is_directory(path_builder.to_string())) + path_builder.append("/index.html"); + + auto file = Core::File::construct(path_builder.to_string()); + if (!file->open(Core::File::ReadOnly)) { + send_error_response(404, "Not found, bro!", request); + return; + } + + m_socket->write("HTTP/1.0 200 OK\r\n"); + m_socket->write("Server: WebServer (SerenityOS)\r\n"); + m_socket->write("Content-Type: text/html\r\n"); + m_socket->write("\r\n"); + + m_socket->write(file->read_all()); + + log_response(200, request); +} + +void Client::send_error_response(unsigned code, const StringView& message, const Core::HttpRequest& request) +{ + StringBuilder builder; + builder.appendf("HTTP/1.0 %u", code); + builder.append(message); + builder.append("\r\n\r\n"); + builder.append("

"); + builder.appendf("%u ", code); + builder.append(message); + builder.append("

"); + m_socket->write(builder.to_string()); + + log_response(code, request); +} + +void Client::log_response(unsigned code, const Core::HttpRequest& request) +{ + printf("%lld :: %03u :: %s %s\n", time(nullptr), code, request.method_name().characters(), request.resource().characters()); +} + +} diff --git a/Servers/WebServer/Client.h b/Servers/WebServer/Client.h new file mode 100644 index 00000000000..c8d5ec3d3ff --- /dev/null +++ b/Servers/WebServer/Client.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +namespace Core { +class HttpRequest; +} + +namespace WebServer { + +class Client final : public Core::Object { + C_OBJECT(Client); +public: + void start(); + +private: + Client(NonnullRefPtr, Core::Object* parent); + + void handle_request(ByteBuffer); + void send_error_response(unsigned code, const StringView& message, const Core::HttpRequest&); + void die(); + void log_response(unsigned code, const Core::HttpRequest&); + + NonnullRefPtr m_socket; +}; + +} diff --git a/Servers/WebServer/Makefile b/Servers/WebServer/Makefile new file mode 100644 index 00000000000..db88ad0679d --- /dev/null +++ b/Servers/WebServer/Makefile @@ -0,0 +1,9 @@ +OBJS = \ + Client.o \ + main.o + +PROGRAM = WebServer + +LIB_DEPS = Core + +include ../../Makefile.common diff --git a/Servers/WebServer/main.cpp b/Servers/WebServer/main.cpp new file mode 100644 index 00000000000..36a207309c3 --- /dev/null +++ b/Servers/WebServer/main.cpp @@ -0,0 +1,23 @@ +#include "Client.h" +#include +#include + +int main(int argc, char** argv) +{ + (void)argc; + (void)argv; + + Core::EventLoop loop; + + auto server = Core::TCPServer::construct(); + + server->on_ready_to_accept = [&] { + auto client_socket = server->accept(); + ASSERT(client_socket); + auto client = WebServer::Client::construct(client_socket.release_nonnull(), server); + client->start(); + }; + + server->listen({}, 8000); + return loop.exec(); +}