Permalink
Browse files

Overhaul the config parsing

This moves stuff to a two-step model. First, the new class CConfig reads the
config file, parses it and creates a in-memory model of stuff. Only then do we
actually go forward and apply the stuff.

The upside of this is that some config errors are caught before we change
anything on the running upside.

Let's see how much stuff this broke...

Signed-off-by: Uli Schlachter <psychon@znc.in>
  • Loading branch information...
1 parent ee9686e commit 70c7745899c659c5b85ea592cb221208a0a6cad0 @psychon psychon committed Mar 28, 2011
Showing with 1,018 additions and 594 deletions.
  1. +2 −0 .gitignore
  2. +19 −1 Chan.cpp
  3. +2 −1 Chan.h
  4. +179 −0 Config.cpp
  5. +92 −0 Config.h
  6. +1 −1 Makefile.in
  7. +212 −0 User.cpp
  8. +3 −0 User.h
  9. +1 −0 configure.ac
  10. +164 −0 test/ConfigTest.cpp
  11. +46 −0 test/Makefile.in
  12. +293 −591 znc.cpp
  13. +4 −0 znc.h
View
@@ -30,6 +30,8 @@ Makefile
/modules/modpython/*.pyc
/modules/*.pyc
+/test/ConfigTest
+
# Compiled Object files
*.o
View
@@ -10,8 +10,9 @@
#include "IRCSock.h"
#include "User.h"
#include "znc.h"
+#include "Config.h"
-CChan::CChan(const CString& sName, CUser* pUser, bool bInConfig) {
+CChan::CChan(const CString& sName, CUser* pUser, bool bInConfig, CConfig *pConfig) {
m_sName = sName.Token(0);
m_sKey = sName.Token(1);
m_pUser = pUser;
@@ -27,6 +28,23 @@ CChan::CChan(const CString& sName, CUser* pUser, bool bInConfig) {
m_bKeepBuffer = m_pUser->KeepBuffer();
m_bDisabled = false;
Reset();
+
+ if (pConfig) {
+ CString sValue;
+ if (pConfig->FindStringEntry("buffer", sValue))
+ SetBufferCount(sValue.ToUInt(), true);
+ if (pConfig->FindStringEntry("keepbuffer", sValue))
+ SetKeepBuffer(sValue.ToBool());
+ if (pConfig->FindStringEntry("detached", sValue))
+ SetDetached(sValue.ToBool());
+ if (pConfig->FindStringEntry("autocycle", sValue))
+ if (sValue.Equals("true"))
+ CUtils::PrintError("WARNING: AutoCycle has been removed, instead try -> LoadModule = autocycle " + sName);
+ if (pConfig->FindStringEntry("key", sValue))
+ SetKey(sValue);
+ if (pConfig->FindStringEntry("modes", sValue))
+ SetDefaultModes(sValue);
+ }
}
CChan::~CChan() {
View
3 Chan.h
@@ -24,6 +24,7 @@ using std::set;
// Forward Declarations
class CUser;
class CClient;
+class CConfig;
// !Forward Declarations
class CChan {
@@ -51,7 +52,7 @@ class CChan {
M_Except = 'e'
} EModes;
- CChan(const CString& sName, CUser* pUser, bool bInConfig);
+ CChan(const CString& sName, CUser* pUser, bool bInConfig, CConfig *pConfig = NULL);
~CChan();
void Reset();
View
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2004-2011 See the AUTHORS file for details.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#include "Config.h"
+#include <stack>
+#include <sstream>
+
+struct ConfigStackEntry {
+ CString sTag;
+ CString sName;
+ CConfig Config;
+
+ ConfigStackEntry(const CString& Tag, const CString Name) {
+ sTag = Tag;
+ sName = Name;
+ }
+};
+
+CConfig::CConfigEntry::CConfigEntry()
+ : m_pSubConfig(NULL) {
+}
+
+CConfig::CConfigEntry::CConfigEntry(const CConfig& Config)
+ : m_pSubConfig(new CConfig(Config)) {
+}
+
+CConfig::CConfigEntry::CConfigEntry(const CConfigEntry& other)
+ : m_pSubConfig(NULL) {
+ if (other.m_pSubConfig)
+ m_pSubConfig = new CConfig(*other.m_pSubConfig);
+}
+
+CConfig::CConfigEntry::~CConfigEntry()
+{
+ delete m_pSubConfig;
+}
+
+CConfig::CConfigEntry& CConfig::CConfigEntry::operator=(const CConfigEntry& other) {
+ delete m_pSubConfig;
+ if (other.m_pSubConfig)
+ m_pSubConfig = new CConfig(*other.m_pSubConfig);
+ else
+ m_pSubConfig = NULL;
+ 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))
+ return "Could not seek to the beginning of the config.";
+
+ 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 ((sLine.empty()) || (sLine[0] == '#') || (sLine.Left(2) == "//")) {
+ continue;
+ }
+
+ if (sLine.Left(2) == "/*") {
+ if (sLine.Right(2) != "*/") {
+ bCommented = true;
+ }
+
+ continue;
+ }
+
+ if (bCommented) {
+ if (sLine.Right(2) == "*/") {
+ bCommented = false;
+ }
+
+ 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 \"</" << 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;
+
+ 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;
+}
View
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2004-2011 See the AUTHORS file for details.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#include "ZNCString.h"
+#include "FileUtils.h"
+
+class CConfig {
+public:
+ struct CConfigEntry {
+ CConfigEntry();
+ CConfigEntry(const CConfig& Config);
+ CConfigEntry(const CConfigEntry& other);
+ ~CConfigEntry();
+ CConfigEntry& operator=(const CConfigEntry& other);
+
+ CConfig* m_pSubConfig;
+ };
+
+ typedef map<CString, VCString> EntryMap;
+ typedef map<CString, CConfigEntry> SubConfig;
+ typedef map<CString, SubConfig> SubConfigMap;
+
+ typedef EntryMap::const_iterator EntryMapIterator;
+ typedef SubConfigMap::const_iterator SubConfigMapIterator;
+
+ EntryMapIterator BeginEntries() const {
+ return m_ConfigEntries.begin();
+ }
+ EntryMapIterator EndEntries() const {
+ return m_ConfigEntries.end();
+ }
+
+ SubConfigMapIterator BeginSubConfigs() const {
+ return m_SubConfigs.begin();
+ }
+ SubConfigMapIterator EndSubConfigs() const {
+ return m_SubConfigs.end();
+ }
+
+ bool FindStringVector(const CString& sName, VCString& vsList) {
+ EntryMap::iterator it = m_ConfigEntries.find(sName);
+ vsList.clear();
+ if (it == m_ConfigEntries.end())
+ return false;
+ vsList = it->second;
+ m_ConfigEntries.erase(it);
+ return true;
+ }
+
+ bool FindStringEntry(const CString& sName, CString& sRes) {
+ EntryMap::iterator it = m_ConfigEntries.find(sName);
+ sRes.clear();
+ if (it == m_ConfigEntries.end() || it->second.empty())
+ return false;
+ sRes = it->second.front();
+ it->second.erase(it->second.begin());
+ if (it->second.empty())
+ m_ConfigEntries.erase(it);
+ return true;
+ }
+
+ bool FindSubConfig(const CString& sName, SubConfig& Config) {
+ SubConfigMap::iterator it = m_SubConfigs.find(sName);
+ if (it == m_SubConfigs.end()) {
+ Config.clear();
+ return false;
+ }
+ Config = it->second;
+ m_SubConfigs.erase(it);
+ return true;
+ }
+
+ bool empty() const {
+ return m_ConfigEntries.empty() && m_SubConfigs.empty();
+ }
+
+ bool Parse(CFile& file, CString& sErrorMsg);
+
+private:
+ EntryMap m_ConfigEntries;
+ SubConfigMap m_SubConfigs;
+};
+
+#endif // !CONFIG_H
View
@@ -31,7 +31,7 @@ INSTALL_DATA := @INSTALL_DATA@
LIB_SRCS := ZNCString.cpp Csocket.cpp znc.cpp User.cpp IRCSock.cpp Client.cpp DCCBounce.cpp \
DCCSock.cpp Chan.cpp Nick.cpp Server.cpp Modules.cpp MD5.cpp Buffer.cpp Utils.cpp \
FileUtils.cpp HTTPSock.cpp Template.cpp ClientCommand.cpp Socket.cpp SHA256.cpp \
- WebModules.cpp Listener.cpp
+ WebModules.cpp Listener.cpp Config.cpp
BIN_SRCS := main.cpp
LIB_OBJS := $(patsubst %cpp,%o,$(LIB_SRCS))
BIN_OBJS := $(patsubst %cpp,%o,$(BIN_SRCS))
Oops, something went wrong.

0 comments on commit 70c7745

Please sign in to comment.