update ffmpeg.git files for newer versions
[goodguy/cinelerra.git] / cinelerra-5.1 / tools / makeappimagetool / desktopfilereader.cpp
1 // system includes
2 #include <fstream>
3 #include <sstream>
4 #include <unordered_map>
5 #include <utility>
6
7 // local headers
8 #include "includes/desktopfileentry.h"
9 #include "includes/desktopfile_exceptions.h"
10 #include "includes/desktopfilereader.h"
11 #include "includes/desktopfile_util.h"
12
13 namespace linuxdeploy {
14     namespace desktopfile {
15         class DesktopFileReader::PrivateData {
16         public:
17             std::string path;
18             DesktopFile::sections_t sections;
19
20         public:
21             bool isEmpty() {
22                 return sections.empty();
23             }
24
25             void assertPathIsNotEmpty() {
26                 if (path.empty())
27                     throw IOError("empty path is not permitted");
28             }
29
30             void copyData(const std::shared_ptr<PrivateData>& other) {
31                 path = other->path;
32                 sections = other->sections;
33             }
34
35             void parse(std::istream& file) {
36                 std::string line;
37                 bool first = true;
38
39                 std::string currentSectionName;
40
41                 while (std::getline(file, line)) {
42                     if (first) {
43                         first = false;
44                         // said to allow handling of UTF-16/32 documents, not entirely sure why
45                         if (line[0] == static_cast<std::string::value_type>(0xEF)) {
46                             line.erase(0, 3);
47                             return;
48                         }
49                     }
50
51                     if (!line.empty()) {
52                         auto len = line.length();
53                         if (len > 0 &&
54                             !((len >= 2 && (line[0] == '/' && line[1] == '/')) || (len >= 1 && line[0] == '#'))) {
55                             if (line[0] == '[') {
56                                 if (line.find_last_of('[') != 0)
57                                     throw ParseError("Multiple opening [ brackets");
58
59                                 // this line apparently introduces a new section
60                                 auto closingBracketPos = line.find(']');
61                                 auto lastClosingBracketPos = line.find_last_of(']');
62
63                                 if (closingBracketPos == std::string::npos)
64                                     throw ParseError("No closing ] bracket in section header");
65                                 else if (closingBracketPos != lastClosingBracketPos)
66                                     throw ParseError("Two or more closing ] brackets in section header");
67
68                                 size_t length = len - 2;
69                                 auto title = line.substr(1, closingBracketPos - 1);
70
71                                 // set up the new section
72                                 sections.insert(std::make_pair(title, DesktopFile::section_t()));
73                                 currentSectionName = std::move(title);
74                             } else {
75                                 // we require at least one section to be present in the desktop file
76                                 if (currentSectionName.empty())
77                                     throw ParseError("No section in desktop file");
78
79                                 auto delimiterPos = line.find('=');
80                                 if (delimiterPos == std::string::npos)
81                                     throw ParseError("No = key/value delimiter found");
82
83                                 // this line should be a normal key-value pair
84                                 std::string key = line.substr(0, delimiterPos);
85                                 std::string value = line.substr(delimiterPos + 1, line.size());
86
87                                 // we can strip away any sort of leading or trailing whitespace safely
88                                 trim(key);
89                                 trim(value);
90
91                                 // empty keys are not allowed for obvious reasons
92                                 if (key.empty())
93                                     throw ParseError("Empty keys are not allowed");
94
95                                 // check if the string is a potentially localized string
96                                 // if yes, parse name and locale out, and check them for validity
97                                 std::string entryName, entryLocale;
98
99                                 auto openingBracketPos = key.find('[');
100                                 if (openingBracketPos != std::string::npos) {
101                                     entryName = key.substr(0, key.find('['));
102                                     entryLocale = key.substr(openingBracketPos, key.size());
103                                 } else {
104                                     entryName = key;
105                                 }
106
107                                 // name may only contain A-Za-z- characters according to specification
108                                 for (const char c : entryName) {
109                                     if (!(
110                                             (c >= 'A' && c <= 'Z') ||
111                                             (c >= 'a' && c <= 'z') ||
112                                             (c >= '0' && c <= '9') ||
113                                             (c == '-')
114                                         )
115                                     ) {
116                                         throw ParseError("Key " + key + " contains invalid character " + std::string{c});
117                                     }
118                                 }
119
120                                 // validate locale part
121                                 if (!entryLocale.empty()) {
122                                     static const auto errorPrefix = "Invalid localization syntax used in key " + key + ": ";
123
124                                     if (std::count(entryLocale.begin(), entryLocale.end(), '[') != 1 ||
125                                         std::count(entryLocale.begin(), entryLocale.end(), '[') != 1) {
126                                         throw ParseError(errorPrefix + "mismatching [] brackets");
127                                     }
128
129                                     // just for clarification: _this_ should never happen, given how the strings are
130                                     // split above
131                                     if (entryLocale.find('[') != 0) {
132                                         throw ParseError(errorPrefix + "invalid [ position");
133                                     }
134
135                                     if (entryLocale.find(']') != entryLocale.size()-1) {
136                                         throw ParseError(errorPrefix + "invalid ] position");
137                                     }
138
139                                     // the syntax within the brackets is not tested by intention, as some KDE apps
140                                     // use a locale called "x-test" for some reason
141                                     // strict validation of the locale part broke all AppImage builds on the KDE binary
142                                     // factory
143                                 }
144
145                                 auto& section = sections[currentSectionName];
146
147                                 // keys must be unique in the same section
148                                 if (section.find(key) != section.end())
149                                     throw ParseError("Key " + key + " found more than once");
150
151                                 section[key] = DesktopFileEntry(key, value);
152                             }
153                         }
154                     }
155                 }
156             }
157         };
158
159         DesktopFileReader::DesktopFileReader() : d(new PrivateData) {}
160
161         DesktopFileReader::DesktopFileReader(std::string path) : DesktopFileReader() {
162             d->path = std::move(path);
163             d->assertPathIsNotEmpty();
164
165             std::ifstream ifs(d->path);
166             if (!ifs)
167                 throw IOError("could not open file: " + d->path);
168
169             d->parse(ifs);
170         }
171
172         DesktopFileReader::DesktopFileReader(std::istream& is) : DesktopFileReader() {
173             d->parse(is);
174         }
175
176         DesktopFileReader::DesktopFileReader(const DesktopFileReader& other) : DesktopFileReader() {
177             d->copyData(other.d);
178         }
179
180         DesktopFileReader& DesktopFileReader::operator=(const DesktopFileReader& other) {
181             if (this != &other) {
182                 // set up a new instance of PrivateData, and copy data over from other object
183                 d.reset(new PrivateData);
184                 d->copyData(other.d);
185             }
186
187             return *this;
188         }
189
190         DesktopFileReader& DesktopFileReader::operator=(DesktopFileReader&& other) noexcept {
191             if (this != &other) {
192                 // move other object's data into this one, and remove reference there
193                 d = other.d;
194                 other.d = nullptr;
195             }
196
197             return *this;
198         }
199
200         bool DesktopFileReader::isEmpty() const {
201             return d->isEmpty();
202         }
203
204         bool DesktopFileReader::operator==(const DesktopFileReader& other) const {
205             return d->path == other.d->path && d->sections == other.d->sections;
206         }
207
208         bool DesktopFileReader::operator!=(const DesktopFileReader& other) const {
209             return !operator==(other);
210         }
211
212         std::string DesktopFileReader::path() const {
213             return d->path;
214         }
215
216         DesktopFile::sections_t DesktopFileReader::data() const {
217             return d->sections;
218         }
219
220         DesktopFile::section_t DesktopFileReader::operator[](const std::string& name) const {
221             auto it = d->sections.find(name);
222
223             // the map would lazy-initialize a new entry in case the section doesn't exist
224             // therefore explicitly checking whether the section exists, throwing an exception in case it does not
225             if (it == d->sections.end())
226                 throw UnknownSectionError(name);
227
228             return it->second;
229         }
230     }
231 }