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 }