Coverage Report

Created: 2026-01-10 06:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/znc/src/Config.cpp
Line
Count
Source
1
/*
2
 * Copyright (C) 2004-2026 ZNC, see the NOTICE file for details.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
#include <znc/Config.h>
18
#include <znc/FileUtils.h>
19
#include <stack>
20
#include <sstream>
21
22
struct ConfigStackEntry {
23
    CString sTag;
24
    CString sName;
25
    CConfig Config;
26
27
    ConfigStackEntry(const CString& Tag, const CString Name)
28
0
        : sTag(Tag), sName(Name), Config() {}
29
};
30
31
0
CConfigEntry::CConfigEntry() : m_pSubConfig(nullptr) {}
32
33
CConfigEntry::CConfigEntry(const CConfig& Config)
34
0
    : m_pSubConfig(new CConfig(Config)) {}
35
36
0
CConfigEntry::CConfigEntry(const CConfigEntry& other) : m_pSubConfig(nullptr) {
37
0
    if (other.m_pSubConfig) m_pSubConfig = new CConfig(*other.m_pSubConfig);
38
0
}
39
40
0
CConfigEntry::~CConfigEntry() { delete m_pSubConfig; }
41
42
0
CConfigEntry& CConfigEntry::operator=(const CConfigEntry& other) {
43
0
    delete m_pSubConfig;
44
0
    if (other.m_pSubConfig)
45
0
        m_pSubConfig = new CConfig(*other.m_pSubConfig);
46
0
    else
47
0
        m_pSubConfig = nullptr;
48
0
    return *this;
49
0
}
50
51
0
bool CConfig::Parse(CFile& file, CString& sErrorMsg) {
52
0
    CString sLine;
53
0
    unsigned int uLineNum = 0;
54
0
    CConfig* pActiveConfig = this;
55
0
    std::stack<ConfigStackEntry> ConfigStack;
56
0
    bool bCommented = false;  // support for /**/ style comments
57
58
0
    if (!file.Seek(0)) {
59
0
        sErrorMsg = "Could not seek to the beginning of the config.";
60
0
        return false;
61
0
    }
62
63
0
    while (file.ReadLine(sLine)) {
64
0
        uLineNum++;
65
66
0
#define ERROR(arg)                                             \
67
0
    do {                                                       \
68
0
        std::stringstream stream;                              \
69
0
        stream << "Error on line " << uLineNum << ": " << arg; \
70
0
        sErrorMsg = stream.str();                              \
71
0
        m_SubConfigNameSets.clear();                           \
72
0
        m_SubConfigs.clear();                                  \
73
0
        m_ConfigEntries.clear();                               \
74
0
        return false;                                          \
75
0
    } while (0)
76
77
        // Remove all leading spaces and trailing line endings
78
0
        sLine.TrimLeft();
79
0
        sLine.TrimRight("\r\n");
80
81
0
        if (bCommented || sLine.StartsWith("/*")) {
82
            /* Does this comment end on the same line again? */
83
0
            bCommented = (!sLine.EndsWith("*/"));
84
85
0
            continue;
86
0
        }
87
88
0
        if ((sLine.empty()) || (sLine.StartsWith("#")) ||
89
0
            (sLine.StartsWith("//"))) {
90
0
            continue;
91
0
        }
92
93
0
        if ((sLine.StartsWith("<")) && (sLine.EndsWith(">"))) {
94
0
            sLine.LeftChomp();
95
0
            sLine.RightChomp();
96
0
            sLine.Trim();
97
98
0
            CString sTag = sLine.Token(0);
99
0
            CString sValue = sLine.Token(1, true);
100
101
0
            sTag.Trim();
102
0
            sValue.Trim();
103
104
0
            if (sTag.TrimPrefix("/")) {
105
0
                if (!sValue.empty())
106
0
                    ERROR("Malformated closing tag. Expected \"</" << sTag
107
0
                                                                   << ">\".");
108
0
                if (ConfigStack.empty())
109
0
                    ERROR("Closing tag \"" << sTag << "\" which is not open.");
110
111
0
                const struct ConfigStackEntry& entry = ConfigStack.top();
112
0
                CConfig myConfig(entry.Config);
113
0
                CString sName(entry.sName);
114
115
0
                if (!sTag.Equals(entry.sTag))
116
0
                    ERROR("Closing tag \"" << sTag << "\" which is not open.");
117
118
                // This breaks entry
119
0
                ConfigStack.pop();
120
121
0
                if (ConfigStack.empty())
122
0
                    pActiveConfig = this;
123
0
                else
124
0
                    pActiveConfig = &ConfigStack.top().Config;
125
126
0
                const auto sTagLower = sTag.AsLower();
127
0
                auto& nameset = pActiveConfig->m_SubConfigNameSets[sTagLower];
128
129
0
                if (nameset.find(sName) != nameset.end())
130
0
                    ERROR("Duplicate entry for tag \"" << sTag << "\" name \""
131
0
                                                       << sName << "\".");
132
133
0
                nameset.insert(sName);
134
0
                pActiveConfig->m_SubConfigs[sTagLower].emplace_back(sName,
135
0
                                                                    myConfig);
136
0
            } else {
137
0
                if (sValue.empty())
138
0
                    ERROR("Empty block name at begin of block.");
139
0
                ConfigStack.push(ConfigStackEntry(sTag.AsLower(), sValue));
140
0
                pActiveConfig = &ConfigStack.top().Config;
141
0
            }
142
143
0
            continue;
144
0
        }
145
146
        // If we have a regular line, figure out where it goes
147
0
        CString sName = sLine.Token(0, false, "=");
148
0
        CString sValue = sLine.Token(1, true, "=");
149
150
        // Only remove the first space, people might want
151
        // leading spaces (e.g. in the MOTD).
152
0
        sValue.TrimPrefix(" ");
153
154
        // We don't have any names with spaces, trim all
155
        // leading/trailing spaces.
156
0
        sName.Trim();
157
158
0
        if (sName.empty() || sValue.empty()) ERROR("Malformed line");
159
160
0
        CString sNameLower = sName.AsLower();
161
0
        pActiveConfig->m_ConfigEntries[sNameLower].push_back(sValue);
162
0
    }
163
164
0
    if (bCommented) ERROR("Comment not closed at end of file.");
165
166
0
    if (!ConfigStack.empty()) {
167
0
        const CString& sTag = ConfigStack.top().sTag;
168
0
        ERROR(
169
0
            "Not all tags are closed at the end of the file. Inner-most open "
170
0
            "tag is \""
171
0
            << sTag << "\".");
172
0
    }
173
174
0
    return true;
175
0
}
176
177
0
void CConfig::Write(CFile& File, unsigned int iIndentation) {
178
0
    CString sIndentation = CString(iIndentation, '\t');
179
180
0
    auto SingleLine = [](const CString& s) {
181
0
        return s.Replace_n("\r", "").Replace_n("\n", "");
182
0
    };
183
184
0
    for (const auto& it : m_ConfigEntries) {
185
0
        for (const CString& sValue : it.second) {
186
0
            File.Write(SingleLine(sIndentation + it.first + " = " + sValue) +
187
0
                       "\n");
188
0
        }
189
0
    }
190
191
0
    for (const auto& it : m_SubConfigs) {
192
0
        for (const auto& it2 : it.second) {
193
0
            File.Write("\n");
194
195
0
            File.Write(SingleLine(sIndentation + "<" + it.first + " " +
196
0
                                  it2.first + ">") +
197
0
                       "\n");
198
0
            it2.second.m_pSubConfig->Write(File, iIndentation + 1);
199
0
            File.Write(SingleLine(sIndentation + "</" + it.first + ">") + "\n");
200
0
        }
201
0
    }
202
0
}