/* * Copyright (C) 2004-2015 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 #include #include #include struct ConfigStackEntry { CString sTag; CString sName; CConfig Config; ConfigStackEntry(const CString& Tag, const CString Name) { sTag = Tag; sName = Name; } }; 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 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_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.Left(2) == "/*") { /* Does this comment end on the same line again? */ bCommented = (sLine.Right(2) != "*/"); continue; } if ((sLine.empty()) || (sLine[0] == '#') || (sLine.Left(2) == "//")) { continue; } if ((sLine.Left(1) == "<") && (sLine.Right(1) == ">")) { sLine.LeftChomp(); sLine.RightChomp(); sLine.Trim(); CString sTag = sLine.Token(0); CString sValue = sLine.Token(1, true); sTag.Trim(); sValue.Trim(); if (sTag.Left(1) == "/") { sTag = sTag.substr(1); if (!sValue.empty()) ERROR("Malformated closing tag. Expected \"\"."); 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; SubConfig &conf = pActiveConfig->m_SubConfigs[sTag.AsLower()]; SubConfig::const_iterator it = conf.find(sName); if (it != conf.end()) ERROR("Duplicate entry for tag \"" << sTag << "\" name \"" << sName << "\"."); conf[sName] = CConfigEntry(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). if (sValue.Left(1) == " ") sValue.LeftChomp(); // 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'); for (EntryMapIterator it = m_ConfigEntries.begin(); it != m_ConfigEntries.end(); ++it) { for (VCString::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) { File.Write(sIndentation + it->first + " = " + *it2 + "\n"); } } for (SubConfigMapIterator it = m_SubConfigs.begin(); it != m_SubConfigs.end(); ++it) { for (SubConfig::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) { File.Write("\n"); File.Write(sIndentation + "<" + it->first + " " + it2->first + ">\n"); it2->second.m_pSubConfig->Write(File, iIndentation + 1); File.Write(sIndentation + "first + ">\n"); } } }