Revision control

++ // Copyright (C) 2016 Denis Shienkov <denis.shienkov@gmail.com>
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:critical reason:data-parser
++
++ #include "qserialportinfo.h"
++ #include "qserialportinfo_p.h"
++ #include "qserialport_p.h"
++
++ #include <QtCore/qdatastream.h>
++ #include <QtCore/qlist.h>
++ #include <QtCore/qdir.h>
++
++ #include <errno.h>
++ #include <sys/types.h> // kill
++ #include <signal.h> // kill
++
++ #include <sys/sysctl.h> // sysctl, sysctlnametomib
++
++ QT_BEGIN_NAMESPACE
++
++ static QString deviceProperty(const QString &source, const QByteArray &pattern)
++ {
++ const qsizetype firstbound = source.indexOf(QLatin1String(pattern));
++ if (firstbound == -1)
++ return QString();
++ qsizetype lastbound = source.indexOf(QLatin1Char(' '), firstbound);
++ if (lastbound == -1)
++ lastbound = source.size();
++ return source.mid(firstbound + pattern.size(), lastbound - firstbound - pattern.size());
++ }
++
++ static QString deviceName(const QString &pnpinfo)
++ {
++ return deviceProperty(pnpinfo, "ttyname=");
++ }
++
++ static QString deviceCount(const QString &pnpinfo)
++ {
++ return deviceProperty(pnpinfo, "ttyports=");
++ }
++
++ static quint16 deviceProductIdentifier(const QString &pnpinfo, bool &hasIdentifier)
++ {
++ QString result = deviceProperty(pnpinfo, "product=");
++ return result.toInt(&hasIdentifier, 16);
++ }
++
++ static quint16 deviceVendorIdentifier(const QString &pnpinfo, bool &hasIdentifier)
++ {
++ QString result = deviceProperty(pnpinfo, "vendor=");
++ return result.toInt(&hasIdentifier, 16);
++ }
++
++ static QString deviceSerialNumber(const QString &pnpinfo)
++ {
++ QString serialNumber = deviceProperty(pnpinfo, "sernum=");
++ serialNumber.remove(QLatin1Char('"'));
++ return serialNumber;
++ }
++
++ // A 'desc' string contains the both description and manufacturer
++ // properties, which are not possible to extract from the source
++ // string. Besides, this string can contains an other information,
++ // which should be excluded from the result.
++ static QString deviceDescriptionAndManufacturer(const QString &desc)
++ {
++ const qsizetype classindex = desc.indexOf(QLatin1String(", class "));
++ if (classindex == -1)
++ return desc;
++ return desc.mid(0, classindex);
++ }
++
++ struct NodeInfo
++ {
++ QString name;
++ QString value;
++ };
++
++ static QList<int> mibFromName(const QString &name)
++ {
++ size_t mibsize = 0;
++ if (::sysctlnametomib(name.toLocal8Bit().constData(), nullptr, &mibsize) < 0
++ || mibsize == 0) {
++ return QList<int>();
++ }
++ QList<int> mib(mibsize);
++ if (::sysctlnametomib(name.toLocal8Bit().constData(), &mib[0], &mibsize) < 0)
++ return QList<int>();
++
++ return mib;
++ }
++
++ static QList<int> nextOid(const QList<int> &previousOid)
++ {
++ Q_ASSERT(!previousOid.isEmpty());
++
++ QList<int> mib;
++ mib.append(0); // Magic undocumented code (CTL_UNSPEC ?)
++ mib.append(2); // Magic undocumented code
++ for (int code : previousOid)
++ mib.append(code);
++
++ size_t requiredLength = 0;
++ if (::sysctl(&mib[0], mib.count(), nullptr, &requiredLength, nullptr, 0) < 0)
++ return QList<int>();
++ // The returned length should not be zero, and should contain
++ // the whole amount of ints (OID components). Otherwise the next
++ // sysctl() call will either dereference the non-existing element
++ // or use the wrong length
++ if (requiredLength == 0 || requiredLength % sizeof(int) != 0)
++ return QList<int>();
++ const size_t oidLength = requiredLength / sizeof(int);
++ QList<int> oid(oidLength, 0);
++ if (::sysctl(&mib[0], mib.count(), &oid[0], &requiredLength, nullptr, 0) < 0)
++ return QList<int>();
++
++ if (previousOid.first() != oid.first())
++ return QList<int>();
++
++ return oid;
++ }
++
++ static NodeInfo nodeForOid(const QList<int> &oid)
++ {
++ QList<int> mib;
++ mib.append(0); // Magic undocumented code (CTL_UNSPEC ?)
++ mib.append(1); // Magic undocumented code
++ for (int code : oid)
++ mib.append(code);
++
++ // query node name
++ size_t requiredLength = 0;
++ if (::sysctl(&mib[0], mib.count(), nullptr, &requiredLength, nullptr, 0) < 0)
++ return NodeInfo();
++ QByteArray name(requiredLength, 0);
++ if (::sysctl(&mib[0], mib.count(), name.data(), &requiredLength, nullptr, 0) < 0)
++ return NodeInfo();
++
++ // query node value
++ requiredLength = 0;
++ if (::sysctl(&oid[0], oid.count(), nullptr, &requiredLength, nullptr, 0) < 0)
++ return NodeInfo();
++ QByteArray value(requiredLength, 0);
++ if (::sysctl(&oid[0], oid.count(), value.data(), &requiredLength, nullptr, 0) < 0)
++ return NodeInfo();
++
++ // query value format
++ mib[1] = 4; // Magic undocumented code
++ requiredLength = 0;
++ if (::sysctl(&mib[0], mib.count(), nullptr, &requiredLength, nullptr, 0) < 0)
++ return NodeInfo();
++ QByteArray buf(requiredLength, 0);
++ if (::sysctl(&mib[0], mib.count(), buf.data(), &requiredLength, nullptr, 0) < 0)
++ return NodeInfo();
++
++ QDataStream in(buf);
++ in.setByteOrder(QDataStream::LittleEndian);
++ quint32 kind = 0;
++ qint8 format = 0;
++ in >> kind >> format;
++
++ NodeInfo result;
++
++ // we need only the string-type value
++ if (format == 'A') {
++ result.name = QString::fromLocal8Bit(name.constData());
++ result.value = QString::fromLocal8Bit(value.constData());
++ }
++
++ return result;
++ }
++
++ static QList<NodeInfo> enumerateDesiredNodes(const QList<int> &mib)
++ {
++ QList<NodeInfo> nodes;
++
++ QList<int> oid = mib;
++
++ for (;;) {
++ const QList<int> nextoid = nextOid(oid);
++ if (nextoid.isEmpty())
++ break;
++
++ const NodeInfo node = nodeForOid(nextoid);
++ if (!node.name.isEmpty()) {
++ if (node.name.endsWith(QLatin1String("\%desc"))
++ || node.name.endsWith(QLatin1String("\%pnpinfo"))) {
++ nodes.append(node);
++ }
++ }
++
++ oid = nextoid;
++ }
++
++ return nodes;
++ }
++
++ QList<QSerialPortInfo> QSerialPortInfo::availablePorts()
++ {
++ const QList<int> mib = mibFromName(QLatin1String("dev"));
++ if (mib.isEmpty())
++ return QList<QSerialPortInfo>();
++
++ const QList<NodeInfo> nodes = enumerateDesiredNodes(mib);
++ if (nodes.isEmpty())
++ return QList<QSerialPortInfo>();
++
++ QDir deviceDir(QLatin1String("/dev"));
++ if (!(deviceDir.exists() && deviceDir.isReadable()))
++ return QList<QSerialPortInfo>();
++
++ deviceDir.setNameFilters(QStringList() << QLatin1String("cua*") << QLatin1String("tty*"));
++ deviceDir.setFilter(QDir::Files | QDir::System | QDir::NoSymLinks);
++
++ QList<QSerialPortInfo> cuaCandidates;
++ QList<QSerialPortInfo> ttyCandidates;
++
++ const auto portNames = deviceDir.entryList();
++ for (const QString &portName : portNames) {
++ if (portName.endsWith(QLatin1String(".init"))
++ || portName.endsWith(QLatin1String(".lock"))) {
++ continue;
++ }
++
++ QSerialPortInfoPrivate priv;
++ priv.portName = portName;
++ priv.device = QSerialPortInfoPrivate::portNameToSystemLocation(portName);
++
++ for (const NodeInfo &node : nodes) {
++ const qsizetype pnpinfoindex = node.name.indexOf(QLatin1String("\%pnpinfo"));
++ if (pnpinfoindex == -1)
++ continue;
++
++ if (node.value.isEmpty())
++ continue;
++
++ QString ttyname = deviceName(node.value);
++ if (ttyname.isEmpty())
++ continue;
++
++ const QString ttyportscount = deviceCount(node.value);
++ if (ttyportscount.isEmpty())
++ continue;
++
++ const int count = ttyportscount.toInt();
++ if (count == 0)
++ continue;
++ if (count > 1) {
++ bool matched = false;
++ for (int i = 0; i < count; ++i) {
++ const QString ends = QString(QLatin1String("%1.%2")).arg(ttyname).arg(i);
++ if (portName.endsWith(ends)) {
++ matched = true;
++ break;
++ }
++ }
++
++ if (!matched)
++ continue;
++ } else {
++ if (!portName.endsWith(ttyname))
++ continue;
++ }
++
++ priv.serialNumber = deviceSerialNumber(node.value);
++ priv.vendorIdentifier = deviceVendorIdentifier(node.value, priv.hasVendorIdentifier);
++ priv.productIdentifier = deviceProductIdentifier(node.value, priv.hasProductIdentifier);
++
++ const QString nodebase = node.name.mid(0, pnpinfoindex);
++ const QString descnode = QString(QLatin1String("%1\%desc")).arg(nodebase);
++
++ // search for description and manufacturer properties
++ for (const NodeInfo &descriptionNode : nodes) {
++ if (descriptionNode.name != descnode)
++ continue;
++
++ if (descriptionNode.value.isEmpty())
++ continue;
++
++ // We can not separate the description and the manufacturer
++ // properties from the node value, so lets just duplicate it.
++ priv.description = deviceDescriptionAndManufacturer(descriptionNode.value);
++ priv.manufacturer = priv.description;
++ break;
++ }
++
++ break;
++ }
++
++ if (portName.startsWith(QLatin1String("cua")))
++ cuaCandidates.append(priv);
++ else if (portName.startsWith(QLatin1String("tty")))
++ ttyCandidates.append(priv);
++ }
++
++ QList<QSerialPortInfo> serialPortInfoList;
++
++ for (const QSerialPortInfo &cuaCandidate : std::as_const(cuaCandidates)) {
++ const QString cuaPortName = cuaCandidate.portName();
++ const QString cuaToken = deviceProperty(cuaPortName, "cua");
++ for (const QSerialPortInfo &ttyCandidate : std::as_const(ttyCandidates)) {
++ const QString ttyPortName = ttyCandidate.portName();
++ const QString ttyToken = deviceProperty(ttyPortName, "tty");
++ if (cuaToken != ttyToken)
++ continue;
++
++ serialPortInfoList.append(cuaCandidate);
++ serialPortInfoList.append(ttyCandidate);
++ }
++ }
++
++ return serialPortInfoList;
++ }
++
++ QString QSerialPortInfoPrivate::portNameToSystemLocation(const QString &source)
++ {
++ return (source.startsWith(QLatin1Char('/'))
++ || source.startsWith(QLatin1String("./"))
++ || source.startsWith(QLatin1String("../")))
++ ? source : (QLatin1String("/dev/") + source);
++ }
++
++ QString QSerialPortInfoPrivate::portNameFromSystemLocation(const QString &source)
++ {
++ return source.startsWith(QLatin1String("/dev/"))
++ ? source.mid(5) : source;
++ }
++
++ QT_END_NAMESPACE