mirror of
https://github.com/znc/znc.git
synced 2026-03-28 17:42:41 +01:00
203 lines
6.7 KiB
C++
203 lines
6.7 KiB
C++
/*
|
|
* Copyright (C) 2004-2025 ZNC, see the NOTICE file for details.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <znc/Config.h>
|
|
#include <znc/FileUtils.h>
|
|
#include <stack>
|
|
#include <sstream>
|
|
|
|
struct ConfigStackEntry {
|
|
CString sTag;
|
|
CString sName;
|
|
CConfig Config;
|
|
|
|
ConfigStackEntry(const CString& Tag, const CString Name)
|
|
: sTag(Tag), sName(Name), Config() {}
|
|
};
|
|
|
|
CConfigEntry::CConfigEntry() : m_pSubConfig(nullptr) {}
|
|
|
|
CConfigEntry::CConfigEntry(const CConfig& Config)
|
|
: m_pSubConfig(new CConfig(Config)) {}
|
|
|
|
CConfigEntry::CConfigEntry(const CConfigEntry& other) : m_pSubConfig(nullptr) {
|
|
if (other.m_pSubConfig) m_pSubConfig = new CConfig(*other.m_pSubConfig);
|
|
}
|
|
|
|
CConfigEntry::~CConfigEntry() { delete m_pSubConfig; }
|
|
|
|
CConfigEntry& CConfigEntry::operator=(const CConfigEntry& other) {
|
|
delete m_pSubConfig;
|
|
if (other.m_pSubConfig)
|
|
m_pSubConfig = new CConfig(*other.m_pSubConfig);
|
|
else
|
|
m_pSubConfig = nullptr;
|
|
return *this;
|
|
}
|
|
|
|
bool CConfig::Parse(CFile& file, CString& sErrorMsg) {
|
|
CString sLine;
|
|
unsigned int uLineNum = 0;
|
|
CConfig* pActiveConfig = this;
|
|
std::stack<ConfigStackEntry> ConfigStack;
|
|
bool bCommented = false; // support for /**/ style comments
|
|
|
|
if (!file.Seek(0)) {
|
|
sErrorMsg = "Could not seek to the beginning of the config.";
|
|
return false;
|
|
}
|
|
|
|
while (file.ReadLine(sLine)) {
|
|
uLineNum++;
|
|
|
|
#define ERROR(arg) \
|
|
do { \
|
|
std::stringstream stream; \
|
|
stream << "Error on line " << uLineNum << ": " << arg; \
|
|
sErrorMsg = stream.str(); \
|
|
m_SubConfigNameSets.clear(); \
|
|
m_SubConfigs.clear(); \
|
|
m_ConfigEntries.clear(); \
|
|
return false; \
|
|
} while (0)
|
|
|
|
// Remove all leading spaces and trailing line endings
|
|
sLine.TrimLeft();
|
|
sLine.TrimRight("\r\n");
|
|
|
|
if (bCommented || sLine.StartsWith("/*")) {
|
|
/* Does this comment end on the same line again? */
|
|
bCommented = (!sLine.EndsWith("*/"));
|
|
|
|
continue;
|
|
}
|
|
|
|
if ((sLine.empty()) || (sLine.StartsWith("#")) ||
|
|
(sLine.StartsWith("//"))) {
|
|
continue;
|
|
}
|
|
|
|
if ((sLine.StartsWith("<")) && (sLine.EndsWith(">"))) {
|
|
sLine.LeftChomp();
|
|
sLine.RightChomp();
|
|
sLine.Trim();
|
|
|
|
CString sTag = sLine.Token(0);
|
|
CString sValue = sLine.Token(1, true);
|
|
|
|
sTag.Trim();
|
|
sValue.Trim();
|
|
|
|
if (sTag.TrimPrefix("/")) {
|
|
if (!sValue.empty())
|
|
ERROR("Malformated closing tag. Expected \"</" << sTag
|
|
<< ">\".");
|
|
if (ConfigStack.empty())
|
|
ERROR("Closing tag \"" << sTag << "\" which is not open.");
|
|
|
|
const struct ConfigStackEntry& entry = ConfigStack.top();
|
|
CConfig myConfig(entry.Config);
|
|
CString sName(entry.sName);
|
|
|
|
if (!sTag.Equals(entry.sTag))
|
|
ERROR("Closing tag \"" << sTag << "\" which is not open.");
|
|
|
|
// This breaks entry
|
|
ConfigStack.pop();
|
|
|
|
if (ConfigStack.empty())
|
|
pActiveConfig = this;
|
|
else
|
|
pActiveConfig = &ConfigStack.top().Config;
|
|
|
|
const auto sTagLower = sTag.AsLower();
|
|
auto& nameset = pActiveConfig->m_SubConfigNameSets[sTagLower];
|
|
|
|
if (nameset.find(sName) != nameset.end())
|
|
ERROR("Duplicate entry for tag \"" << sTag << "\" name \""
|
|
<< sName << "\".");
|
|
|
|
nameset.insert(sName);
|
|
pActiveConfig->m_SubConfigs[sTagLower].emplace_back(sName,
|
|
myConfig);
|
|
} else {
|
|
if (sValue.empty())
|
|
ERROR("Empty block name at begin of block.");
|
|
ConfigStack.push(ConfigStackEntry(sTag.AsLower(), sValue));
|
|
pActiveConfig = &ConfigStack.top().Config;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// If we have a regular line, figure out where it goes
|
|
CString sName = sLine.Token(0, false, "=");
|
|
CString sValue = sLine.Token(1, true, "=");
|
|
|
|
// Only remove the first space, people might want
|
|
// leading spaces (e.g. in the MOTD).
|
|
sValue.TrimPrefix(" ");
|
|
|
|
// We don't have any names with spaces, trim all
|
|
// leading/trailing spaces.
|
|
sName.Trim();
|
|
|
|
if (sName.empty() || sValue.empty()) ERROR("Malformed line");
|
|
|
|
CString sNameLower = sName.AsLower();
|
|
pActiveConfig->m_ConfigEntries[sNameLower].push_back(sValue);
|
|
}
|
|
|
|
if (bCommented) ERROR("Comment not closed at end of file.");
|
|
|
|
if (!ConfigStack.empty()) {
|
|
const CString& sTag = ConfigStack.top().sTag;
|
|
ERROR(
|
|
"Not all tags are closed at the end of the file. Inner-most open "
|
|
"tag is \""
|
|
<< sTag << "\".");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CConfig::Write(CFile& File, unsigned int iIndentation) {
|
|
CString sIndentation = CString(iIndentation, '\t');
|
|
|
|
auto SingleLine = [](const CString& s) {
|
|
return s.Replace_n("\r", "").Replace_n("\n", "");
|
|
};
|
|
|
|
for (const auto& it : m_ConfigEntries) {
|
|
for (const CString& sValue : it.second) {
|
|
File.Write(SingleLine(sIndentation + it.first + " = " + sValue) +
|
|
"\n");
|
|
}
|
|
}
|
|
|
|
for (const auto& it : m_SubConfigs) {
|
|
for (const auto& it2 : it.second) {
|
|
File.Write("\n");
|
|
|
|
File.Write(SingleLine(sIndentation + "<" + it.first + " " +
|
|
it2.first + ">") +
|
|
"\n");
|
|
it2.second.m_pSubConfig->Write(File, iIndentation + 1);
|
|
File.Write(SingleLine(sIndentation + "</" + it.first + ">") + "\n");
|
|
}
|
|
}
|
|
}
|