1/*
2 SPDX-FileCopyrightText: 2014-2019 Harald Sitter <sitter@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6
7#include "kosrelease.h"
8
9#include <QFile>
10
11#include "kcoreaddons_debug.h"
12#include "kshell.h"
13
14// Sets a QString var
15static void setVar(QString *var, const QString &value)
16{
17 // Values may contain quotation marks, strip them as we have no use for them.
18 KShell::Errors error;
19 QStringList args = KShell::splitArgs(cmd: value, flags: KShell::NoOptions, err: &error);
20 if (error != KShell::NoError) { // Failed to parse.
21 return;
22 }
23 *var = args.join(sep: QLatin1Char(' '));
24}
25
26// Sets a QStringList var (i.e. splits a string value)
27static void setVar(QStringList *var, const QString &value)
28{
29 // Instead of passing the verbatim value we manually strip any initial quotes
30 // and then run it through KShell. At this point KShell will actually split
31 // by spaces giving us the final QStringList.
32 // NOTE: Splitting like this does not actually allow escaped substrings to
33 // be handled correctly, so "kitteh \"french fries\"" would result in
34 // three list entries. I'd argue that if someone makes an id like that
35 // they are at fault for the bogus parsing here though as id explicitly
36 // is required to not contain spaces even if more advanced shell escaping
37 // is also allowed...
38 QString value_ = value;
39 if (value_.at(i: 0) == QLatin1Char('"') && value_.at(i: value_.size() - 1) == QLatin1Char('"')) {
40 value_.remove(i: 0, len: 1);
41 value_.remove(i: -1, len: 1);
42 }
43 KShell::Errors error;
44 QStringList args = KShell::splitArgs(cmd: value_, flags: KShell::NoOptions, err: &error);
45 if (error != KShell::NoError) { // Failed to parse.
46 return;
47 }
48 *var = args;
49}
50
51static QStringList splitEntry(const QString &line)
52{
53 QStringList list;
54 const int separatorIndex = line.indexOf(ch: QLatin1Char('='));
55 list << line.mid(position: 0, n: separatorIndex);
56 if (separatorIndex != -1) {
57 list << line.mid(position: separatorIndex + 1, n: -1);
58 }
59 return list;
60}
61
62static QString defaultFilePath()
63{
64 if (QFile::exists(QStringLiteral("/etc/os-release"))) {
65 return QStringLiteral("/etc/os-release");
66 } else if (QFile::exists(QStringLiteral("/usr/lib/os-release"))) {
67 return QStringLiteral("/usr/lib/os-release");
68 } else {
69 return QString();
70 }
71}
72
73class KOSReleasePrivate
74{
75public:
76 explicit KOSReleasePrivate(QString filePath)
77 : name(QStringLiteral("Linux"))
78 , id(QStringLiteral("linux"))
79 , prettyName(QStringLiteral("Linux"))
80 {
81 // Default values for non-optional fields set above ^.
82
83 QHash<QString, QString *> stringHash = {{QStringLiteral("NAME"), &name},
84 {QStringLiteral("VERSION"), &version},
85 {QStringLiteral("ID"), &id},
86 // idLike is not a QString, special handling below!
87 {QStringLiteral("VERSION_CODENAME"), &versionCodename},
88 {QStringLiteral("VERSION_ID"), &versionId},
89 {QStringLiteral("PRETTY_NAME"), &prettyName},
90 {QStringLiteral("ANSI_COLOR"), &ansiColor},
91 {QStringLiteral("CPE_NAME"), &cpeName},
92 {QStringLiteral("HOME_URL"), &homeUrl},
93 {QStringLiteral("DOCUMENTATION_URL"), &documentationUrl},
94 {QStringLiteral("SUPPORT_URL"), &supportUrl},
95 {QStringLiteral("BUG_REPORT_URL"), &bugReportUrl},
96 {QStringLiteral("PRIVACY_POLICY_URL"), &privacyPolicyUrl},
97 {QStringLiteral("BUILD_ID"), &buildId},
98 {QStringLiteral("VARIANT"), &variant},
99 {QStringLiteral("VARIANT_ID"), &variantId},
100 {QStringLiteral("LOGO"), &logo}};
101
102 if (filePath.isEmpty()) {
103 filePath = defaultFilePath();
104 }
105 if (filePath.isEmpty()) {
106 qCWarning(KCOREADDONS_DEBUG) << "Failed to find os-release file!";
107 return;
108 }
109
110 QFile file(filePath);
111 // NOTE: The os-release specification defines default values for specific
112 // fields which means that even if we can not read the os-release file
113 // we have sort of expected default values to use.
114 // TODO: it might still be handy to indicate to the outside whether
115 // fallback values are being used or not.
116 if (!file.open(flags: QIODevice::ReadOnly | QIODevice::Text)) {
117 qCWarning(KCOREADDONS_DEBUG) << "Failed to open os-release file!" << file.errorString();
118 return;
119 }
120 QString line;
121 QStringList parts;
122 while (!file.atEnd()) {
123 // Trimmed to handle indented comment lines properly
124 line = QString::fromUtf8(ba: file.readLine()).trimmed();
125
126 if (line.startsWith(c: QLatin1Char('#'))) {
127 // Comment line
128 // Lines beginning with "#" shall be ignored as comments.
129 continue;
130 }
131
132 parts = splitEntry(line);
133
134 if (parts.size() != 2) {
135 // Line has no =, must be invalid.
136 qCDebug(KCOREADDONS_DEBUG) << "Unexpected/invalid os-release line:" << line;
137 continue;
138 }
139
140 QString key = parts.at(i: 0);
141 QString value = parts.at(i: 1).trimmed();
142
143 if (QString *var = stringHash.value(key, defaultValue: nullptr)) {
144 setVar(var, value);
145 continue;
146 }
147
148 // ID_LIKE is a list and parsed as such (rather than a QString).
149 if (key == QLatin1String("ID_LIKE")) {
150 setVar(var: &idLike, value);
151 continue;
152 }
153
154 // os-release explicitly allows for vendor specific additions, we'll
155 // collect them as strings and exposes them as "extras".
156 QString parsedValue;
157 setVar(var: &parsedValue, value);
158 extras.insert(key, value: parsedValue);
159 }
160 }
161
162 QString name;
163 QString version;
164 QString id;
165 QStringList idLike;
166 QString versionCodename;
167 QString versionId;
168 QString prettyName;
169 QString ansiColor;
170 QString cpeName;
171 QString homeUrl;
172 QString documentationUrl;
173 QString supportUrl;
174 QString bugReportUrl;
175 QString privacyPolicyUrl;
176 QString buildId;
177 QString variant;
178 QString variantId;
179 QString ;
180
181 QHash<QString, QString> extras;
182};
183
184KOSRelease::KOSRelease(const QString &filePath)
185 : d(new KOSReleasePrivate(filePath))
186{
187}
188
189KOSRelease::~KOSRelease() = default;
190
191QString KOSRelease::name() const
192{
193 return d->name;
194}
195
196QString KOSRelease::version() const
197{
198 return d->version;
199}
200
201QString KOSRelease::id() const
202{
203 return d->id;
204}
205
206QStringList KOSRelease::idLike() const
207{
208 return d->idLike;
209}
210
211QString KOSRelease::versionCodename() const
212{
213 return d->versionCodename;
214}
215
216QString KOSRelease::versionId() const
217{
218 return d->versionId;
219}
220
221QString KOSRelease::prettyName() const
222{
223 return d->prettyName;
224}
225
226QString KOSRelease::ansiColor() const
227{
228 return d->ansiColor;
229}
230
231QString KOSRelease::cpeName() const
232{
233 return d->cpeName;
234}
235
236QString KOSRelease::homeUrl() const
237{
238 return d->homeUrl;
239}
240
241QString KOSRelease::documentationUrl() const
242{
243 return d->documentationUrl;
244}
245
246QString KOSRelease::supportUrl() const
247{
248 return d->supportUrl;
249}
250
251QString KOSRelease::bugReportUrl() const
252{
253 return d->bugReportUrl;
254}
255
256QString KOSRelease::privacyPolicyUrl() const
257{
258 return d->privacyPolicyUrl;
259}
260
261QString KOSRelease::buildId() const
262{
263 return d->buildId;
264}
265
266QString KOSRelease::variant() const
267{
268 return d->variant;
269}
270
271QString KOSRelease::variantId() const
272{
273 return d->variantId;
274}
275
276QString KOSRelease::logo() const
277{
278 return d->logo;
279}
280
281QStringList KOSRelease::extraKeys() const
282{
283 return d->extras.keys();
284}
285
286QString KOSRelease::extraValue(const QString &key) const
287{
288 return d->extras.value(key);
289}
290

source code of kcoreaddons/src/lib/util/kosrelease.cpp