diff options
author | Andreas Kling <kling@serenityos.org> | 2021-01-12 12:23:01 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-01-12 12:23:01 +0100 |
commit | c7ac7e6eaff862c1ea4f99e0c6ce75e095106d9c (patch) | |
tree | ce2a3fef96f0b8eebe907743f1e03739457c11a6 /Userland/Services/LaunchServer | |
parent | 4055b0329117c1a280080bbd638eb48bafe29638 (diff) | |
download | serenity-c7ac7e6eaff862c1ea4f99e0c6ce75e095106d9c.zip |
Services: Move to Userland/Services/
Diffstat (limited to 'Userland/Services/LaunchServer')
-rw-r--r-- | Userland/Services/LaunchServer/CMakeLists.txt | 13 | ||||
-rw-r--r-- | Userland/Services/LaunchServer/ClientConnection.cpp | 159 | ||||
-rw-r--r-- | Userland/Services/LaunchServer/ClientConnection.h | 64 | ||||
-rw-r--r-- | Userland/Services/LaunchServer/LaunchClient.ipc | 4 | ||||
-rw-r--r-- | Userland/Services/LaunchServer/LaunchServer.ipc | 12 | ||||
-rw-r--r-- | Userland/Services/LaunchServer/Launcher.cpp | 309 | ||||
-rw-r--r-- | Userland/Services/LaunchServer/Launcher.h | 78 | ||||
-rw-r--r-- | Userland/Services/LaunchServer/main.cpp | 65 |
8 files changed, 704 insertions, 0 deletions
diff --git a/Userland/Services/LaunchServer/CMakeLists.txt b/Userland/Services/LaunchServer/CMakeLists.txt new file mode 100644 index 0000000000..29308ce7f1 --- /dev/null +++ b/Userland/Services/LaunchServer/CMakeLists.txt @@ -0,0 +1,13 @@ +compile_ipc(LaunchServer.ipc LaunchServerEndpoint.h) +compile_ipc(LaunchClient.ipc LaunchClientEndpoint.h) + +set(SOURCES + ClientConnection.cpp + Launcher.cpp + main.cpp + LaunchClientEndpoint.h + LaunchServerEndpoint.h +) + +serenity_bin(LaunchServer) +target_link_libraries(LaunchServer LibCore LibIPC LibDesktop) diff --git a/Userland/Services/LaunchServer/ClientConnection.cpp b/Userland/Services/LaunchServer/ClientConnection.cpp new file mode 100644 index 0000000000..7ac167c1ae --- /dev/null +++ b/Userland/Services/LaunchServer/ClientConnection.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2020, Nicholas Hollett <niax@niax.co.uk> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ClientConnection.h" +#include "Launcher.h" +#include <AK/HashMap.h> +#include <AK/URL.h> +#include <LaunchServer/LaunchClientEndpoint.h> + +namespace LaunchServer { + +static HashMap<int, RefPtr<ClientConnection>> s_connections; +ClientConnection::ClientConnection(NonnullRefPtr<Core::LocalSocket> client_socket, int client_id) + : IPC::ClientConnection<LaunchClientEndpoint, LaunchServerEndpoint>(*this, move(client_socket), client_id) +{ + s_connections.set(client_id, *this); +} + +ClientConnection::~ClientConnection() +{ +} + +void ClientConnection::die() +{ + s_connections.remove(client_id()); +} + +OwnPtr<Messages::LaunchServer::GreetResponse> ClientConnection::handle(const Messages::LaunchServer::Greet&) +{ + return make<Messages::LaunchServer::GreetResponse>(client_id()); +} + +OwnPtr<Messages::LaunchServer::OpenURLResponse> ClientConnection::handle(const Messages::LaunchServer::OpenURL& request) +{ + if (!m_allowlist.is_empty()) { + bool allowed = false; + for (auto& allowed_handler : m_allowlist) { + if (allowed_handler.handler_name == request.handler_name() + && (allowed_handler.any_url || allowed_handler.urls.contains_slow(request.url()))) { + allowed = true; + break; + } + } + if (!allowed) { + // You are not on the list, go home! + did_misbehave(String::formatted("Client requested a combination of handler/URL that was not on the list: '{}' with '{}'", request.handler_name(), request.url()).characters()); + return {}; + } + } + + URL url(request.url()); + auto result = Launcher::the().open_url(url, request.handler_name()); + return make<Messages::LaunchServer::OpenURLResponse>(result); +} + +OwnPtr<Messages::LaunchServer::GetHandlersForURLResponse> ClientConnection::handle(const Messages::LaunchServer::GetHandlersForURL& request) +{ + URL url(request.url()); + auto result = Launcher::the().handlers_for_url(url); + return make<Messages::LaunchServer::GetHandlersForURLResponse>(result); +} + +OwnPtr<Messages::LaunchServer::GetHandlersWithDetailsForURLResponse> ClientConnection::handle(const Messages::LaunchServer::GetHandlersWithDetailsForURL& request) +{ + URL url(request.url()); + auto result = Launcher::the().handlers_with_details_for_url(url); + return make<Messages::LaunchServer::GetHandlersWithDetailsForURLResponse>(result); +} + +OwnPtr<Messages::LaunchServer::AddAllowedURLResponse> ClientConnection::handle(const Messages::LaunchServer::AddAllowedURL& request) +{ + if (m_allowlist_is_sealed) { + did_misbehave("Got request to add more allowed handlers after list was sealed"); + return {}; + } + + if (!request.url().is_valid()) { + did_misbehave("Got request to allow invalid URL"); + return {}; + } + + m_allowlist.empend(String(), false, Vector<URL> { request.url() }); + + return make<Messages::LaunchServer::AddAllowedURLResponse>(); +} + +OwnPtr<Messages::LaunchServer::AddAllowedHandlerWithAnyURLResponse> ClientConnection::handle(const Messages::LaunchServer::AddAllowedHandlerWithAnyURL& request) +{ + if (m_allowlist_is_sealed) { + did_misbehave("Got request to add more allowed handlers after list was sealed"); + return {}; + } + + if (request.handler_name().is_empty()) { + did_misbehave("Got request to allow empty handler name"); + return {}; + } + + m_allowlist.empend(request.handler_name(), true, Vector<URL>()); + + return make<Messages::LaunchServer::AddAllowedHandlerWithAnyURLResponse>(); +} + +OwnPtr<Messages::LaunchServer::AddAllowedHandlerWithOnlySpecificURLsResponse> ClientConnection::handle(const Messages::LaunchServer::AddAllowedHandlerWithOnlySpecificURLs& request) +{ + if (m_allowlist_is_sealed) { + did_misbehave("Got request to add more allowed handlers after list was sealed"); + return {}; + } + + if (request.handler_name().is_empty()) { + did_misbehave("Got request to allow empty handler name"); + return {}; + } + + if (request.urls().is_empty()) { + did_misbehave("Got request to allow empty URL list"); + return {}; + } + + m_allowlist.empend(request.handler_name(), false, request.urls()); + + return make<Messages::LaunchServer::AddAllowedHandlerWithOnlySpecificURLsResponse>(); +} + +OwnPtr<Messages::LaunchServer::SealAllowlistResponse> ClientConnection::handle(const Messages::LaunchServer::SealAllowlist&) +{ + if (m_allowlist_is_sealed) { + did_misbehave("Got more than one request to seal the allowed handlers list"); + return {}; + } + + return make<Messages::LaunchServer::SealAllowlistResponse>(); +} + +} diff --git a/Userland/Services/LaunchServer/ClientConnection.h b/Userland/Services/LaunchServer/ClientConnection.h new file mode 100644 index 0000000000..77eef53374 --- /dev/null +++ b/Userland/Services/LaunchServer/ClientConnection.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020, Nicholas Hollett <niax@niax.co.uk> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <LaunchServer/LaunchClientEndpoint.h> +#include <LaunchServer/LaunchServerEndpoint.h> +#include <LibIPC/ClientConnection.h> + +namespace LaunchServer { + +class ClientConnection final : public IPC::ClientConnection<LaunchClientEndpoint, LaunchServerEndpoint> + , public LaunchServerEndpoint { + C_OBJECT(ClientConnection) +public: + ~ClientConnection() override; + + virtual void die() override; + +private: + explicit ClientConnection(NonnullRefPtr<Core::LocalSocket>, int client_id); + + virtual OwnPtr<Messages::LaunchServer::GreetResponse> handle(const Messages::LaunchServer::Greet&) override; + virtual OwnPtr<Messages::LaunchServer::OpenURLResponse> handle(const Messages::LaunchServer::OpenURL&) override; + virtual OwnPtr<Messages::LaunchServer::GetHandlersForURLResponse> handle(const Messages::LaunchServer::GetHandlersForURL&) override; + virtual OwnPtr<Messages::LaunchServer::GetHandlersWithDetailsForURLResponse> handle(const Messages::LaunchServer::GetHandlersWithDetailsForURL&) override; + virtual OwnPtr<Messages::LaunchServer::AddAllowedURLResponse> handle(const Messages::LaunchServer::AddAllowedURL&) override; + virtual OwnPtr<Messages::LaunchServer::AddAllowedHandlerWithAnyURLResponse> handle(const Messages::LaunchServer::AddAllowedHandlerWithAnyURL&) override; + virtual OwnPtr<Messages::LaunchServer::AddAllowedHandlerWithOnlySpecificURLsResponse> handle(const Messages::LaunchServer::AddAllowedHandlerWithOnlySpecificURLs&) override; + virtual OwnPtr<Messages::LaunchServer::SealAllowlistResponse> handle(const Messages::LaunchServer::SealAllowlist&) override; + + struct AllowlistEntry { + String handler_name; + bool any_url { false }; + Vector<URL> urls; + }; + + Vector<AllowlistEntry> m_allowlist; + bool m_allowlist_is_sealed { false }; +}; +} diff --git a/Userland/Services/LaunchServer/LaunchClient.ipc b/Userland/Services/LaunchServer/LaunchClient.ipc new file mode 100644 index 0000000000..abd4e9ba3d --- /dev/null +++ b/Userland/Services/LaunchServer/LaunchClient.ipc @@ -0,0 +1,4 @@ +endpoint LaunchClient = 102 +{ + Dummy() =| +} diff --git a/Userland/Services/LaunchServer/LaunchServer.ipc b/Userland/Services/LaunchServer/LaunchServer.ipc new file mode 100644 index 0000000000..4f50221f4b --- /dev/null +++ b/Userland/Services/LaunchServer/LaunchServer.ipc @@ -0,0 +1,12 @@ +endpoint LaunchServer = 101 +{ + Greet() => (i32 client_id) + OpenURL(URL url, String handler_name) => (bool response) + GetHandlersForURL(URL url) => (Vector<String> handlers) + GetHandlersWithDetailsForURL(URL url) => (Vector<String> handlers_details) + + AddAllowedURL(URL url) => () + AddAllowedHandlerWithAnyURL(String handler_name) => () + AddAllowedHandlerWithOnlySpecificURLs(String handler_name, Vector<URL> urls) => () + SealAllowlist() => () +} diff --git a/Userland/Services/LaunchServer/Launcher.cpp b/Userland/Services/LaunchServer/Launcher.cpp new file mode 100644 index 0000000000..ad6d025035 --- /dev/null +++ b/Userland/Services/LaunchServer/Launcher.cpp @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2020, Nicholas Hollett <niax@niax.co.uk>, Andreas Kling <kling@serenityos.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "Launcher.h" +#include <AK/Function.h> +#include <AK/JsonObject.h> +#include <AK/JsonObjectSerializer.h> +#include <AK/JsonValue.h> +#include <AK/LexicalPath.h> +#include <AK/StringBuilder.h> +#include <LibCore/ConfigFile.h> +#include <LibDesktop/AppFile.h> +#include <serenity.h> +#include <spawn.h> +#include <stdio.h> +#include <sys/stat.h> + +namespace LaunchServer { + +static Launcher* s_the; +static bool spawn(String executable, String argument); + +String Handler::name_from_executable(const StringView& executable) +{ + auto separator = executable.find_last_of('/'); + if (separator.has_value()) { + auto start = separator.value() + 1; + return executable.substring_view(start, executable.length() - start); + } + return executable; +} + +void Handler::from_executable(Type handler_type, const String& executable) +{ + this->handler_type = handler_type; + this->name = name_from_executable(executable); + this->executable = executable; +} + +String Handler::to_details_str() const +{ + StringBuilder builder; + JsonObjectSerializer obj { builder }; + obj.add("executable", executable); + obj.add("name", name); + switch (handler_type) { + case Type::Application: + obj.add("type", "app"); + break; + case Type::UserDefault: + obj.add("type", "userdefault"); + break; + case Type::UserPreferred: + obj.add("type", "userpreferred"); + break; + default: + break; + } + obj.finish(); + return builder.build(); +} + +Launcher::Launcher() +{ + ASSERT(s_the == nullptr); + s_the = this; +} + +Launcher& Launcher::the() +{ + ASSERT(s_the); + return *s_the; +} + +void Launcher::load_handlers(const String& af_dir) +{ + Desktop::AppFile::for_each([&](auto af) { + auto app_name = af->name(); + auto app_executable = af->executable(); + HashTable<String> file_types; + for (auto& file_type : af->launcher_file_types()) + file_types.set(file_type); + HashTable<String> protocols; + for (auto& protocol : af->launcher_protocols()) + protocols.set(protocol); + m_handlers.set(app_executable, { Handler::Type::Default, app_name, app_executable, file_types, protocols }); + }, + af_dir); +} + +void Launcher::load_config(const Core::ConfigFile& cfg) +{ + for (auto key : cfg.keys("FileType")) { + auto handler = cfg.read_entry("FileType", key).trim_whitespace(); + if (handler.is_empty()) + continue; + m_file_handlers.set(key.to_lowercase(), handler); + } + + for (auto key : cfg.keys("Protocol")) { + auto handler = cfg.read_entry("Protocol", key).trim_whitespace(); + if (handler.is_empty()) + continue; + m_protocol_handlers.set(key.to_lowercase(), handler); + } +} + +Vector<String> Launcher::handlers_for_url(const URL& url) +{ + Vector<String> handlers; + if (url.protocol() == "file") { + for_each_handler_for_path(url.path(), [&](auto& handler) -> bool { + handlers.append(handler.executable); + return true; + }); + } else { + for_each_handler(url.protocol(), m_protocol_handlers, [&](const auto& handler) -> bool { + if (handler.handler_type != Handler::Type::Default || handler.protocols.contains(url.protocol())) { + handlers.append(handler.executable); + return true; + } + return false; + }); + } + return handlers; +} + +Vector<String> Launcher::handlers_with_details_for_url(const URL& url) +{ + Vector<String> handlers; + if (url.protocol() == "file") { + for_each_handler_for_path(url.path(), [&](auto& handler) -> bool { + handlers.append(handler.to_details_str()); + return true; + }); + } else { + for_each_handler(url.protocol(), m_protocol_handlers, [&](const auto& handler) -> bool { + if (handler.handler_type != Handler::Type::Default || handler.protocols.contains(url.protocol())) { + handlers.append(handler.to_details_str()); + return true; + } + return false; + }); + } + return handlers; +} + +bool Launcher::open_url(const URL& url, const String& handler_name) +{ + if (!handler_name.is_null()) + return open_with_handler_name(url, handler_name); + + if (url.protocol() == "file") + return open_file_url(url); + + return open_with_user_preferences(m_protocol_handlers, url.protocol(), url.to_string()); +} + +bool Launcher::open_with_handler_name(const URL& url, const String& handler_name) +{ + auto handler_optional = m_handlers.get(handler_name); + if (!handler_optional.has_value()) + return false; + + auto& handler = handler_optional.value(); + String argument; + if (url.protocol() == "file") + argument = url.path(); + else + argument = url.to_string(); + return spawn(handler.executable, argument); +} + +bool spawn(String executable, String argument) +{ + pid_t child_pid; + const char* argv[] = { executable.characters(), argument.characters(), nullptr }; + if ((errno = posix_spawn(&child_pid, executable.characters(), nullptr, nullptr, const_cast<char**>(argv), environ))) { + perror("posix_spawn"); + return false; + } else { + if (disown(child_pid) < 0) + perror("disown"); + } + return true; +} + +Handler Launcher::get_handler_for_executable(Handler::Type handler_type, const String& executable) const +{ + Handler handler; + auto existing_handler = m_handlers.get(executable); + if (existing_handler.has_value()) { + handler = existing_handler.value(); + handler.handler_type = handler_type; + } else { + handler.from_executable(handler_type, executable); + } + return handler; +} + +bool Launcher::open_with_user_preferences(const HashMap<String, String>& user_preferences, const String key, const String argument, const String default_program) +{ + auto program_path = user_preferences.get(key); + if (program_path.has_value()) + return spawn(program_path.value(), argument); + + // There wasn't a handler for this, so try the fallback instead + program_path = user_preferences.get("*"); + if (program_path.has_value()) + return spawn(program_path.value(), argument); + + // Absolute worst case, try the provided default program, if any + if (!default_program.is_empty()) + return spawn(default_program, argument); + + return false; +} + +void Launcher::for_each_handler(const String& key, HashMap<String, String>& user_preference, Function<bool(const Handler&)> f) +{ + auto user_preferred = user_preference.get(key); + if (user_preferred.has_value()) + f(get_handler_for_executable(Handler::Type::UserPreferred, user_preferred.value())); + + size_t counted = 0; + for (auto& handler : m_handlers) { + // Skip over the existing item in the list + if (user_preferred.has_value() && user_preferred.value() == handler.value.executable) + continue; + if (f(handler.value)) + counted++; + } + + auto user_default = user_preference.get("*"); + if (counted == 0 && user_default.has_value()) + f(get_handler_for_executable(Handler::Type::UserDefault, user_default.value())); +} + +void Launcher::for_each_handler_for_path(const String& path, Function<bool(const Handler&)> f) +{ + struct stat st; + if (stat(path.characters(), &st) < 0) { + perror("stat"); + return; + } + + // TODO: Make directory opening configurable + if (S_ISDIR(st.st_mode)) { + f(get_handler_for_executable(Handler::Type::Default, "/bin/FileManager")); + return; + } + + if ((st.st_mode & S_IFMT) == S_IFREG && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) + f(get_handler_for_executable(Handler::Type::Application, path)); + + auto extension = LexicalPath(path).extension().to_lowercase(); + + for_each_handler(extension, m_file_handlers, [&](const auto& handler) -> bool { + if (handler.handler_type != Handler::Type::Default || handler.file_types.contains(extension)) + return f(handler); + return false; + }); +} + +bool Launcher::open_file_url(const URL& url) +{ + struct stat st; + if (stat(url.path().characters(), &st) < 0) { + perror("stat"); + return false; + } + + // TODO: Make directory opening configurable + if (S_ISDIR(st.st_mode)) + return spawn("/bin/FileManager", url.path()); + + if ((st.st_mode & S_IFMT) == S_IFREG && st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) + return spawn(url.path(), {}); + + auto extension_parts = url.path().to_lowercase().split('.'); + String extension = {}; + if (extension_parts.size() > 1) + extension = extension_parts.last(); + return open_with_user_preferences(m_file_handlers, extension, url.path(), "/bin/TextEditor"); +} +} diff --git a/Userland/Services/LaunchServer/Launcher.h b/Userland/Services/LaunchServer/Launcher.h new file mode 100644 index 0000000000..8708cdc258 --- /dev/null +++ b/Userland/Services/LaunchServer/Launcher.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020, Nicholas Hollett <niax@niax.co.uk> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <AK/HashMap.h> +#include <AK/HashTable.h> +#include <AK/URL.h> +#include <LibCore/ConfigFile.h> +#include <LibDesktop/AppFile.h> + +namespace LaunchServer { + +struct Handler { + enum class Type { + Default = 0, + Application, + UserPreferred, + UserDefault + }; + Type handler_type; + String name; + String executable; + HashTable<String> file_types {}; + HashTable<String> protocols {}; + + static String name_from_executable(const StringView&); + void from_executable(Type, const String&); + String to_details_str() const; +}; + +class Launcher { +public: + Launcher(); + static Launcher& the(); + + void load_handlers(const String& af_dir = Desktop::AppFile::APP_FILES_DIRECTORY); + void load_config(const Core::ConfigFile&); + bool open_url(const URL&, const String& handler_name); + Vector<String> handlers_for_url(const URL&); + Vector<String> handlers_with_details_for_url(const URL&); + +private: + HashMap<String, Handler> m_handlers; + HashMap<String, String> m_protocol_handlers; + HashMap<String, String> m_file_handlers; + + Handler get_handler_for_executable(Handler::Type, const String&) const; + void for_each_handler(const String& key, HashMap<String, String>& user_preferences, Function<bool(const Handler&)> f); + void for_each_handler_for_path(const String&, Function<bool(const Handler&)> f); + bool open_file_url(const URL&); + bool open_with_user_preferences(const HashMap<String, String>& user_preferences, const String key, const String argument, const String default_program = {}); + bool open_with_handler_name(const URL&, const String& handler_name); +}; +} diff --git a/Userland/Services/LaunchServer/main.cpp b/Userland/Services/LaunchServer/main.cpp new file mode 100644 index 0000000000..2535d1e8bb --- /dev/null +++ b/Userland/Services/LaunchServer/main.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020, Nicholas Hollett <niax@niax.co.uk> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ClientConnection.h" +#include "Launcher.h" +#include <LibCore/ConfigFile.h> +#include <LibCore/EventLoop.h> +#include <LibCore/LocalServer.h> +#include <stdio.h> +#include <unistd.h> + +int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) +{ + Core::EventLoop event_loop; + auto server = Core::LocalServer::construct(); + + auto launcher = LaunchServer::Launcher(); + + launcher.load_handlers(); + launcher.load_config(Core::ConfigFile::get_for_app("LaunchServer")); + + if (pledge("stdio accept rpath proc exec", nullptr) < 0) { + perror("pledge"); + return 1; + } + + bool ok = server->take_over_from_system_server(); + ASSERT(ok); + server->on_ready_to_accept = [&] { + auto client_socket = server->accept(); + if (!client_socket) { + dbgln("LaunchServer: accept failed."); + return; + } + static int s_next_client_id = 0; + int client_id = ++s_next_client_id; + dbgln("Received connection"); + IPC::new_client_connection<LaunchServer::ClientConnection>(client_socket.release_nonnull(), client_id); + }; + + return event_loop.exec(); +} |