File indexing completed on 2024-05-12 03:54:59
0001 /* 0002 SPDX-FileCopyrightText: 2014-2019 Harald Sitter <sitter@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include "kosrelease.h" 0008 0009 #include <QFile> 0010 0011 #include "kcoreaddons_debug.h" 0012 #include "kshell.h" 0013 0014 // Sets a QString var 0015 static void setVar(QString *var, const QString &value) 0016 { 0017 // Values may contain quotation marks, strip them as we have no use for them. 0018 KShell::Errors error; 0019 QStringList args = KShell::splitArgs(value, KShell::NoOptions, &error); 0020 if (error != KShell::NoError) { // Failed to parse. 0021 return; 0022 } 0023 *var = args.join(QLatin1Char(' ')); 0024 } 0025 0026 // Sets a QStringList var (i.e. splits a string value) 0027 static void setVar(QStringList *var, const QString &value) 0028 { 0029 // Instead of passing the verbatim value we manually strip any initial quotes 0030 // and then run it through KShell. At this point KShell will actually split 0031 // by spaces giving us the final QStringList. 0032 // NOTE: Splitting like this does not actually allow escaped substrings to 0033 // be handled correctly, so "kitteh \"french fries\"" would result in 0034 // three list entries. I'd argue that if someone makes an id like that 0035 // they are at fault for the bogus parsing here though as id explicitly 0036 // is required to not contain spaces even if more advanced shell escaping 0037 // is also allowed... 0038 QString value_ = value; 0039 if (value_.at(0) == QLatin1Char('"') && value_.at(value_.size() - 1) == QLatin1Char('"')) { 0040 value_.remove(0, 1); 0041 value_.remove(-1, 1); 0042 } 0043 KShell::Errors error; 0044 QStringList args = KShell::splitArgs(value_, KShell::NoOptions, &error); 0045 if (error != KShell::NoError) { // Failed to parse. 0046 return; 0047 } 0048 *var = args; 0049 } 0050 0051 static QStringList splitEntry(const QString &line) 0052 { 0053 QStringList list; 0054 const int separatorIndex = line.indexOf(QLatin1Char('=')); 0055 list << line.mid(0, separatorIndex); 0056 if (separatorIndex != -1) { 0057 list << line.mid(separatorIndex + 1, -1); 0058 } 0059 return list; 0060 } 0061 0062 static QString defaultFilePath() 0063 { 0064 if (QFile::exists(QStringLiteral("/etc/os-release"))) { 0065 return QStringLiteral("/etc/os-release"); 0066 } else if (QFile::exists(QStringLiteral("/usr/lib/os-release"))) { 0067 return QStringLiteral("/usr/lib/os-release"); 0068 } else { 0069 return QString(); 0070 } 0071 } 0072 0073 class KOSReleasePrivate 0074 { 0075 public: 0076 explicit KOSReleasePrivate(QString filePath) 0077 : name(QStringLiteral("Linux")) 0078 , id(QStringLiteral("linux")) 0079 , prettyName(QStringLiteral("Linux")) 0080 { 0081 // Default values for non-optional fields set above ^. 0082 0083 QHash<QString, QString *> stringHash = {{QStringLiteral("NAME"), &name}, 0084 {QStringLiteral("VERSION"), &version}, 0085 {QStringLiteral("ID"), &id}, 0086 // idLike is not a QString, special handling below! 0087 {QStringLiteral("VERSION_CODENAME"), &versionCodename}, 0088 {QStringLiteral("VERSION_ID"), &versionId}, 0089 {QStringLiteral("PRETTY_NAME"), &prettyName}, 0090 {QStringLiteral("ANSI_COLOR"), &ansiColor}, 0091 {QStringLiteral("CPE_NAME"), &cpeName}, 0092 {QStringLiteral("HOME_URL"), &homeUrl}, 0093 {QStringLiteral("DOCUMENTATION_URL"), &documentationUrl}, 0094 {QStringLiteral("SUPPORT_URL"), &supportUrl}, 0095 {QStringLiteral("BUG_REPORT_URL"), &bugReportUrl}, 0096 {QStringLiteral("PRIVACY_POLICY_URL"), &privacyPolicyUrl}, 0097 {QStringLiteral("BUILD_ID"), &buildId}, 0098 {QStringLiteral("VARIANT"), &variant}, 0099 {QStringLiteral("VARIANT_ID"), &variantId}, 0100 {QStringLiteral("LOGO"), &logo}}; 0101 0102 if (filePath.isEmpty()) { 0103 filePath = defaultFilePath(); 0104 } 0105 if (filePath.isEmpty()) { 0106 qCWarning(KCOREADDONS_DEBUG) << "Failed to find os-release file!"; 0107 return; 0108 } 0109 0110 QFile file(filePath); 0111 // NOTE: The os-release specification defines default values for specific 0112 // fields which means that even if we can not read the os-release file 0113 // we have sort of expected default values to use. 0114 // TODO: it might still be handy to indicate to the outside whether 0115 // fallback values are being used or not. 0116 file.open(QIODevice::ReadOnly | QIODevice::Text); 0117 QString line; 0118 QStringList parts; 0119 while (!file.atEnd()) { 0120 // Trimmed to handle indented comment lines properly 0121 line = QString::fromLatin1(file.readLine()).trimmed(); 0122 0123 if (line.startsWith(QLatin1Char('#'))) { 0124 // Comment line 0125 // Lines beginning with "#" shall be ignored as comments. 0126 continue; 0127 } 0128 0129 parts = splitEntry(line); 0130 0131 if (parts.size() != 2) { 0132 // Line has no =, must be invalid. 0133 qCDebug(KCOREADDONS_DEBUG) << "Unexpected/invalid os-release line:" << line; 0134 continue; 0135 } 0136 0137 QString key = parts.at(0); 0138 QString value = parts.at(1).trimmed(); 0139 0140 if (QString *var = stringHash.value(key, nullptr)) { 0141 setVar(var, value); 0142 continue; 0143 } 0144 0145 // ID_LIKE is a list and parsed as such (rather than a QString). 0146 if (key == QLatin1String("ID_LIKE")) { 0147 setVar(&idLike, value); 0148 continue; 0149 } 0150 0151 // os-release explicitly allows for vendor specific additions, we'll 0152 // collect them as strings and exposes them as "extras". 0153 QString parsedValue; 0154 setVar(&parsedValue, value); 0155 extras.insert(key, parsedValue); 0156 } 0157 } 0158 0159 QString name; 0160 QString version; 0161 QString id; 0162 QStringList idLike; 0163 QString versionCodename; 0164 QString versionId; 0165 QString prettyName; 0166 QString ansiColor; 0167 QString cpeName; 0168 QString homeUrl; 0169 QString documentationUrl; 0170 QString supportUrl; 0171 QString bugReportUrl; 0172 QString privacyPolicyUrl; 0173 QString buildId; 0174 QString variant; 0175 QString variantId; 0176 QString logo; 0177 0178 QHash<QString, QString> extras; 0179 }; 0180 0181 KOSRelease::KOSRelease(const QString &filePath) 0182 : d(new KOSReleasePrivate(filePath)) 0183 { 0184 } 0185 0186 KOSRelease::~KOSRelease() = default; 0187 0188 QString KOSRelease::name() const 0189 { 0190 return d->name; 0191 } 0192 0193 QString KOSRelease::version() const 0194 { 0195 return d->version; 0196 } 0197 0198 QString KOSRelease::id() const 0199 { 0200 return d->id; 0201 } 0202 0203 QStringList KOSRelease::idLike() const 0204 { 0205 return d->idLike; 0206 } 0207 0208 QString KOSRelease::versionCodename() const 0209 { 0210 return d->versionCodename; 0211 } 0212 0213 QString KOSRelease::versionId() const 0214 { 0215 return d->versionId; 0216 } 0217 0218 QString KOSRelease::prettyName() const 0219 { 0220 return d->prettyName; 0221 } 0222 0223 QString KOSRelease::ansiColor() const 0224 { 0225 return d->ansiColor; 0226 } 0227 0228 QString KOSRelease::cpeName() const 0229 { 0230 return d->cpeName; 0231 } 0232 0233 QString KOSRelease::homeUrl() const 0234 { 0235 return d->homeUrl; 0236 } 0237 0238 QString KOSRelease::documentationUrl() const 0239 { 0240 return d->documentationUrl; 0241 } 0242 0243 QString KOSRelease::supportUrl() const 0244 { 0245 return d->supportUrl; 0246 } 0247 0248 QString KOSRelease::bugReportUrl() const 0249 { 0250 return d->bugReportUrl; 0251 } 0252 0253 QString KOSRelease::privacyPolicyUrl() const 0254 { 0255 return d->privacyPolicyUrl; 0256 } 0257 0258 QString KOSRelease::buildId() const 0259 { 0260 return d->buildId; 0261 } 0262 0263 QString KOSRelease::variant() const 0264 { 0265 return d->variant; 0266 } 0267 0268 QString KOSRelease::variantId() const 0269 { 0270 return d->variantId; 0271 } 0272 0273 QString KOSRelease::logo() const 0274 { 0275 return d->logo; 0276 } 0277 0278 QStringList KOSRelease::extraKeys() const 0279 { 0280 return d->extras.keys(); 0281 } 0282 0283 QString KOSRelease::extraValue(const QString &key) const 0284 { 0285 return d->extras.value(key); 0286 }