File indexing completed on 2024-05-12 05:37:18

0001 /*
0002     SPDX-FileCopyrightText: 2009 Petri Damstén <damu@iki.fi>
0003 
0004     Original Implementation:
0005     SPDX-FileCopyrightText: 2009 Andrew Coles <andrew.coles@yahoo.co.uk>
0006 
0007     Extension to iplocationtools engine:
0008     SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
0009 
0010     SPDX-License-Identifier: GPL-2.0-or-later
0011 */
0012 
0013 #include "location_ip.h"
0014 #include "geolocdebug.h"
0015 #include <KSharedConfig>
0016 #include <NetworkManagerQt/Manager>
0017 #include <NetworkManagerQt/WirelessDevice>
0018 #include <QJsonArray>
0019 #include <QJsonDocument>
0020 #include <QJsonObject>
0021 #include <QNetworkAccessManager>
0022 #include <QNetworkReply>
0023 #include <QUrl>
0024 
0025 class Ip::Private : public QObject
0026 {
0027     Q_OBJECT
0028 public:
0029     Private(Ip *q)
0030         : q(q)
0031     {
0032         m_nam.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
0033         m_nam.setStrictTransportSecurityEnabled(true);
0034         m_nam.enableStrictTransportSecurityStore(true,
0035                                                  QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/plasmashell/hsts/"));
0036     }
0037 
0038     void readGeoLocation(QNetworkReply *reply)
0039     {
0040         m_geoLocationResolved = true;
0041         if (reply->error()) {
0042             qCCritical(DATAENGINE_GEOLOCATION) << "error: " << reply->errorString();
0043             checkUpdateData();
0044             return;
0045         }
0046         const QJsonObject json = QJsonDocument::fromJson(reply->readAll()).object();
0047 
0048         auto accuracyIt = json.find(QStringLiteral("accuracy"));
0049         if (accuracyIt != json.end()) {
0050             m_data[QStringLiteral("accuracy")] = (*accuracyIt).toDouble();
0051         } else {
0052             m_data[QStringLiteral("accuracy")] = 40000;
0053         }
0054 
0055         auto locationIt = json.find(QStringLiteral("location"));
0056         if (locationIt != json.end()) {
0057             QJsonObject location = (*locationIt).toObject();
0058             m_data[QStringLiteral("latitude")] = location.value(QStringLiteral("lat")).toDouble();
0059             m_data[QStringLiteral("longitude")] = location.value(QStringLiteral("lng")).toDouble();
0060         }
0061         checkUpdateData();
0062     }
0063 
0064     void clear()
0065     {
0066         m_countryResolved = false;
0067         m_geoLocationResolved = false;
0068         m_data.clear();
0069     }
0070 
0071     void readCountry(QNetworkReply *reply)
0072     {
0073         m_countryResolved = true;
0074         if (reply->error()) {
0075             qCCritical(DATAENGINE_GEOLOCATION) << "error: " << reply->errorString();
0076             checkUpdateData();
0077             return;
0078         }
0079 
0080         const QJsonObject json = QJsonDocument::fromJson(reply->readAll()).object();
0081 
0082         m_data[QStringLiteral("country")] = json.value(QStringLiteral("country_name")).toString();
0083         m_data[QStringLiteral("country code")] = json.value(QStringLiteral("country_code")).toString();
0084 
0085         checkUpdateData();
0086     }
0087 
0088     void checkUpdateData()
0089     {
0090         if (!m_countryResolved || !m_geoLocationResolved) {
0091             return;
0092         }
0093         q->setData(m_data);
0094     }
0095 
0096     Ip *q;
0097     bool m_countryResolved = false;
0098     bool m_geoLocationResolved = false;
0099     Plasma5Support::DataEngine::Data m_data;
0100     QNetworkAccessManager m_nam;
0101 };
0102 
0103 Ip::Ip(QObject *parent)
0104     : GeolocationProvider(parent)
0105     , d(new Private(this))
0106 {
0107     setUpdateTriggers(SourceEvent | NetworkConnected);
0108 }
0109 
0110 Ip::~Ip()
0111 {
0112     delete d;
0113 }
0114 
0115 static QJsonArray accessPoints()
0116 {
0117     QJsonArray wifiAccessPoints;
0118     const KConfigGroup config = KSharedConfig::openConfig()->group(QStringLiteral("org.kde.plasma.geolocation.ip"));
0119     if (!NetworkManager::isWirelessEnabled() || !config.readEntry("Wifi", false)) {
0120         return wifiAccessPoints;
0121     }
0122     for (const auto &device : NetworkManager::networkInterfaces()) {
0123         QSharedPointer<NetworkManager::WirelessDevice> wifi = qSharedPointerDynamicCast<NetworkManager::WirelessDevice>(device);
0124         if (!wifi) {
0125             continue;
0126         }
0127         for (const auto &network : wifi->networks()) {
0128             const QString &ssid = network->ssid();
0129             if (ssid.isEmpty() || ssid.endsWith(QLatin1String("_nomap"))) {
0130                 // skip hidden SSID and networks with "_nomap"
0131                 continue;
0132             }
0133             for (const auto &accessPoint : network->accessPoints()) {
0134                 wifiAccessPoints.append(QJsonObject{{QStringLiteral("macAddress"), accessPoint->hardwareAddress()}});
0135             }
0136         }
0137     }
0138     return wifiAccessPoints;
0139 }
0140 
0141 void Ip::update()
0142 {
0143     d->clear();
0144     if (!NetworkManager::isNetworkingEnabled()) {
0145         setData(Plasma5Support::DataEngine::Data());
0146         return;
0147     }
0148     const QJsonArray wifiAccessPoints = accessPoints();
0149     QJsonObject request;
0150     if (wifiAccessPoints.count() >= 2) {
0151         request.insert(QStringLiteral("wifiAccessPoints"), wifiAccessPoints);
0152     }
0153     const QByteArray postData = QJsonDocument(request).toJson(QJsonDocument::Compact);
0154     const QString apiKey = QStringLiteral("60e8eae6-3988-4ada-ad48-2cfddddf216b");
0155 
0156     qCDebug(DATAENGINE_GEOLOCATION) << "Fetching https://location.services.mozilla.com/v1/geolocate";
0157     QNetworkRequest locationRequest(QUrl(QStringLiteral("https://location.services.mozilla.com/v1/geolocate?key=%1").arg(apiKey)));
0158     locationRequest.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
0159     QNetworkReply *locationReply = d->m_nam.post(locationRequest, postData);
0160 
0161     connect(locationReply, &QNetworkReply::finished, this, [this, locationReply] {
0162         locationReply->deleteLater();
0163         d->readGeoLocation(locationReply);
0164     });
0165 
0166     qCDebug(DATAENGINE_GEOLOCATION) << "Fetching https://location.services.mozilla.com/v1/country";
0167     QNetworkRequest countryRequest(QUrl(QStringLiteral("https://location.services.mozilla.com/v1/country?key=%1").arg(apiKey)));
0168     countryRequest.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
0169     QNetworkReply *countryReply = d->m_nam.post(countryRequest, postData);
0170 
0171     connect(countryReply, &QNetworkReply::finished, this, [this, countryReply] {
0172         countryReply->deleteLater();
0173         d->readCountry(countryReply);
0174     });
0175 }
0176 
0177 K_PLUGIN_CLASS(Ip)
0178 
0179 #include "location_ip.moc"