From f885699d1a3cb6376dc6162f9e8440791034a329 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Sat, 19 Aug 2017 18:05:47 +0100 Subject: [PATCH] Make list of languages installed discoverable at runtime. Stop hardcoding Russian in webadmin. Limit the setting in controlpanel to the known languages, because untrusted language code might lead to some interesting vulnerabilities. --- CMakeLists.txt | 2 ++ include/znc/Translation.h | 7 +++++++ modules/controlpanel.cpp | 22 +++++++++++++++++++--- modules/webadmin.cpp | 16 +++++++++++----- src/Translation.cpp | 25 +++++++++++++++++++++++++ translations/ru-RU | 1 + 6 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 translations/ru-RU diff --git a/CMakeLists.txt b/CMakeLists.txt index 079b2335..02ff01b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -247,6 +247,8 @@ endif() install(DIRECTORY webskins DESTINATION "${CMAKE_INSTALL_DATADIR}/znc") +install(DIRECTORY translations + DESTINATION "${CMAKE_INSTALL_DATADIR}/znc") install(DIRECTORY man/ DESTINATION "${CMAKE_INSTALL_MANDIR}/man1" FILES_MATCHING PATTERN "znc*") diff --git a/include/znc/Translation.h b/include/znc/Translation.h index 0ea4864c..8ecfd2c2 100644 --- a/include/znc/Translation.h +++ b/include/znc/Translation.h @@ -20,6 +20,12 @@ #include #include +struct CTranslationInfo { + static std::map GetTranslations(); + + CString sSelfName; +}; + // All instances of modules share single message map using this class stored in // CZNC. class CTranslation { @@ -38,6 +44,7 @@ class CTranslation { void DelReference(const CString& sDomain); private: + // Domain is either "znc" or "znc-foo" where foo is a module name const std::locale& LoadTranslation(const CString& sDomain); std::unordered_map> diff --git a/modules/controlpanel.cpp b/modules/controlpanel.cpp index 5472ef74..e26be904 100644 --- a/modules/controlpanel.cpp +++ b/modules/controlpanel.cpp @@ -276,7 +276,9 @@ class CAdminMod : public CModule { PutModule("StatusPrefix = " + pUser->GetStatusPrefix()); #ifdef HAVE_I18N else if (sVar == "language") - PutModule("Language = " + pUser->GetLanguage()); + PutModule("Language = " + (pUser->GetLanguage().empty() + ? "en" + : pUser->GetLanguage())); #endif #ifdef HAVE_ICU else if (sVar == "clientencoding") @@ -453,8 +455,22 @@ class CAdminMod : public CModule { } #ifdef HAVE_I18N else if (sVar == "language") { - pUser->SetLanguage(sValue); - PutModule("Language = " + pUser->GetLanguage()); + auto mTranslations = CTranslationInfo::GetTranslations(); + // TODO: maybe stop special-casing English + if (sValue == "en") { + pUser->SetLanguage(""); + PutModule("Language is set to English"); + } else if (mTranslations.count(sValue)) { + pUser->SetLanguage(sValue); + PutModule("Language = " + sValue); + } else { + VCString vsCodes = {"en"}; + for (const auto it : mTranslations) { + vsCodes.push_back(it.first); + } + PutModule("Supported languages: " + + CString(", ").Join(vsCodes.begin(), vsCodes.end())); + } } #endif #ifdef HAVE_ICU diff --git a/modules/webadmin.cpp b/modules/webadmin.cpp index 966c0a62..b44342a6 100644 --- a/modules/webadmin.cpp +++ b/modules/webadmin.cpp @@ -316,7 +316,11 @@ class CWebAdminMod : public CModule { pNewUser->SetNoTrafficTimeout(uNoTrafficTimeout); #ifdef HAVE_I18N - pNewUser->SetLanguage(WebSock.GetParam("language")); + CString sLanguage = WebSock.GetParam("language"); + if (CTranslationInfo::GetTranslations().count(sLanguage) == 0) { + sLanguage = ""; + } + pNewUser->SetLanguage(sLanguage); #endif #ifdef HAVE_ICU CString sEncodingUtf = WebSock.GetParam("encoding_utf"); @@ -1376,13 +1380,15 @@ class CWebAdminMod : public CModule { #ifdef HAVE_I18N Tmpl["HaveI18N"] = "true"; - // TODO don't have them hardcoded here + // TODO maybe stop hardcoding English here CTemplate& l_en = Tmpl.AddRow("LanguageLoop"); l_en["Code"] = ""; l_en["Name"] = "English"; - CTemplate& l_ru = Tmpl.AddRow("LanguageLoop"); - l_ru["Code"] = "ru-RU"; - l_ru["Name"] = "Russian"; + for (const auto& it : CTranslationInfo::GetTranslations()) { + CTemplate& lang = Tmpl.AddRow("LanguageLoop"); + lang["Code"] = it.first; + lang["Name"] = it.second.sSelfName; + } #else Tmpl["HaveI18N"] = "false"; #endif diff --git a/src/Translation.cpp b/src/Translation.cpp index dbde1268..cc7e1d6c 100644 --- a/src/Translation.cpp +++ b/src/Translation.cpp @@ -15,11 +15,36 @@ */ #include +#include #ifdef HAVE_I18N #include #endif +namespace { +std::map FillTranslations() { + std::map mTranslations; + CDir Dir; + Dir.Fill(_DATADIR_ "/translations"); + for (CFile* pFile : Dir) { + CString sName = pFile->GetShortName(); + CTranslationInfo& translation = mTranslations[sName]; + MCString msData; + // TODO: make the file format more sensible than this + msData.ReadFromDisk(pFile->GetLongName()); + translation.sSelfName = msData["SelfName"]; + // TODO: right-to-left support + } + return mTranslations; +} +} // namespace + +std::map CTranslationInfo::GetTranslations() { + static std::map mTranslations = + FillTranslations(); + return mTranslations; +} + CTranslation& CTranslation::Get() { static CTranslation translation; return translation; diff --git a/translations/ru-RU b/translations/ru-RU new file mode 100644 index 00000000..8076c1ad --- /dev/null +++ b/translations/ru-RU @@ -0,0 +1 @@ +SelfName Русский