diff --git a/servatrice/CMakeLists.txt b/servatrice/CMakeLists.txt index c7276a0b..2b345362 100644 --- a/servatrice/CMakeLists.txt +++ b/servatrice/CMakeLists.txt @@ -5,6 +5,7 @@ project(Servatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") set(servatrice_SOURCES + src/email_parser.cpp src/main.cpp src/servatrice.cpp src/servatrice_connection_pool.cpp diff --git a/servatrice/src/email_parser.cpp b/servatrice/src/email_parser.cpp new file mode 100644 index 00000000..ebb9cece --- /dev/null +++ b/servatrice/src/email_parser.cpp @@ -0,0 +1,54 @@ +#include "email_parser.h" + +#include +#include + +QPair EmailParser::parseEmailAddress(const QString &dirtyEmailAddress) +{ + // https://www.regular-expressions.info/email.html + static const QRegularExpression emailRegex(R"(^([A-Z0-9._%+-]+)@([A-Z0-9.-]+\.[A-Z]{2,})$)", + QRegularExpression::CaseInsensitiveOption); + const auto match = emailRegex.match(dirtyEmailAddress); + + if (dirtyEmailAddress.isEmpty() || !match.hasMatch()) { + return {}; + } + + QString capturedEmailUser = match.captured(1); + QString capturedEmailAddressDomain = match.captured(2); + + // Replace googlemail.com with gmail.com, as is standard nowadays + // https://www.gmass.co/blog/domains-gmail-com-googlemail-com-and-google-com/ + if (capturedEmailAddressDomain.toLower() == "googlemail.com") { + capturedEmailAddressDomain = "gmail.com"; + } + + // Trim out dots and pluses from Google/Gmail domains + if (capturedEmailAddressDomain.toLower() == "gmail.com") { + // Remove all content after first plus sign (as unnecessary with gmail) + // https://gmail.googleblog.com/2008/03/2-hidden-ways-to-get-more-from-your.html + const auto firstPlusSign = capturedEmailUser.indexOf("+"); + if (firstPlusSign != -1) { + capturedEmailUser = capturedEmailUser.left(firstPlusSign); + } + + // Remove all periods (as unnecessary with gmail) + // https://gmail.googleblog.com/2008/03/2-hidden-ways-to-get-more-from-your.html + capturedEmailUser.replace(".", ""); + } + + return {capturedEmailUser, capturedEmailAddressDomain}; +} + +QString EmailParser::getParsedEmailAddress(const QString &dirtyEmailAddress) +{ + const auto parsedEmailAddress = EmailParser::parseEmailAddress(dirtyEmailAddress); + return EmailParser::getParsedEmailAddress(parsedEmailAddress); +} + +QString EmailParser::getParsedEmailAddress(const QPair &emailAddressIntermediate) +{ + const auto emailUser = emailAddressIntermediate.first; + const auto emailDomain = emailAddressIntermediate.second; + return emailUser + "@" + emailDomain; +} \ No newline at end of file diff --git a/servatrice/src/email_parser.h b/servatrice/src/email_parser.h new file mode 100644 index 00000000..4cb6ab05 --- /dev/null +++ b/servatrice/src/email_parser.h @@ -0,0 +1,15 @@ +#ifndef COCKATRICE_EMAILPARSER_H +#define COCKATRICE_EMAILPARSER_H + +#include +#include + +class EmailParser +{ +public: + static QPair parseEmailAddress(const QString &dirtyEmailAddress); + static QString getParsedEmailAddress(const QString &dirtyEmailAddress); + static QString getParsedEmailAddress(const QPair &emailAddressIntermediate); +}; + +#endif // COCKATRICE_EMAILPARSER_H diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index 80f60e3d..619586da 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -20,6 +20,7 @@ #include "servatrice.h" #include "decklist.h" +#include "email_parser.h" #include "featureset.h" #include "isl_interface.h" #include "main.h" @@ -627,7 +628,7 @@ void Servatrice::statusUpdate() while (servDbSelQuery->next()) { const QString userName = servDbSelQuery->value(0).toString(); - const QString emailAddress = servDbSelQuery->value(1).toString(); + const auto emailAddress = EmailParser::getParsedEmailAddress(servDbSelQuery->value(1).toString()); const QString token = servDbSelQuery->value(2).toString(); if (smtpClient->enqueueActivationTokenMail(userName, emailAddress, token)) { @@ -649,7 +650,7 @@ void Servatrice::statusUpdate() while (forgotPwQuery->next()) { const QString userName = forgotPwQuery->value(0).toString(); - const QString emailAddress = forgotPwQuery->value(1).toString(); + const auto emailAddress = EmailParser::getParsedEmailAddress(forgotPwQuery->value(1).toString()); const QString token = forgotPwQuery->value(2).toString(); if (smtpClient->enqueueForgotPasswordTokenMail(userName, emailAddress, token)) { diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index 363c2590..a77055f3 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -21,6 +21,7 @@ #include "serversocketinterface.h" #include "decklist.h" +#include "email_parser.h" #include "main.h" #include "pb/command_deck_del.pb.h" #include "pb/command_deck_del_dir.pb.h" @@ -1004,43 +1005,6 @@ Response::ResponseCode AbstractServerSocketInterface::cmdBanFromServer(const Com return Response::RespOk; } -QPair AbstractServerSocketInterface::parseEmailAddress(const QString &emailAddress) -{ - // https://www.regular-expressions.info/email.html - static const QRegularExpression emailRegex(R"(^([A-Z0-9._%+-]+)@([A-Z0-9.-]+\.[A-Z]{2,})$)", - QRegularExpression::CaseInsensitiveOption); - const auto match = emailRegex.match(emailAddress); - - if (emailAddress.isEmpty() || !match.hasMatch()) { - return {}; - } - - QString capturedEmailUser = match.captured(1); - QString capturedEmailAddressDomain = match.captured(2); - - // Replace googlemail.com with gmail.com, as is standard nowadays - // https://www.gmass.co/blog/domains-gmail-com-googlemail-com-and-google-com/ - if (capturedEmailAddressDomain.toLower() == "googlemail.com") { - capturedEmailAddressDomain = "gmail.com"; - } - - // Trim out dots and pluses from Google/Gmail domains - if (capturedEmailAddressDomain.toLower() == "gmail.com") { - // Remove all content after first plus sign (as unnecessary with gmail) - // https://gmail.googleblog.com/2008/03/2-hidden-ways-to-get-more-from-your.html - const int firstPlusSign = capturedEmailUser.indexOf("+"); - if (firstPlusSign != -1) { - capturedEmailUser = capturedEmailUser.left(firstPlusSign); - } - - // Remove all periods (as unnecessary with gmail) - // https://gmail.googleblog.com/2008/03/2-hidden-ways-to-get-more-from-your.html - capturedEmailUser.replace(".", ""); - } - - return {capturedEmailUser, capturedEmailAddressDomain}; -} - Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc) { @@ -1059,9 +1023,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C const QString emailBlackList = servatrice->getEmailBlackList(); const QString emailWhiteList = servatrice->getEmailWhiteList(); - auto parsedEmailAddress = parseEmailAddress(nameFromStdString(cmd.email())); - const QString emailUser = parsedEmailAddress.first; - const QString emailDomain = parsedEmailAddress.second; + const auto parsedEmailParts = EmailParser::parseEmailAddress(nameFromStdString(cmd.email())); + const auto emailUser = parsedEmailParts.first; + const auto emailDomain = parsedEmailParts.second; #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) const QStringList emailBlackListFilters = emailBlackList.split(",", Qt::SkipEmptyParts); const QStringList emailWhiteListFilters = emailWhiteList.split(",", Qt::SkipEmptyParts); @@ -1126,9 +1090,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C return Response::RespUserAlreadyExists; } - QString emailAddress = emailUser + "@" + emailDomain; + const auto parsedEmailAddress = EmailParser::getParsedEmailAddress(parsedEmailParts); if (servatrice->getMaxAccountsPerEmail() > 0 && - sqlInterface->checkNumberOfUserAccounts(emailAddress) >= servatrice->getMaxAccountsPerEmail()) { + sqlInterface->checkNumberOfUserAccounts(parsedEmailAddress) >= servatrice->getMaxAccountsPerEmail()) { if (servatrice->getEnableRegistrationAudit()) sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "REGISTER_ACCOUNT", "Too many usernames registered with this email address", @@ -1184,7 +1148,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C } bool requireEmailActivation = settingsCache->value("registration/requireemailactivation", true).toBool(); - bool regSucceeded = sqlInterface->registerUser(userName, realName, password, passwordNeedsHash, emailAddress, + bool regSucceeded = sqlInterface->registerUser(userName, realName, password, passwordNeedsHash, parsedEmailAddress, country, !requireEmailActivation); if (regSucceeded) { @@ -1262,7 +1226,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountEdit(const Comma return Response::RespFunctionNotAllowed; QString realName = nameFromStdString(cmd.real_name()); - QString emailAddress = nameFromStdString(cmd.email()); + const auto parsedEmailAddress = EmailParser::getParsedEmailAddress(nameFromStdString(cmd.email())); QString country = nameFromStdString(cmd.country()); bool checkedPassword = false; @@ -1310,8 +1274,8 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountEdit(const Comma query->bindValue(":realName", _realName); } if (cmd.has_email()) { - auto _emailAddress = nameFromStdString(cmd.email()); - query->bindValue(":email", _emailAddress); + const auto _parsedEmailAddress = EmailParser::getParsedEmailAddress(nameFromStdString(cmd.email())); + query->bindValue(":email", _parsedEmailAddress); } if (cmd.has_country()) { auto _country = nameFromStdString(cmd.country()); @@ -1326,7 +1290,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountEdit(const Comma userInfo->set_real_name(realName.toStdString()); } if (cmd.has_email()) { - userInfo->set_email(emailAddress.toStdString()); + userInfo->set_email(parsedEmailAddress.toStdString()); } if (cmd.has_country()) { userInfo->set_country(country.toStdString()); @@ -1543,8 +1507,8 @@ AbstractServerSocketInterface::cmdForgotPasswordChallenge(const Command_ForgotPa return Response::RespFunctionNotAllowed; } - if (!sqlInterface->validateTableColumnStringData("{prefix}_users", "email", userName, - nameFromStdString(cmd.email()))) { + const auto parsedEmailAddress = EmailParser::getParsedEmailAddress(nameFromStdString(cmd.email())); + if (!sqlInterface->validateTableColumnStringData("{prefix}_users", "email", userName, parsedEmailAddress)) { if (servatrice->getEnableForgotPasswordAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), "PASSWORD_RESET_CHALLENGE", "Failed to answer email challenge question", diff --git a/servatrice/src/serversocketinterface.h b/servatrice/src/serversocketinterface.h index b2ee2e27..b6aa7c1e 100644 --- a/servatrice/src/serversocketinterface.h +++ b/servatrice/src/serversocketinterface.h @@ -131,7 +131,6 @@ private: bool removeAdminFlagFromUser(const QString &user, int flag); bool isPasswordLongEnough(const int passwordLength); - static QPair parseEmailAddress(const QString &emailAddress); void removeSaidMessages(const QString &userName, int amount); public: