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(c: 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 file.open(flags: QIODevice::ReadOnly | QIODevice::Text);
117 QString line;
118 QStringList parts;
119 while (!file.atEnd()) {
120 // Trimmed to handle indented comment lines properly
121 line = QString::fromLatin1(ba: file.readLine()).trimmed();
122
123 if (line.startsWith(c: QLatin1Char('#'))) {
124 // Comment line
125 // Lines beginning with "#" shall be ignored as comments.
126 continue;
127 }
128
129 parts = splitEntry(line);
130
131 if (parts.size() != 2) {
132 // Line has no =, must be invalid.
133 qCDebug(KCOREADDONS_DEBUG) << "Unexpected/invalid os-release line:" << line;
134 continue;
135 }
136
137 QString key = parts.at(i: 0);
138 QString value = parts.at(i: 1).trimmed();
139
140 if (QString *var = stringHash.value(key, defaultValue: nullptr)) {
141 setVar(var, value);
142 continue;
143 }
144
145 // ID_LIKE is a list and parsed as such (rather than a QString).
146 if (key == QLatin1String("ID_LIKE")) {
147 setVar(var: &idLike, value);
148 continue;
149 }
150
151 // os-release explicitly allows for vendor specific additions, we'll
152 // collect them as strings and exposes them as "extras".
153 QString parsedValue;
154 setVar(var: &parsedValue, value);
155 extras.insert(key, value: parsedValue);
156 }
157 }
158
159 QString name;
160 QString version;
161 QString id;
162 QStringList idLike;
163 QString versionCodename;
164 QString versionId;
165 QString prettyName;
166 QString ansiColor;
167 QString cpeName;
168 QString homeUrl;
169 QString documentationUrl;
170 QString supportUrl;
171 QString bugReportUrl;
172 QString privacyPolicyUrl;
173 QString buildId;
174 QString variant;
175 QString variantId;
176 QString ;
177
178 QHash<QString, QString> extras;
179};
180
181KOSRelease::KOSRelease(const QString &filePath)
182 : d(new KOSReleasePrivate(filePath))
183{
184}
185
186KOSRelease::~KOSRelease() = default;
187
188QString KOSRelease::name() const
189{
190 return d->name;
191}
192
193QString KOSRelease::version() const
194{
195 return d->version;
196}
197
198QString KOSRelease::id() const
199{
200 return d->id;
201}
202
203QStringList KOSRelease::idLike() const
204{
205 return d->idLike;
206}
207
208QString KOSRelease::versionCodename() const
209{
210 return d->versionCodename;
211}
212
213QString KOSRelease::versionId() const
214{
215 return d->versionId;
216}
217
218QString KOSRelease::prettyName() const
219{
220 return d->prettyName;
221}
222
223QString KOSRelease::ansiColor() const
224{
225 return d->ansiColor;
226}
227
228QString KOSRelease::cpeName() const
229{
230 return d->cpeName;
231}
232
233QString KOSRelease::homeUrl() const
234{
235 return d->homeUrl;
236}
237
238QString KOSRelease::documentationUrl() const
239{
240 return d->documentationUrl;
241}
242
243QString KOSRelease::supportUrl() const
244{
245 return d->supportUrl;
246}
247
248QString KOSRelease::bugReportUrl() const
249{
250 return d->bugReportUrl;
251}
252
253QString KOSRelease::privacyPolicyUrl() const
254{
255 return d->privacyPolicyUrl;
256}
257
258QString KOSRelease::buildId() const
259{
260 return d->buildId;
261}
262
263QString KOSRelease::variant() const
264{
265 return d->variant;
266}
267
268QString KOSRelease::variantId() const
269{
270 return d->variantId;
271}
272
273QString KOSRelease::logo() const
274{
275 return d->logo;
276}
277
278QStringList KOSRelease::extraKeys() const
279{
280 return d->extras.keys();
281}
282
283QString KOSRelease::extraValue(const QString &key) const
284{
285 return d->extras.value(key);
286}
287

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