mirror of
				https://github.com/LadybirdBrowser/ladybird.git
				synced 2025-11-03 23:00:58 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			249 lines
		
	
	
	
		
			7.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			249 lines
		
	
	
	
		
			7.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
 | 
						|
 * Copyright (c) 2021, networkException <networkexception@serenityos.org>
 | 
						|
 * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
 | 
						|
 *
 | 
						|
 * SPDX-License-Identifier: BSD-2-Clause
 | 
						|
 */
 | 
						|
 | 
						|
#include <AK/LexicalPath.h>
 | 
						|
#include <AK/StringBuilder.h>
 | 
						|
#include <LibCore/ConfigFile.h>
 | 
						|
#include <LibCore/Directory.h>
 | 
						|
#include <LibCore/StandardPaths.h>
 | 
						|
#include <LibCore/System.h>
 | 
						|
#include <pwd.h>
 | 
						|
#include <sys/types.h>
 | 
						|
 | 
						|
namespace Core {
 | 
						|
 | 
						|
ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open_for_lib(DeprecatedString const& lib_name, AllowWriting allow_altering)
 | 
						|
{
 | 
						|
    DeprecatedString directory_name = DeprecatedString::formatted("{}/lib", StandardPaths::config_directory());
 | 
						|
    auto directory = TRY(Directory::create(directory_name, Directory::CreateDirectories::Yes));
 | 
						|
    auto path = DeprecatedString::formatted("{}/{}.ini", directory, lib_name);
 | 
						|
    return ConfigFile::open(path, allow_altering);
 | 
						|
}
 | 
						|
 | 
						|
ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open_for_app(DeprecatedString const& app_name, AllowWriting allow_altering)
 | 
						|
{
 | 
						|
    auto directory = TRY(Directory::create(StandardPaths::config_directory(), Directory::CreateDirectories::Yes));
 | 
						|
    auto path = DeprecatedString::formatted("{}/{}.ini", directory, app_name);
 | 
						|
    return ConfigFile::open(path, allow_altering);
 | 
						|
}
 | 
						|
 | 
						|
ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open_for_system(DeprecatedString const& app_name, AllowWriting allow_altering)
 | 
						|
{
 | 
						|
    auto path = DeprecatedString::formatted("/etc/{}.ini", app_name);
 | 
						|
    return ConfigFile::open(path, allow_altering);
 | 
						|
}
 | 
						|
 | 
						|
ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open(DeprecatedString const& filename, AllowWriting allow_altering)
 | 
						|
{
 | 
						|
    auto maybe_file = File::open(filename, allow_altering == AllowWriting::Yes ? File::OpenMode::ReadWrite : File::OpenMode::Read);
 | 
						|
    OwnPtr<InputBufferedFile> buffered_file;
 | 
						|
    if (maybe_file.is_error()) {
 | 
						|
        // If we attempted to open a read-only file that does not exist, we ignore the error, making it appear
 | 
						|
        // the same as if we had opened an empty file. This behavior is a little weird, but is required by
 | 
						|
        // user code, which does not check the config file exists before opening.
 | 
						|
        if (!(allow_altering == AllowWriting::No && maybe_file.error().code() == ENOENT))
 | 
						|
            return maybe_file.release_error();
 | 
						|
    } else {
 | 
						|
        buffered_file = TRY(InputBufferedFile::create(maybe_file.release_value()));
 | 
						|
    }
 | 
						|
 | 
						|
    auto config_file = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) ConfigFile(filename, move(buffered_file))));
 | 
						|
    TRY(config_file->reparse());
 | 
						|
    return config_file;
 | 
						|
}
 | 
						|
 | 
						|
ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open(DeprecatedString const& filename, int fd)
 | 
						|
{
 | 
						|
    auto file = TRY(File::adopt_fd(fd, File::OpenMode::ReadWrite));
 | 
						|
    return open(filename, move(file));
 | 
						|
}
 | 
						|
 | 
						|
ErrorOr<NonnullRefPtr<ConfigFile>> ConfigFile::open(DeprecatedString const& filename, NonnullOwnPtr<Core::File> file)
 | 
						|
{
 | 
						|
    auto buffered_file = TRY(InputBufferedFile::create(move(file)));
 | 
						|
 | 
						|
    auto config_file = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) ConfigFile(filename, move(buffered_file))));
 | 
						|
    TRY(config_file->reparse());
 | 
						|
    return config_file;
 | 
						|
}
 | 
						|
 | 
						|
ConfigFile::ConfigFile(DeprecatedString const& filename, OwnPtr<InputBufferedFile> open_file)
 | 
						|
    : m_filename(filename)
 | 
						|
    , m_file(move(open_file))
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
ConfigFile::~ConfigFile()
 | 
						|
{
 | 
						|
    MUST(sync());
 | 
						|
}
 | 
						|
 | 
						|
ErrorOr<void> ConfigFile::reparse()
 | 
						|
{
 | 
						|
    m_groups.clear();
 | 
						|
    if (!m_file)
 | 
						|
        return {};
 | 
						|
 | 
						|
    HashMap<DeprecatedString, DeprecatedString>* current_group = nullptr;
 | 
						|
 | 
						|
    auto buffer = TRY(ByteBuffer::create_uninitialized(4096));
 | 
						|
    while (TRY(m_file->can_read_line())) {
 | 
						|
        auto line = TRY(m_file->read_line(buffer));
 | 
						|
        size_t i = 0;
 | 
						|
 | 
						|
        while (i < line.length() && (line[i] == ' ' || line[i] == '\t' || line[i] == '\n'))
 | 
						|
            ++i;
 | 
						|
 | 
						|
        if (i >= line.length())
 | 
						|
            continue;
 | 
						|
 | 
						|
        switch (line[i]) {
 | 
						|
        case '#': // Comment, skip entire line.
 | 
						|
        case ';': // -||-
 | 
						|
            continue;
 | 
						|
        case '[': { // Start of new group.
 | 
						|
            StringBuilder builder;
 | 
						|
            ++i; // Skip the '['
 | 
						|
            while (i < line.length() && (line[i] != ']')) {
 | 
						|
                builder.append(line[i]);
 | 
						|
                ++i;
 | 
						|
            }
 | 
						|
            current_group = &m_groups.ensure(builder.to_deprecated_string());
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        default: { // Start of key
 | 
						|
            StringBuilder key_builder;
 | 
						|
            StringBuilder value_builder;
 | 
						|
            while (i < line.length() && (line[i] != '=')) {
 | 
						|
                key_builder.append(line[i]);
 | 
						|
                ++i;
 | 
						|
            }
 | 
						|
            ++i; // Skip the '='
 | 
						|
            while (i < line.length() && (line[i] != '\n')) {
 | 
						|
                value_builder.append(line[i]);
 | 
						|
                ++i;
 | 
						|
            }
 | 
						|
            if (!current_group) {
 | 
						|
                // We're not in a group yet, create one with the name ""...
 | 
						|
                current_group = &m_groups.ensure("");
 | 
						|
            }
 | 
						|
            auto value_string = value_builder.to_deprecated_string();
 | 
						|
            current_group->set(key_builder.to_deprecated_string(), value_string.trim_whitespace(TrimMode::Right));
 | 
						|
        }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return {};
 | 
						|
}
 | 
						|
 | 
						|
DeprecatedString ConfigFile::read_entry(DeprecatedString const& group, DeprecatedString const& key, DeprecatedString const& default_value) const
 | 
						|
{
 | 
						|
    if (!has_key(group, key)) {
 | 
						|
        return default_value;
 | 
						|
    }
 | 
						|
    auto it = m_groups.find(group);
 | 
						|
    auto jt = it->value.find(key);
 | 
						|
    return jt->value;
 | 
						|
}
 | 
						|
 | 
						|
bool ConfigFile::read_bool_entry(DeprecatedString const& group, DeprecatedString const& key, bool default_value) const
 | 
						|
{
 | 
						|
    auto value = read_entry(group, key, default_value ? "true" : "false");
 | 
						|
    return value == "1" || value.equals_ignoring_ascii_case("true"sv);
 | 
						|
}
 | 
						|
 | 
						|
void ConfigFile::write_entry(DeprecatedString const& group, DeprecatedString const& key, DeprecatedString const& value)
 | 
						|
{
 | 
						|
    m_groups.ensure(group).ensure(key) = value;
 | 
						|
    m_dirty = true;
 | 
						|
}
 | 
						|
 | 
						|
void ConfigFile::write_bool_entry(DeprecatedString const& group, DeprecatedString const& key, bool value)
 | 
						|
{
 | 
						|
    write_entry(group, key, value ? "true" : "false");
 | 
						|
}
 | 
						|
 | 
						|
ErrorOr<void> ConfigFile::sync()
 | 
						|
{
 | 
						|
    if (!m_dirty)
 | 
						|
        return {};
 | 
						|
 | 
						|
    if (!m_file)
 | 
						|
        return Error::from_errno(ENOENT);
 | 
						|
 | 
						|
    TRY(m_file->truncate(0));
 | 
						|
    TRY(m_file->seek(0, SeekMode::SetPosition));
 | 
						|
 | 
						|
    for (auto& it : m_groups) {
 | 
						|
        TRY(m_file->write_until_depleted(DeprecatedString::formatted("[{}]\n", it.key).bytes()));
 | 
						|
        for (auto& jt : it.value)
 | 
						|
            TRY(m_file->write_until_depleted(DeprecatedString::formatted("{}={}\n", jt.key, jt.value).bytes()));
 | 
						|
        TRY(m_file->write_until_depleted("\n"sv.bytes()));
 | 
						|
    }
 | 
						|
 | 
						|
    m_dirty = false;
 | 
						|
    return {};
 | 
						|
}
 | 
						|
 | 
						|
void ConfigFile::dump() const
 | 
						|
{
 | 
						|
    for (auto& it : m_groups) {
 | 
						|
        outln("[{}]", it.key);
 | 
						|
        for (auto& jt : it.value)
 | 
						|
            outln("{}={}", jt.key, jt.value);
 | 
						|
        outln();
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
Vector<DeprecatedString> ConfigFile::groups() const
 | 
						|
{
 | 
						|
    return m_groups.keys();
 | 
						|
}
 | 
						|
 | 
						|
Vector<DeprecatedString> ConfigFile::keys(DeprecatedString const& group) const
 | 
						|
{
 | 
						|
    auto it = m_groups.find(group);
 | 
						|
    if (it == m_groups.end())
 | 
						|
        return {};
 | 
						|
    return it->value.keys();
 | 
						|
}
 | 
						|
 | 
						|
bool ConfigFile::has_key(DeprecatedString const& group, DeprecatedString const& key) const
 | 
						|
{
 | 
						|
    auto it = m_groups.find(group);
 | 
						|
    if (it == m_groups.end())
 | 
						|
        return {};
 | 
						|
    return it->value.contains(key);
 | 
						|
}
 | 
						|
 | 
						|
bool ConfigFile::has_group(DeprecatedString const& group) const
 | 
						|
{
 | 
						|
    return m_groups.contains(group);
 | 
						|
}
 | 
						|
 | 
						|
void ConfigFile::add_group(DeprecatedString const& group)
 | 
						|
{
 | 
						|
    m_groups.ensure(group);
 | 
						|
    m_dirty = true;
 | 
						|
}
 | 
						|
 | 
						|
void ConfigFile::remove_group(DeprecatedString const& group)
 | 
						|
{
 | 
						|
    m_groups.remove(group);
 | 
						|
    m_dirty = true;
 | 
						|
}
 | 
						|
 | 
						|
void ConfigFile::remove_entry(DeprecatedString const& group, DeprecatedString const& key)
 | 
						|
{
 | 
						|
    auto it = m_groups.find(group);
 | 
						|
    if (it == m_groups.end())
 | 
						|
        return;
 | 
						|
    it->value.remove(key);
 | 
						|
    m_dirty = true;
 | 
						|
}
 | 
						|
 | 
						|
}
 |