Revision control

++ // Copyright (C) 2011-2012 Denis Shienkov <denis.shienkov@gmail.com>
++ // Copyright (C) 2011 Sergey Belyashov <Sergey.Belyashov@gmail.com>
++ // Copyright (C) 2012 Laszlo Papp <lpapp@kde.org>
++ // 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/quuid.h>
++ #include <QtCore/qpair.h>
++ #include <QtCore/qstringlist.h>
++ #include <QtCore/private/qwinregistry_p.h>
++ #include <QtCore/private/quniquehandle_types_p.h>
++
++ #include <vector>
++
++ #include <initguid.h>
++ #include <devguid.h> // for GUID_DEVCLASS_PORTS and GUID_DEVCLASS_MODEM
++ #include <winioctl.h> // for GUID_DEVINTERFACE_COMPORT
++ #include <setupapi.h>
++ #include <cfgmgr32.h>
++ #include <usbioctl.h>
++ #include <usbiodef.h>
++ #include <ioapiset.h>
++
++ #ifdef QT_NO_REDEFINE_GUID_DEVINTERFACE_MODEM
++ # include <ntddmodm.h> // for GUID_DEVINTERFACE_MODEM
++ #else
++ DEFINE_GUID(GUID_DEVINTERFACE_MODEM, 0x2c7089aa, 0x2e0e, 0x11d1, 0xb1, 0x14, 0x00, 0xc0, 0x4f, 0xc2, 0xaa, 0xe4);
++ #endif
++
++ QT_BEGIN_NAMESPACE
++
++ namespace {
++
++ struct DevInfoHandleTraits
++ {
++ using Type = HDEVINFO;
++ static Type invalidValue() noexcept
++ {
++ return INVALID_HANDLE_VALUE;
++ }
++ static bool close(Type handle) noexcept
++ {
++ return SetupDiDestroyDeviceInfoList(handle) == TRUE;
++ }
++ };
++
++ using DevInfoHandle = QUniqueHandle<DevInfoHandleTraits>;
++
++ } // namespace
++
++ static QStringList portNamesFromHardwareDeviceMap()
++ {
++ const QWinRegistryKey key{ HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM", 0,
++ KEY_QUERY_VALUE };
++
++ if (!key.isValid())
++ return { };
++
++ QStringList result;
++ DWORD index = 0;
++
++ // This is a maximum length of value name, see:
++ enum { MaximumValueNameInChars = 16383 };
++
++ std::vector<wchar_t> outputValueName(MaximumValueNameInChars, 0);
++ std::vector<wchar_t> outputBuffer(MAX_PATH + 1, 0);
++ DWORD bytesRequired = MAX_PATH;
++ for (;;) {
++ DWORD requiredValueNameChars = MaximumValueNameInChars;
++ const LONG ret = ::RegEnumValue(key, index, &outputValueName[0], &requiredValueNameChars,
++ nullptr, nullptr, reinterpret_cast<PBYTE>(&outputBuffer[0]), &bytesRequired);
++ if (ret == ERROR_MORE_DATA) {
++ outputBuffer.resize(bytesRequired / sizeof(wchar_t) + 2, 0);
++ } else if (ret == ERROR_SUCCESS) {
++ result.append(QString::fromWCharArray(&outputBuffer[0]));
++ ++index;
++ } else {
++ break;
++ }
++ }
++ return result;
++ }
++
++ static QString deviceRegistryProperty(HDEVINFO deviceInfoSet,
++ PSP_DEVINFO_DATA deviceInfoData,
++ DWORD property)
++ {
++ DWORD dataType = 0;
++ std::vector<wchar_t> outputBuffer(MAX_PATH + 1, 0);
++ DWORD bytesRequired = MAX_PATH;
++ for (;;) {
++ if (::SetupDiGetDeviceRegistryProperty(deviceInfoSet, deviceInfoData, property, &dataType,
++ reinterpret_cast<PBYTE>(&outputBuffer[0]),
++ bytesRequired, &bytesRequired)) {
++ break;
++ }
++
++ if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER
++ || (dataType != REG_SZ && dataType != REG_EXPAND_SZ)) {
++ return QString();
++ }
++ outputBuffer.resize(bytesRequired / sizeof(wchar_t) + 2, 0);
++ }
++ return QString::fromWCharArray(&outputBuffer[0]);
++ }
++
++ static QString getStringDescriptor(HANDLE hHubDevice, ULONG connectionIndex,
++ UCHAR descriptorIndex, USHORT languageID)
++ {
++
++ constexpr DWORD bufferSize = sizeof(USB_DESCRIPTOR_REQUEST) + MAXIMUM_USB_STRING_LENGTH;
++ std::array<UCHAR, bufferSize> buffer{};
++ auto request = reinterpret_cast<PUSB_DESCRIPTOR_REQUEST>(buffer.data());
++
++ request->ConnectionIndex = connectionIndex;
++ request->SetupPacket.wValue = (USB_STRING_DESCRIPTOR_TYPE << 8) | descriptorIndex;
++ request->SetupPacket.wIndex = languageID;
++ request->SetupPacket.wLength = MAXIMUM_USB_STRING_LENGTH;
++
++ ULONG bytesReturned = 0;
++ BOOL success = DeviceIoControl(hHubDevice, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION,
++ request, bufferSize, request, bufferSize, &bytesReturned,
++ nullptr);
++
++ if (!success)
++ return {};
++
++ constexpr ULONG minExpectedSize = sizeof(USB_DESCRIPTOR_REQUEST) +
++ sizeof(USB_STRING_DESCRIPTOR);
++
++ if (bytesReturned < minExpectedSize)
++ return {};
++
++ const auto stringDesc = reinterpret_cast<PUSB_STRING_DESCRIPTOR>(request->Data);
++
++ if (stringDesc->bDescriptorType != USB_STRING_DESCRIPTOR_TYPE)
++ return {};
++
++ if (stringDesc->bLength != bytesReturned - sizeof(USB_DESCRIPTOR_REQUEST))
++ return {};
++
++ if (stringDesc->bLength % 2 != 0)
++ return {};
++
++ // Offset per USB 2.0 standard, section 9.6.7 table 9-16.
++ // bLength appears to include the size of the first 2 bytes of the descriptor
++ constexpr int stringOffset = 2;
++ const int numWideChars = (stringDesc->bLength - stringOffset) / sizeof(wchar_t);
++ return QString::fromWCharArray(stringDesc->bString, numWideChars);
++ }
++
++ static std::vector<wchar_t> getDevicePath(DEVINST devInst)
++ {
++ unsigned long devIdSize;
++
++ if (::CM_Get_Device_ID_Size(&devIdSize, devInst, 0) != CR_SUCCESS) {
++ return {};
++ }
++
++ std::vector<wchar_t> buffer(devIdSize + 1);
++
++ if (::CM_Get_Device_ID(devInst, buffer.data(), devIdSize, 0) != CR_SUCCESS) {
++ return {};
++ }
++
++ return buffer;
++ }
++
++ // Returns true if a hub is found, false if we gave up and didn't find one
++ // Passes devLocation by ref - port number on the hub of the device we need info for.
++ // Passes buffer by ref - path to the device we need to query to get information
++ static bool getUSBLocationAndPath(DWORD devInst, int &devLocation, std::vector<wchar_t> &buffer)
++ {
++ bool hubFound = false;
++ int count = 0;
++ QString devLocationStr;
++ std::vector<wchar_t> devicePath = {};
++
++
++ // In order to find the actual information we want to find in subsequent function calls,
++ // We have to get the port number on the hub of the device we want from that device,
++ // and we have to get the path to the hub device. Both are required to get
++ // any of the usb data we want. So we loop through starting with the passed-in
++ // device instance, and we return (via out arguments) the port of the device connected
++ // to the hub, and the path to the hub itself.
++
++ while (!hubFound && (count < 3)) {
++ DEVINST parentDevInst;
++ if (count == 0) {
++ parentDevInst = devInst;
++ } else {
++ if (::CM_Get_Parent(&parentDevInst, devInst, 0) != CR_SUCCESS) {
++ return false;
++ }
++ }
++
++ devicePath = getDevicePath(parentDevInst);
++
++ if (devicePath.empty())
++ return false;
++
++ // Get info for this device. If it's a composite device we'll have to go up another level.
++
++ const DevInfoHandle currDeviceInformation{ ::SetupDiGetClassDevsW(
++ nullptr, devicePath.data(), nullptr,
++ DIGCF_ALLCLASSES | DIGCF_PRESENT | DIGCF_DEVICEINTERFACE) };
++
++ if (!currDeviceInformation)
++ return false;
++
++ SP_DEVINFO_DATA currDeviceInfoData = {};
++ currDeviceInfoData.cbSize = sizeof(currDeviceInfoData);
++
++ // There should only be one since we're getting info for the parent device
++ if (!::SetupDiEnumDeviceInfo(currDeviceInformation.get(), 0, &currDeviceInfoData)) {
++ return false;
++ }
++
++ // Find out if this device is a hub device, if so, we're done
++ const QString devDescription = deviceRegistryProperty(currDeviceInformation.get(),
++ &currDeviceInfoData,
++ SPDRP_DEVICEDESC);
++
++ if (devDescription.contains(L"Hub")) {
++ hubFound = true;
++ } else {
++ devInst = parentDevInst;
++ devLocationStr = deviceRegistryProperty(currDeviceInformation.get(),
++ &currDeviceInfoData,
++ SPDRP_LOCATION_INFORMATION);
++ count++;
++ }
++ }
++
++ if (count == 3)
++ // didn't find what we were looking for. Give up.
++ return false;
++
++ if (!hubFound)
++ return false;
++
++ const QString portString(L"Port_#");
++
++ if (devLocationStr.contains(portString)) {
++ devLocationStr.remove(portString);
++ devLocation = devLocationStr.left(devLocationStr.indexOf(u'.')).toInt();
++ } else {
++ return false;
++ }
++
++ // Build the path based on information found here:
++ // 8007468/how-do-i-obtain-usb-device-descriptor-given-a-device-path/32641140#32641140
++ QString devPath = QString::fromStdWString(devicePath.data());
++ QString hubUuidStr = u'#' + QUuid(GUID_DEVINTERFACE_USB_HUB).toString();
++
++ devPath.replace(u'\\', u'#');
++ devPath.prepend(L"\\\\?\\");
++ devPath.append(hubUuidStr); // GUID for hub devices
++ buffer.resize(devPath.size()+1, 0);
++ devPath.toWCharArray(buffer.data());
++
++ return true;
++ }
++
++ struct UsbData
++ {
++ QString iManufacturer;
++ QString iProduct;
++ };
++
++ static UsbData getUSBDataFromDevice(DWORD devInst)
++ {
++ std::vector<wchar_t> buffer;
++ int devLocation;
++
++ if (!getUSBLocationAndPath(devInst, devLocation, buffer))
++ return {};
++
++ // Open the Hub device. To get the information we want, we have to open the Hub device,
++ // and pass in the port number in the argument structure.
++
++ const QUniqueWin32Handle fileHandle{ ::CreateFile(buffer.data(), GENERIC_READ, FILE_SHARE_READ,
++ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
++ nullptr) };
++
++ if (!fileHandle)
++ return {};
++
++ // Some of the following code was from Microsoft's sample usbview code
++ constexpr ULONG size = sizeof(USB_NODE_CONNECTION_INFORMATION);
++ std::array<UCHAR, size> connectionInfoData;
++
++ // Set the port number of the device on the hub for the DeviceIoControl call.
++ auto connectionInfo =
++ reinterpret_cast<PUSB_NODE_CONNECTION_INFORMATION>(connectionInfoData.data());
++ connectionInfo->ConnectionIndex = devLocation;
++
++ ULONG dataSize = 0;
++ if (!::DeviceIoControl(fileHandle.get(), IOCTL_USB_GET_NODE_CONNECTION_INFORMATION,
++ connectionInfo, size, connectionInfo, size, &dataSize, nullptr)) {
++ return {};
++ }
++
++ // Get the language information, but we're only going to use the first one since we're going to
++ // use this information for the manufacturer returned to Qt's caller, and there's only 1 allowed
++ // there
++
++ QString usbStringData = getStringDescriptor(fileHandle.get(),
++ connectionInfo->ConnectionIndex,
++ 0, 0);
++
++ if (usbStringData.isEmpty())
++ return {};
++
++ USHORT languageIDs = usbStringData[0].unicode();
++ UsbData usbData;
++
++ if (connectionInfo->DeviceDescriptor.iManufacturer != 0)
++ usbData.iManufacturer = getStringDescriptor(fileHandle.get(),
++ connectionInfo->ConnectionIndex,
++ connectionInfo->DeviceDescriptor.iManufacturer,
++ languageIDs);
++
++ if (connectionInfo->DeviceDescriptor.iProduct != 0)
++ usbData.iProduct = getStringDescriptor(fileHandle.get(),
++ connectionInfo->ConnectionIndex,
++ connectionInfo->DeviceDescriptor.iProduct,
++ languageIDs);
++
++ return usbData;
++ }
++
++ static QString deviceInstanceIdentifier(DEVINST deviceInstanceNumber)
++ {
++ std::vector<wchar_t> outputBuffer(MAX_DEVICE_ID_LEN + 1, 0);
++ if (::CM_Get_Device_ID(
++ deviceInstanceNumber,
++ &outputBuffer[0],
++ MAX_DEVICE_ID_LEN,
++ 0) != CR_SUCCESS) {
++ return QString();
++ }
++ return QString::fromWCharArray(&outputBuffer[0]).toUpper();
++ }
++
++ static DEVINST parentDeviceInstanceNumber(DEVINST childDeviceInstanceNumber)
++ {
++ ULONG nodeStatus = 0;
++ ULONG problemNumber = 0;
++ if (::CM_Get_DevNode_Status(&nodeStatus, &problemNumber,
++ childDeviceInstanceNumber, 0) != CR_SUCCESS) {
++ return 0;
++ }
++ DEVINST parentInstanceNumber = 0;
++ if (::CM_Get_Parent(&parentInstanceNumber, childDeviceInstanceNumber, 0) != CR_SUCCESS)
++ return 0;
++ return parentInstanceNumber;
++ }
++
++ static QString devicePortName(HDEVINFO deviceInfoSet, PSP_DEVINFO_DATA deviceInfoData)
++ {
++ const HKEY key = ::SetupDiOpenDevRegKey(deviceInfoSet, deviceInfoData, DICS_FLAG_GLOBAL,
++ 0, DIREG_DEV, KEY_READ);
++ if (key == INVALID_HANDLE_VALUE)
++ return QString();
++
++ static const wchar_t * const keyTokens[] = {
++ L"PortName\0",
++ L"PortNumber\0"
++ };
++
++ QString portName;
++ for (auto keyToken : keyTokens) {
++ DWORD dataType = 0;
++ std::vector<wchar_t> outputBuffer(MAX_PATH + 1, 0);
++ DWORD bytesRequired = MAX_PATH;
++ for (;;) {
++ const LONG ret = ::RegQueryValueEx(key, keyToken, nullptr, &dataType,
++ reinterpret_cast<PBYTE>(&outputBuffer[0]), &bytesRequired);
++ if (ret == ERROR_MORE_DATA) {
++ outputBuffer.resize(bytesRequired / sizeof(wchar_t) + 2, 0);
++ continue;
++ } else if (ret == ERROR_SUCCESS) {
++ if (dataType == REG_SZ)
++ portName = QString::fromWCharArray(&outputBuffer[0]);
++ else if (dataType == REG_DWORD)
++ portName = QStringLiteral("COM%1").arg(*(PDWORD(&outputBuffer[0])));
++ }
++ break;
++ }
++
++ if (!portName.isEmpty())
++ break;
++ }
++ ::RegCloseKey(key);
++ return portName;
++ }
++
++ static QString deviceDescription(HDEVINFO deviceInfoSet,
++ PSP_DEVINFO_DATA deviceInfoData)
++ {
++ return deviceRegistryProperty(deviceInfoSet, deviceInfoData, SPDRP_DEVICEDESC);
++ }
++
++ static QString deviceManufacturer(HDEVINFO deviceInfoSet,
++ PSP_DEVINFO_DATA deviceInfoData)
++ {
++ return deviceRegistryProperty(deviceInfoSet, deviceInfoData, SPDRP_MFG);
++ }
++
++ static quint16 parseDeviceIdentifier(const QString &instanceIdentifier,
++ const QString &identifierPrefix,
++ qsizetype identifierSize, bool &ok)
++ {
++ const qsizetype index = instanceIdentifier.indexOf(identifierPrefix);
++ if (index == -1)
++ return quint16(0);
++ return instanceIdentifier.mid(index + identifierPrefix.size(), identifierSize).toInt(&ok, 16);
++ }
++
++ static quint16 deviceVendorIdentifier(const QString &instanceIdentifier, bool &ok)
++ {
++ static constexpr qsizetype vendorIdentifierSize = 4;
++ quint16 result = parseDeviceIdentifier(
++ instanceIdentifier, QStringLiteral("VID_"), vendorIdentifierSize, ok);
++ if (!ok)
++ result = parseDeviceIdentifier(
++ instanceIdentifier, QStringLiteral("VEN_"), vendorIdentifierSize, ok);
++ return result;
++ }
++
++ static quint16 deviceProductIdentifier(const QString &instanceIdentifier, bool &ok)
++ {
++ static constexpr qsizetype productIdentifierSize = 4;
++ quint16 result = parseDeviceIdentifier(
++ instanceIdentifier, QStringLiteral("PID_"), productIdentifierSize, ok);
++ if (!ok)
++ result = parseDeviceIdentifier(
++ instanceIdentifier, QStringLiteral("DEV_"), productIdentifierSize, ok);
++ return result;
++ }
++
++ static QString parseDeviceSerialNumber(const QString &instanceIdentifier)
++ {
++ qsizetype firstbound = instanceIdentifier.lastIndexOf(QLatin1Char('\\'));
++ qsizetype lastbound = instanceIdentifier.indexOf(QLatin1Char('_'), firstbound);
++ if (instanceIdentifier.startsWith(QLatin1String("USB\\"))) {
++ if (lastbound != instanceIdentifier.size() - 3)
++ lastbound = instanceIdentifier.size();
++ qsizetype ampersand = instanceIdentifier.indexOf(QLatin1Char('&'), firstbound);
++ if (ampersand != -1 && ampersand < lastbound)
++ return QString();
++ } else if (instanceIdentifier.startsWith(QLatin1String("FTDIBUS\\"))) {
++ firstbound = instanceIdentifier.lastIndexOf(QLatin1Char('+'));
++ lastbound = instanceIdentifier.indexOf(QLatin1Char('\\'), firstbound);
++ if (lastbound == -1)
++ return QString();
++ } else {
++ return QString();
++ }
++
++ return instanceIdentifier.mid(firstbound + 1, lastbound - firstbound - 1);
++ }
++
++ static QString deviceSerialNumber(QString instanceIdentifier,
++ DEVINST deviceInstanceNumber)
++ {
++ for (;;) {
++ const QString result = parseDeviceSerialNumber(instanceIdentifier);
++ if (!result.isEmpty())
++ return result;
++ deviceInstanceNumber = parentDeviceInstanceNumber(deviceInstanceNumber);
++ if (deviceInstanceNumber == 0)
++ break;
++ instanceIdentifier = deviceInstanceIdentifier(deviceInstanceNumber);
++ if (instanceIdentifier.isEmpty())
++ break;
++ }
++
++ return QString();
++ }
++
++ static bool anyOfPorts(const QList<QSerialPortInfo> &ports, const QString &portName)
++ {
++ const auto end = ports.end();
++ auto isPortNamesEquals = [&portName](const QSerialPortInfo &portInfo) {
++ return portInfo.portName() == portName;
++ };
++ return std::find_if(ports.begin(), end, isPortNamesEquals) != end;
++ }
++
++ QList<QSerialPortInfo> QSerialPortInfo::availablePorts()
++ {
++ static const struct {
++ GUID guid; DWORD flags;
++ } setupTokens[] = {
++ { GUID_DEVCLASS_PORTS, DIGCF_PRESENT },
++ { GUID_DEVCLASS_MODEM, DIGCF_PRESENT },
++ { GUID_DEVINTERFACE_COMPORT, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE },
++ { GUID_DEVINTERFACE_MODEM, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE }
++ };
++
++ QList<QSerialPortInfo> serialPortInfoList;
++
++ for (const auto& setupToken : setupTokens) {
++ const DevInfoHandle deviceInfoSet{ ::SetupDiGetClassDevs(&setupToken.guid, nullptr,
++ nullptr, setupToken.flags) };
++ if (!deviceInfoSet)
++ return serialPortInfoList;
++
++ SP_DEVINFO_DATA deviceInfoData{};
++ deviceInfoData.cbSize = sizeof(deviceInfoData);
++
++ DWORD index = 0;
++ while (::SetupDiEnumDeviceInfo(deviceInfoSet.get(), index++, &deviceInfoData)) {
++ const QString portName = devicePortName(deviceInfoSet.get(), &deviceInfoData);
++ if (portName.isEmpty() || portName.contains(QLatin1String("LPT")))
++ continue;
++
++ if (anyOfPorts(serialPortInfoList, portName))
++ continue;
++
++ QSerialPortInfoPrivate priv;
++
++ priv.portName = portName;
++ priv.device = QSerialPortInfoPrivate::portNameToSystemLocation(portName);
++ priv.description = deviceDescription(deviceInfoSet.get(), &deviceInfoData);
++ priv.manufacturer = deviceManufacturer(deviceInfoSet.get(), &deviceInfoData);
++
++ const QString instanceIdentifier = deviceInstanceIdentifier(deviceInfoData.DevInst);
++
++ priv.serialNumber =
++ deviceSerialNumber(instanceIdentifier, deviceInfoData.DevInst);
++ priv.vendorIdentifier =
++ deviceVendorIdentifier(instanceIdentifier, priv.hasVendorIdentifier);
++ priv.productIdentifier =
++ deviceProductIdentifier(instanceIdentifier, priv.hasProductIdentifier);
++
++ // This makes Windows return the same data that linux does for Manufacturer and Product
++ if ((instanceIdentifier.startsWith(L"USB\\")
++ || instanceIdentifier.startsWith(L"FTDIBUS\\"))
++ && priv.hasVendorIdentifier && priv.hasProductIdentifier) {
++ UsbData usbStrings = getUSBDataFromDevice(deviceInfoData.DevInst);
++
++ if (!usbStrings.iManufacturer.isEmpty())
++ priv.manufacturer = usbStrings.iManufacturer;
++
++ if (!usbStrings.iProduct.isEmpty())
++ priv.description = usbStrings.iProduct;
++ // End of making Windows return the same data as Linux
++ }
++
++ serialPortInfoList.append(priv);
++ }
++ }
++
++ const auto portNames = portNamesFromHardwareDeviceMap();
++ for (const QString &portName : portNames) {
++ if (!anyOfPorts(serialPortInfoList, portName)) {
++ QSerialPortInfoPrivate priv;
++ priv.portName = portName;
++ priv.device = QSerialPortInfoPrivate::portNameToSystemLocation(portName);
++ serialPortInfoList.append(priv);
++ }
++ }
++
++ return serialPortInfoList;
++ }
++
++ QString QSerialPortInfoPrivate::portNameToSystemLocation(const QString &source)
++ {
++ return source.startsWith(QLatin1String("COM"))
++ ? (QLatin1String("\\\\.\\") + source) : source;
++ }
++
++ QString QSerialPortInfoPrivate::portNameFromSystemLocation(const QString &source)
++ {
++ return (source.startsWith(QLatin1String("\\\\.\\"))
++ || source.startsWith(QLatin1String("//./")))
++ ? source.mid(4) : source;
++ }
++
++ QT_END_NAMESPACE