1 /*
2  * Copyright (C) 2018 Red Hat, Inc.
3  *
4  * Licensed under the GNU Lesser General Public License Version 2.1
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 
21 #include "ConfigParser.hpp"
22 #include "../utils/iniparser/iniparser.hpp"
23 
24 #include <algorithm>
25 #include <fstream>
26 
27 namespace libdnf {
28 
substitute(std::string & text,const std::map<std::string,std::string> & substitutions)29 void ConfigParser::substitute(std::string & text,
30     const std::map<std::string, std::string> & substitutions)
31 {
32     auto start = text.find_first_of("$");
33     while (start != text.npos)
34     {
35         auto variable = start + 1;
36         if (variable >= text.length())
37             break;
38         bool bracket;
39         if (text[variable] == '{') {
40             bracket = true;
41             if (++variable >= text.length())
42                 break;
43         } else
44             bracket = false;
45         auto it = std::find_if_not(text.begin()+variable, text.end(),
46             [](char c){return std::isalnum(c) || c=='_';});
47         if (bracket && it == text.end())
48             break;
49         auto pastVariable = std::distance(text.begin(), it);
50         if (bracket && *it != '}') {
51             start = text.find_first_of("$", pastVariable);
52             continue;
53         }
54         auto subst = substitutions.find(text.substr(variable, pastVariable - variable));
55         if (subst != substitutions.end()) {
56             if (bracket)
57                 ++pastVariable;
58             text.replace(start, pastVariable - start, subst->second);
59             start = text.find_first_of("$", start + subst->second.length());
60         } else {
61             start = text.find_first_of("$", pastVariable);
62         }
63     }
64 }
65 
read(ConfigParser & cfgParser,IniParser & parser)66 static void read(ConfigParser & cfgParser, IniParser & parser)
67 {
68     IniParser::ItemType readedType;
69     while ((readedType = parser.next()) != IniParser::ItemType::END_OF_INPUT) {
70         auto section = parser.getSection();
71         if (readedType == IniParser::ItemType::SECTION) {
72             cfgParser.addSection(std::move(section), std::move(parser.getRawItem()));
73         }
74         else if (readedType == IniParser::ItemType::KEY_VAL) {
75             cfgParser.setValue(section, std::move(parser.getKey()), std::move(parser.getValue()), std::move(parser.getRawItem()));
76         }
77         else if (readedType == IniParser::ItemType::COMMENT_LINE || readedType == IniParser::ItemType::EMPTY_LINE) {
78             if (section.empty())
79                 cfgParser.getHeader() += parser.getRawItem();
80             else
81                 cfgParser.addCommentLine(section, std::move(parser.getRawItem()));
82         }
83     }
84 }
85 
read(const std::string & filePath)86 void ConfigParser::read(const std::string & filePath)
87 {
88     try {
89         IniParser parser(filePath);
90         ::libdnf::read(*this, parser);
91     } catch (const IniParser::CantOpenFile & e) {
92         throw CantOpenFile(e.what());
93     } catch (const IniParser::Exception & e) {
94         throw ParsingError(e.what() + std::string(" at line ") + std::to_string(e.getLineNumber()));
95     }
96 }
97 
read(std::unique_ptr<std::istream> && inputStream)98 void ConfigParser::read(std::unique_ptr<std::istream> && inputStream)
99 {
100     try {
101         IniParser parser(std::move(inputStream));
102         ::libdnf::read(*this, parser);
103     } catch (const IniParser::CantOpenFile & e) {
104         throw CantOpenFile(e.what());
105     } catch (const IniParser::Exception & e) {
106         throw ParsingError(e.what() + std::string(" at line ") + std::to_string(e.getLineNumber()));
107     }
108 }
109 
createRawItem(const std::string & value,const std::string & oldRawItem)110 static std::string createRawItem(const std::string & value, const std::string & oldRawItem)
111 {
112     auto eqlPos = oldRawItem.find('=');
113     if (eqlPos == oldRawItem.npos)
114         return "";
115     auto valuepos = oldRawItem.find_first_not_of(" \t", eqlPos + 1);
116     auto keyAndDelimLength = valuepos != oldRawItem.npos ? valuepos : oldRawItem.length();
117     return oldRawItem.substr(0, keyAndDelimLength) + value + '\n';
118 }
119 
setValue(const std::string & section,const std::string & key,const std::string & value)120 void ConfigParser::setValue(const std::string & section, const std::string & key, const std::string & value)
121 {
122     auto rawIter = rawItems.find(section + ']' + key);
123     auto raw = createRawItem(value, rawIter != rawItems.end() ? rawIter->second : "");
124     setValue(section, key, value, raw);
125 }
126 
setValue(const std::string & section,std::string && key,std::string && value)127 void ConfigParser::setValue(const std::string & section, std::string && key, std::string && value)
128 {
129     auto rawIter = rawItems.find(section + ']' + key);
130     auto raw = createRawItem(value, rawIter != rawItems.end() ? rawIter->second : "");
131     setValue(section, std::move(key), std::move(value), std::move(raw));
132 }
133 
134 const std::string &
getValue(const std::string & section,const std::string & key) const135 ConfigParser::getValue(const std::string & section, const std::string & key) const
136 {
137     auto sect = data.find(section);
138     if (sect == data.end())
139         throw MissingSection("OptionReader::getValue(): Missing section " + section);
140     auto keyVal = sect->second.find(key);
141     if (keyVal == sect->second.end())
142         throw MissingOption("OptionReader::getValue(): Missing option " + key +
143             " in section " + section);
144     return keyVal->second;
145 }
146 
147 std::string
getSubstitutedValue(const std::string & section,const std::string & key) const148 ConfigParser::getSubstitutedValue(const std::string & section, const std::string & key) const
149 {
150     auto ret = getValue(section, key);
151     substitute(ret, substitutions);
152     return ret;
153 }
154 
writeKeyVals(std::ostream & out,const std::string & section,const ConfigParser::Container::mapped_type & keyValMap,const std::map<std::string,std::string> & rawItems)155 static void writeKeyVals(std::ostream & out, const std::string & section, const ConfigParser::Container::mapped_type & keyValMap, const std::map<std::string, std::string> & rawItems)
156 {
157     for (const auto & keyVal : keyValMap) {
158         auto first = keyVal.first[0];
159         if (first == '#' || first == ';')
160             out << keyVal.second;
161         else {
162             auto rawItem = rawItems.find(section + ']' + keyVal.first);
163             if (rawItem != rawItems.end())
164                 out << rawItem->second;
165             else {
166                 out << keyVal.first << "=";
167                 for (const auto chr : keyVal.second) {
168                     out << chr;
169                     if (chr == '\n')
170                         out << " ";
171                 }
172                 out << "\n";
173             }
174         }
175     }
176 }
177 
writeSection(std::ostream & out,const std::string & section,const ConfigParser::Container::mapped_type & keyValMap,const std::map<std::string,std::string> & rawItems)178 static void writeSection(std::ostream & out, const std::string & section, const ConfigParser::Container::mapped_type & keyValMap, const std::map<std::string, std::string> & rawItems)
179 {
180     auto rawItem = rawItems.find(section);
181     if (rawItem != rawItems.end())
182         out << rawItem->second;
183     else
184         out << "[" << section << "]" << "\n";
185     writeKeyVals(out, section, keyValMap, rawItems);
186 }
187 
write(const std::string & filePath,bool append) const188 void ConfigParser::write(const std::string & filePath, bool append) const
189 {
190     std::ofstream ofs;
191     ofs.exceptions(std::ofstream::failbit | std::ofstream::badbit);
192     ofs.open(filePath, append ? std::ofstream::app : std::ofstream::trunc);
193     write(ofs);
194 }
195 
write(const std::string & filePath,bool append,const std::string & section) const196 void ConfigParser::write(const std::string & filePath, bool append, const std::string & section) const
197 {
198     auto sit = data.find(section);
199     if (sit == data.end())
200         throw MissingSection("ConfigParser::write(): Missing section " + section);
201     std::ofstream ofs;
202     ofs.exceptions(std::ofstream::failbit | std::ofstream::badbit);
203     ofs.open(filePath, append ? std::ofstream::app : std::ofstream::trunc);
204     writeSection(ofs, sit->first, sit->second, rawItems);
205 }
206 
write(std::ostream & outputStream) const207 void ConfigParser::write(std::ostream & outputStream) const
208 {
209     outputStream << header;
210     for (const auto & section : data) {
211         writeSection(outputStream, section.first, section.second, rawItems);
212     }
213 }
214 
write(std::ostream & outputStream,const std::string & section) const215 void ConfigParser::write(std::ostream & outputStream, const std::string & section) const
216 {
217     auto sit = data.find(section);
218     if (sit == data.end())
219         throw MissingSection("ConfigParser::write(): Missing section " + section);
220     writeSection(outputStream, sit->first, sit->second, rawItems);
221 }
222 
223 }
224