1 /*
2  * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 #include "kludge_c++11.h"
27 
28 #include <fstream>
29 #include <algorithm>
30 #include "CfgFile.h"
31 #include "Log.h"
32 #include "Toolbox.h"
33 #include "FileUtils.h"
34 #include "ErrorHandling.h"
35 
36 
getProperties(const SectionName & sectionName) const37 const CfgFile::Properties& CfgFile::getProperties(
38     const SectionName& sectionName) const {
39     const PropertyMap::const_iterator entry = data.find(sectionName);
40     if (entry != data.end()) {
41         return entry->second;
42     }
43     return empty;
44 }
45 
46 
setPropertyValue(const SectionName & sectionName,const PropertyName & name,const tstring_array & value)47 CfgFile& CfgFile::setPropertyValue(const SectionName& sectionName,
48     const PropertyName& name, const tstring_array& value) {
49     PropertyMap::iterator entry = data.find(sectionName);
50     if (entry != data.end()) {
51         entry->second[name] = value;
52     } else {
53         Properties props;
54         props[name] = value;
55         data[sectionName] = props;
56     }
57     return *this;
58 }
59 
60 
61 namespace {
62 
expandMacros(const tstring & str,const CfgFile::Macros & macros)63 tstring expandMacros(const tstring& str, const CfgFile::Macros& macros) {
64     tstring reply = str;
65     CfgFile::Macros::const_iterator it = macros.begin();
66     const CfgFile::Macros::const_iterator end = macros.end();
67     for (; it != end; ++it) {
68         reply = tstrings::replace(reply, it->first, it->second);
69     }
70     return reply;
71 }
72 
73 } // namespace
74 
expandMacros(const Macros & macros) const75 CfgFile CfgFile::expandMacros(const Macros& macros) const {
76     CfgFile copyCfgFile = *this;
77 
78     PropertyMap::iterator mapIt = copyCfgFile.data.begin();
79     const PropertyMap::iterator mapEnd = copyCfgFile.data.end();
80     for (; mapIt != mapEnd; ++mapIt) {
81         Properties::iterator propertyIt = mapIt->second.begin();
82         const Properties::iterator propertyEnd = mapIt->second.end();
83         for (; propertyIt != propertyEnd; ++propertyIt) {
84             tstring_array::iterator strIt = propertyIt->second.begin();
85             const tstring_array::iterator strEnd = propertyIt->second.end();
86             for (; strIt != strEnd; ++strIt) {
87                 tstring newValue;
88                 while ((newValue = ::expandMacros(*strIt, macros)) != *strIt) {
89                     strIt->swap(newValue);
90                 }
91             }
92         }
93     }
94 
95     return copyCfgFile;
96 }
97 
98 
99 namespace {
100 
101 const CfgFile::SectionName* getSectionName(const tstring& str);
102 const CfgFile::PropertyName* getPropertyName(const tstring& str);
103 
104 const CfgFile::SectionName UnknownSection = CfgFile::SectionName(_T(""));
105 
106 
107 class PurgeSection {
108 public:
PurgeSection(CfgFile::SectionName & sectionName,CfgFile::Properties & sectionData,CfgFile::PropertyMap & cfgFileData)109     PurgeSection(CfgFile::SectionName& sectionName,
110             CfgFile::Properties& sectionData,
111             CfgFile::PropertyMap& cfgFileData):
112                 sectionName(sectionName), sectionData(sectionData),
113                 cfgFileData(cfgFileData) {
114     }
115 
operator ()()116     void operator ()() {
117         if (sectionName != UnknownSection && !sectionData.empty()) {
118             std::swap(cfgFileData[sectionName], sectionData);
119             sectionName = UnknownSection;
120             sectionData.clear();
121         }
122     }
123 
124 private:
125     CfgFile::SectionName& sectionName;
126     CfgFile::Properties& sectionData;
127     CfgFile::PropertyMap& cfgFileData;
128 };
129 
130 class AddProperty {
131 public:
AddProperty(const CfgFile::SectionName & sectionName,CfgFile::Properties & sectionData)132     AddProperty(const CfgFile::SectionName& sectionName,
133             CfgFile::Properties& sectionData): sectionName(sectionName),
134                 sectionData(sectionData) {
135     }
136 
operator ()(const tstring & name,const tstring & value)137     void operator ()(const tstring& name, const tstring& value) {
138         if (sectionName != UnknownSection) {
139             const CfgFile::PropertyName *known = getPropertyName(name);
140             if (known) {
141                 sectionData[*known].push_back(value);
142             }
143         }
144     }
145 
146 private:
147     const CfgFile::SectionName& sectionName;
148     CfgFile::Properties& sectionData;
149 };
150 
151 } // namepsace
152 
load(const tstring & path)153 CfgFile CfgFile::load(const tstring& path) {
154     std::ifstream input(path.c_str());
155     if (!input.good()) {
156         JP_THROW(tstrings::any() << "Error opening \"" << path << "\" file: "
157                 << lastCRTError());
158     }
159 
160     CfgFile cfgFile;
161 
162     SectionName sectionName = UnknownSection;
163     Properties sectionData;
164 
165     PurgeSection purgeSection(sectionName, sectionData, cfgFile.data);
166 
167     AddProperty addProperty(sectionName, sectionData);
168 
169     std::string utf8line;
170     int lineno = 0;
171     while (std::getline(input, utf8line)) {
172         ++lineno;
173         const tstring line = tstrings::any(utf8line).tstr();
174 
175         if (line.empty() || _T(';') == *line.begin()) {
176             // Empty line or comment, ignore.
177             continue;
178         }
179 
180         if (_T('[') == *line.begin()) {
181             const size_t endIdx = line.find_last_of(_T(']'));
182             if (endIdx == tstring::npos) {
183                 JP_THROW(tstrings::any() << "Error parsing [" << path
184                     << "] file at " << lineno << ": Missing ']' character");
185             }
186 
187             purgeSection();
188 
189             // Section begin.
190             const SectionName *knownName = getSectionName(line.substr(1, endIdx - 1));
191             if (knownName) {
192                 sectionName = *knownName;
193             } else {
194                 sectionName = UnknownSection;
195             }
196             continue;
197         }
198 
199         size_t sepIdx = 0;
200         do {
201             sepIdx = line.find_first_of(_T('='), sepIdx);
202             if (sepIdx == tstring::npos) {
203                 addProperty(line, tstring());
204                 break;
205             }
206 
207             if (sepIdx != 0 && line[sepIdx - 1] == '\\') {
208                 sepIdx++;
209                 continue;
210             }
211 
212             addProperty(line.substr(0, sepIdx), line.substr(sepIdx + 1));
213             break;
214         } while (true);
215     }
216 
217     if (!input.eof()) {
218         // Failed to process file up to the end.
219         JP_THROW(tstrings::any() << "Failed to read \"" << path
220                 << "\" file up to the end: " << lastCRTError());
221     }
222 
223     purgeSection();
224 
225     return cfgFile;
226 }
227 
228 
join(const tstring_array & values,const tstring::value_type delimiter)229 tstring join(const tstring_array& values, const tstring::value_type delimiter) {
230     return tstrings::join(values.begin(), values.end(), tstring(1, delimiter));
231 }
232 
asString(Properties::const_reference property)233 tstring CfgFile::asString(Properties::const_reference property) {
234     return *property.second.rbegin();
235 }
236 
asPathList(Properties::const_reference property)237 tstring CfgFile::asPathList(Properties::const_reference property) {
238     return join(property.second, FileUtils::pathSeparator);
239 }
240 
241 
242 #define JP_ALL_SECTIONS \
243     JP_SECTION(Application); \
244     JP_SECTION(JavaOptions); \
245     JP_SECTION(AppCDSJavaOptions); \
246     JP_SECTION(AppCDSGenerateCacheJavaOptions); \
247     JP_SECTION(ArgOptions);
248 
249 namespace SectionName {
250 #define JP_SECTION(name) const CfgFile::SectionName name(_T(#name))
251     JP_ALL_SECTIONS
252 #undef JP_SECTION
253 } // namespace SectionName
254 
255 namespace {
getSectionName(const tstring & str)256     const CfgFile::SectionName* getSectionName(const tstring& str) {
257 #define JP_SECTION(name) while (str == _T(#name)) { return &SectionName::name; }
258         JP_ALL_SECTIONS
259 #undef JP_SECTION
260             return 0;
261     }
262 }
263 
264 #undef JP_ALL_SECTIONS
265 
266 
267 #define JP_ALL_PROPERTIES \
268     JP_PROPERTY(version, "app.version"); \
269     JP_PROPERTY(mainjar, "app.mainjar"); \
270     JP_PROPERTY(mainmodule, "app.mainmodule"); \
271     JP_PROPERTY(mainclass, "app.mainclass"); \
272     JP_PROPERTY(classpath, "app.classpath"); \
273     JP_PROPERTY(modulepath, "app.modulepath"); \
274     JP_PROPERTY(runtime, "app.runtime"); \
275     JP_PROPERTY(splash, "app.splash"); \
276     JP_PROPERTY(memory, "app.memory"); \
277     JP_PROPERTY(arguments, "arguments"); \
278     JP_PROPERTY(javaOptions, "java-options"); \
279 
280 namespace PropertyName {
281 #define JP_PROPERTY(varName, name) const CfgFile::PropertyName varName(_T(name))
282     JP_ALL_PROPERTIES
283 #undef JP_PROPERTY
284 } // namespace PropertyName
285 
286 namespace {
getPropertyName(const tstring & str)287     const CfgFile::PropertyName* getPropertyName(const tstring& str) {
288 #define JP_PROPERTY(varName, name) while (str == _T(name)) { return &PropertyName::varName; }
289         JP_ALL_PROPERTIES
290 #undef JP_PROPERTY
291             return 0;
292     }
293 } // namespace
294 
295 #undef JP_ALL_PROPERTIES
296