New paste Repaste Download
/*
* message.cpp -- respresent an IRC message.
*
* Written on Sunday, 05 May 2024 by oldfashionedcow
*
* Copyright 2024 Cow
*
* This file is part of ircplusplus.
*
* ircplusplus is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ircplusplus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with ircplusplus.  If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include "ircplusplus/parser/message.hpp"
#include "ircplusplus/parser/error.hpp"
#include <ranges>
#include <sstream>
namespace ircplusplus::parser {
using std::string;
using enum ParserError;
[[nodiscard]] std::string Message::_unescape_tag(std::string value) {
    std::string result;
    for (size_t i = 0; i < value.size(); ++i) {
        if (value[i] == '\\') {
            if (i + 1 < value.size()) {
                ++i;
                switch (value[i]) {
                case '\\':
                    result += '\\';
                    break;
                case ':':
                    result += ';';
                    break;
                case 's':
                    result += ' ';
                    break;
                case 'r':
                    result += '\r';
                    break;
                case 'n':
                    result += '\n';
                    break;
                default:
                    result += '\\';
                    result += value[i];
                    break;
                }
            } else {
                result += '\\';
            }
        } else {
            result += value[i];
        }
    }
    return result;
}
[[nodiscard]] std::expected<std::pair<std::unordered_map<std::string, std::string>, std::string>, ParserError>
Message::_parse_tags(const std::string &response) {
    auto line = response;
    std::unordered_map<std::string, std::string> tags = {};
    if (!line.starts_with('@')) {
        return std::make_pair(tags, line);
    }
    const auto space_pos = line.find(' ');
    if (space_pos == std::string_view::npos) {
        return std::unexpected(EmptyCommand);
    }
    const auto tags_split = std::string{line.substr(0, space_pos)};
    line = std::string{line.substr(space_pos + 1)};
    auto parts = std::ranges::transform_view(std::views::split(tags_split.substr(1), ';'), [](auto &&part) {
        return std::string(part.begin(), part.end());
    });
    for (const auto &part : parts) {
        if (part.empty()) {
            return std::unexpected(TrailingSemicolon);
        }
        auto kv_result = [](auto part) -> std::expected<std::pair<std::string, std::string>, ParserError> {
            auto equal_pos = part.find('=');
            if (equal_pos == std::string::npos) {
                return std::make_pair(part, "");
            }
            auto key = part.substr(0, equal_pos);
            auto value = _unescape_tag(part.substr(equal_pos + 1));
            if (key.empty()) {
                return std::unexpected(MissingKey);
            }
            return std::make_pair(key, value);
        }(part);
        if (!kv_result.has_value()) {
            return std::unexpected(kv_result.error());
        }
        auto [key, value] = kv_result.value();
        if (tags.contains(key)) {
            tags[key] = value;
        } else {
            tags.insert({key, value});
        }
    }
    return std::make_pair(tags, line);
}
[[nodiscard]] std::pair<std::string, std::optional<std::string>>
Message::_split_line(const std::string &response) {
    auto pos = response.find(" :");
    if (pos == std::string::npos) {
        return std::make_pair(std::string(response), std::optional<std::string>());
    }
    auto line = std::string(response.substr(0, pos));
    if (pos + 2 >= response.size()) {
        return std::make_pair(line, std::optional<std::string>());
    }
    auto trailing = std::string(response.substr(pos + 2));
    return std::make_pair(line, std::optional<std::string>(std::move(trailing)));
}
[[nodiscard]] std::vector<std::string> Message::_split_params(const std::string &line, char delim) {
    std::vector<std::string> params;
    std::istringstream iss(line);
    std::string token;
    while (std::getline(iss, token, delim)) {
        if (!token.empty()) {
            params.emplace_back(token);
        }
    }
    return params;
}
[[nodiscard]] std::expected<std::pair<std::optional<std::string>, std::string>, ParserError>
Message::_parse_command(std::vector<std::string> &params, const std::optional<std::string> &trailing) {
    std::optional<std::string> source;
    if (params.at(0).starts_with(":")) {
        source = params.at(0).substr(1);
        params.erase(params.begin());
    }
    if (params.empty()) {
        return std::unexpected(EmptyCommand); // Error if command-less line
    }
    auto command = params.at(0);
    params.erase(params.begin());
    if (trailing.has_value()) {
        params.emplace_back(*trailing);
    }
    return std::make_tuple(std::move(source), std::move(command));
}
[[nodiscard]] Source Message::_parse_source(const std::string &source) {
    std::string nickname;
    std::string username;
    string hostmask;
    std::vector<std::string> tokens;
    size_t pos = source.find('@');
    if (pos != std::string::npos) {
        tokens.emplace_back(source.substr(0, pos));
        tokens.emplace_back(source.substr(pos + 1));
    } else {
        tokens.emplace_back(source);
    }
    if (tokens.size() == 2) {
        username = tokens.at(0);
        hostmask = tokens.at(1);
    } else {
        username = source;
    }
    pos = username.find('!');
    if (pos != std::string::npos) {
        nickname = username.substr(0, pos);
        username = username.substr(pos + 1);
    } else {
        nickname = username;
        username.clear();
    }
    return {nickname, username, hostmask};
}
[[nodiscard]] std::expected<Message, ParserError> Message::parse(const std::string &response) {
    auto tags_result = _parse_tags(response);
    if (!tags_result.has_value()) {
        return std::unexpected(tags_result.error());
    }
    auto [tags, response_split] = tags_result.value();
    auto [line, trailing] = _split_line(response_split);
    auto params = _split_params(line, ' ');
    if (params.empty()) {
        return std::unexpected(EmptyCommand); // Error if command-less line
    }
    auto command_result = _parse_command(params, trailing);
    if (!command_result.has_value()) {
        return std::unexpected(command_result.error());
    }
    auto [source_str, command] = command_result.value();
    std::optional<Source> source = std::nullopt;
    if (source_str.has_value()) {
        source = _parse_source(source_str.value());
    }
    return Message(tags, source, command, params);
}
} // namespace ircplusplus::parser
Filename: ../ircplusplus/lib/parser/message.cpp. Size: 7kb. View raw, , hex, or download this file.

This paste expires on 2024-05-19 08:33:23.222107. Pasted through v1-api.