Serenity Operating System
at master 110 lines 4.2 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2021, Max Wipfli <mail@maxwipfli.ch> 4 * Copyright (c) 2022, Thomas Keppler <serenity@tkeppler.de> 5 * 6 * SPDX-License-Identifier: BSD-2-Clause 7 */ 8 9#include <AK/String.h> 10#include <LibCore/ArgsParser.h> 11#include <LibCore/DeprecatedFile.h> 12#include <LibCore/EventLoop.h> 13#include <LibCore/MappedFile.h> 14#include <LibCore/System.h> 15#include <LibCore/TCPServer.h> 16#include <LibHTTP/HttpRequest.h> 17#include <LibMain/Main.h> 18#include <WebServer/Client.h> 19#include <WebServer/Configuration.h> 20#include <stdio.h> 21#include <unistd.h> 22 23ErrorOr<int> serenity_main(Main::Arguments arguments) 24{ 25 static auto const default_listen_address = TRY("0.0.0.0"_string); 26 static auto const default_port = 8000; 27 static auto const default_document_root_path = TRY("/www"_string); 28 29 DeprecatedString listen_address = default_listen_address.to_deprecated_string(); 30 int port = default_port; 31 DeprecatedString username; 32 DeprecatedString password; 33 DeprecatedString document_root_path = default_document_root_path.to_deprecated_string(); 34 35 Core::ArgsParser args_parser; 36 args_parser.add_option(listen_address, "IP address to listen on", "listen-address", 'l', "listen_address"); 37 args_parser.add_option(port, "Port to listen on", "port", 'p', "port"); 38 args_parser.add_option(username, "HTTP basic authentication username", "user", 'U', "username"); 39 args_parser.add_option(password, "HTTP basic authentication password", "pass", 'P', "password"); 40 args_parser.add_positional_argument(document_root_path, "Path to serve the contents of", "path", Core::ArgsParser::Required::No); 41 args_parser.parse(arguments); 42 43 auto ipv4_address = IPv4Address::from_string(listen_address); 44 if (!ipv4_address.has_value()) { 45 warnln("Invalid listen address: {}", listen_address); 46 return 1; 47 } 48 49 if ((u16)port != port) { 50 warnln("Invalid port number: {}", port); 51 return 1; 52 } 53 54 if (username.is_empty() != password.is_empty()) { 55 warnln("Both username and password are required for HTTP basic authentication."); 56 return 1; 57 } 58 59 auto real_document_root_path = Core::DeprecatedFile::real_path_for(document_root_path); 60 if (!Core::DeprecatedFile::exists(real_document_root_path)) { 61 warnln("Root path does not exist: '{}'", document_root_path); 62 return 1; 63 } 64 65 TRY(Core::System::pledge("stdio accept rpath inet unix")); 66 67 Optional<HTTP::HttpRequest::BasicAuthenticationCredentials> credentials; 68 if (!username.is_empty() && !password.is_empty()) 69 credentials = HTTP::HttpRequest::BasicAuthenticationCredentials { username, password }; 70 71 WebServer::Configuration configuration(real_document_root_path, credentials); 72 73 Core::EventLoop loop; 74 75 auto server = TRY(Core::TCPServer::try_create()); 76 77 server->on_ready_to_accept = [&] { 78 auto maybe_client_socket = server->accept(); 79 if (maybe_client_socket.is_error()) { 80 warnln("Failed to accept the client: {}", maybe_client_socket.error()); 81 return; 82 } 83 84 auto maybe_buffered_socket = Core::BufferedTCPSocket::create(maybe_client_socket.release_value()); 85 if (maybe_buffered_socket.is_error()) { 86 warnln("Could not obtain a buffered socket for the client: {}", maybe_buffered_socket.error()); 87 return; 88 } 89 90 // FIXME: Propagate errors 91 MUST(maybe_buffered_socket.value()->set_blocking(true)); 92 auto client = WebServer::Client::construct(maybe_buffered_socket.release_value(), server); 93 client->start(); 94 }; 95 96 TRY(server->listen(ipv4_address.value(), port)); 97 98 out("Listening on "); 99 out("\033]8;;http://{}:{}\033\\", ipv4_address.value(), port); 100 out("{}:{}", ipv4_address.value(), port); 101 outln("\033]8;;\033\\"); 102 103 TRY(Core::System::unveil("/etc/timezone", "r")); 104 TRY(Core::System::unveil("/res/icons", "r")); 105 TRY(Core::System::unveil(real_document_root_path, "r"sv)); 106 TRY(Core::System::unveil(nullptr, nullptr)); 107 108 TRY(Core::System::pledge("stdio accept rpath")); 109 return loop.exec(); 110}