#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)};
}