BareGit
#include "email_address.h"

#include <algorithm>
#include <cctype>
#include <ranges>
#include <string>
#include <string_view>

namespace
{

bool isAsciiWhitespace(unsigned char value)
{
    return value == ' ' || value == '\t' || value == '\n' ||
           value == '\r' || value == '\f' || value == '\v';
}

bool isLocalAtom(unsigned char value)
{
    return std::isalnum(value) != 0 ||
           std::string_view("!#$%&'*+-/=?^_`{|}~").find(
               static_cast<char>(value)) != std::string_view::npos;
}

bool isDomainLabel(const std::string& label)
{
    if(label.empty() || label.size() > 63 || label.front() == '-' ||
       label.back() == '-')
    {
        return false;
    }
    return std::ranges::all_of(label, [](unsigned char value)
    {
        return std::isalnum(value) != 0 || value == '-';
    });
}

} // namespace

mw::E<EmailAddress> normalizeEmail(const std::string& input)
{
    const auto first = std::ranges::find_if_not(input, isAsciiWhitespace);
    const auto last = std::ranges::find_if_not(
        input | std::views::reverse, isAsciiWhitespace).base();
    const std::string email = first < last ? std::string(first, last) : "";
    if(email.empty() || email.size() > 254)
    {
        return std::unexpected(mw::runtimeError("Invalid email address"));
    }
    for(unsigned char value : email)
    {
        if(value > 0x7f || value == 0 || value < 0x20 || value == 0x7f)
        {
            return std::unexpected(mw::runtimeError(
                "Invalid email address"));
        }
    }

    const std::size_t at = email.find('@');
    if(at == std::string::npos || at == 0 ||
       at != email.rfind('@') || at > 64 || at + 1 == email.size())
    {
        return std::unexpected(mw::runtimeError("Invalid email address"));
    }
    const std::string local = email.substr(0, at);
    const std::string domain = email.substr(at + 1);
    if(domain.size() > 253 || local.front() == '.' || local.back() == '.' ||
       local.find("..") != std::string::npos ||
       !std::ranges::all_of(local, [](unsigned char value)
       {
           return value == '.' || isLocalAtom(value);
       }))
    {
        return std::unexpected(mw::runtimeError("Invalid email address"));
    }

    std::size_t begin = 0;
    while(begin <= domain.size())
    {
        const std::size_t end = domain.find('.', begin);
        const std::string label = domain.substr(
            begin, end == std::string::npos ? end : end - begin);
        if(!isDomainLabel(label))
        {
            return std::unexpected(mw::runtimeError(
                "Invalid email address"));
        }
        if(end == std::string::npos)
        {
            break;
        }
        begin = end + 1;
    }

    std::string key = email;
    std::ranges::transform(key, key.begin(), [](unsigned char value)
    {
        return static_cast<char>(std::tolower(value));
    });
    return EmailAddress{email, std::move(key)};
}