QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QGCNetworkHelper.cc
Go to the documentation of this file.
1#include "QGCNetworkHelper.h"
2
3#include <QtCore/QCoreApplication>
4#include <QtCore/QIODevice>
5#include <QtCore/QJsonDocument>
6#include <QtCore/QUrlQuery>
7#include <QtNetwork/QHttpPart>
8#include <QtNetwork/QNetworkAccessManager>
9#include <QtNetwork/QNetworkInformation>
10#include <QtNetwork/QNetworkProxy>
11#include <QtNetwork/QNetworkProxyFactory>
12#include <QtNetwork/QSslSocket>
13
14#include "QGCCompression.h"
15#include "QGCLoggingCategory.h"
16
17#include <QtBluetooth/QBluetoothLocalDevice>
18
19QGC_LOGGING_CATEGORY(QGCNetworkHelperLog, "Utilities.QGCNetworkHelper")
20
22
23// ============================================================================
24// HTTP Status Code Helpers
25// ============================================================================
26
28{
29 if (statusCode >= 100 && statusCode < 200) {
30 return HttpStatusClass::Informational;
31 }
32 if (statusCode >= 200 && statusCode < 300) {
33 return HttpStatusClass::Success;
34 }
35 if (statusCode >= 300 && statusCode < 400) {
36 return HttpStatusClass::Redirection;
37 }
38 if (statusCode >= 400 && statusCode < 500) {
39 return HttpStatusClass::ClientError;
40 }
41 if (statusCode >= 500 && statusCode < 600) {
42 return HttpStatusClass::ServerError;
43 }
44 return HttpStatusClass::Unknown;
45}
46
47QString httpStatusText(HttpStatusCode statusCode)
48{
49 switch (statusCode) {
50 // 1xx Informational
51 case HttpStatusCode::Continue:
52 return QStringLiteral("Continue");
53 case HttpStatusCode::SwitchingProtocols:
54 return QStringLiteral("Switching Protocols");
55 case HttpStatusCode::Processing:
56 return QStringLiteral("Processing");
57 // 2xx Success
58 case HttpStatusCode::Ok:
59 return QStringLiteral("OK");
60 case HttpStatusCode::Created:
61 return QStringLiteral("Created");
62 case HttpStatusCode::Accepted:
63 return QStringLiteral("Accepted");
64 case HttpStatusCode::NonAuthoritativeInformation:
65 return QStringLiteral("Non-Authoritative Information");
66 case HttpStatusCode::NoContent:
67 return QStringLiteral("No Content");
68 case HttpStatusCode::ResetContent:
69 return QStringLiteral("Reset Content");
70 case HttpStatusCode::PartialContent:
71 return QStringLiteral("Partial Content");
72 case HttpStatusCode::MultiStatus:
73 return QStringLiteral("Multi-Status");
74 case HttpStatusCode::AlreadyReported:
75 return QStringLiteral("Already Reported");
76 case HttpStatusCode::IMUsed:
77 return QStringLiteral("IM Used");
78 // 3xx Redirection
79 case HttpStatusCode::MultipleChoices:
80 return QStringLiteral("Multiple Choices");
81 case HttpStatusCode::MovedPermanently:
82 return QStringLiteral("Moved Permanently");
83 case HttpStatusCode::Found:
84 return QStringLiteral("Found");
85 case HttpStatusCode::SeeOther:
86 return QStringLiteral("See Other");
87 case HttpStatusCode::NotModified:
88 return QStringLiteral("Not Modified");
89 case HttpStatusCode::UseProxy:
90 return QStringLiteral("Use Proxy");
91 case HttpStatusCode::TemporaryRedirect:
92 return QStringLiteral("Temporary Redirect");
93 case HttpStatusCode::PermanentRedirect:
94 return QStringLiteral("Permanent Redirect");
95 // 4xx Client Errors
96 case HttpStatusCode::BadRequest:
97 return QStringLiteral("Bad Request");
98 case HttpStatusCode::Unauthorized:
99 return QStringLiteral("Unauthorized");
100 case HttpStatusCode::PaymentRequired:
101 return QStringLiteral("Payment Required");
102 case HttpStatusCode::Forbidden:
103 return QStringLiteral("Forbidden");
104 case HttpStatusCode::NotFound:
105 return QStringLiteral("Not Found");
106 case HttpStatusCode::MethodNotAllowed:
107 return QStringLiteral("Method Not Allowed");
108 case HttpStatusCode::NotAcceptable:
109 return QStringLiteral("Not Acceptable");
110 case HttpStatusCode::ProxyAuthenticationRequired:
111 return QStringLiteral("Proxy Authentication Required");
112 case HttpStatusCode::RequestTimeout:
113 return QStringLiteral("Request Timeout");
114 case HttpStatusCode::Conflict:
115 return QStringLiteral("Conflict");
116 case HttpStatusCode::Gone:
117 return QStringLiteral("Gone");
118 case HttpStatusCode::LengthRequired:
119 return QStringLiteral("Length Required");
120 case HttpStatusCode::PreconditionFailed:
121 return QStringLiteral("Precondition Failed");
122 case HttpStatusCode::PayloadTooLarge:
123 return QStringLiteral("Payload Too Large");
124 case HttpStatusCode::UriTooLong:
125 return QStringLiteral("URI Too Long");
126 case HttpStatusCode::UnsupportedMediaType:
127 return QStringLiteral("Unsupported Media Type");
128 case HttpStatusCode::RequestRangeNotSatisfiable:
129 return QStringLiteral("Range Not Satisfiable");
130 case HttpStatusCode::ExpectationFailed:
131 return QStringLiteral("Expectation Failed");
132 case HttpStatusCode::ImATeapot:
133 return QStringLiteral("I'm a teapot");
134 case HttpStatusCode::MisdirectedRequest:
135 return QStringLiteral("Misdirected Request");
136 case HttpStatusCode::UnprocessableEntity:
137 return QStringLiteral("Unprocessable Entity");
138 case HttpStatusCode::Locked:
139 return QStringLiteral("Locked");
140 case HttpStatusCode::FailedDependency:
141 return QStringLiteral("Failed Dependency");
142 case HttpStatusCode::UpgradeRequired:
143 return QStringLiteral("Upgrade Required");
144 case HttpStatusCode::PreconditionRequired:
145 return QStringLiteral("Precondition Required");
146 case HttpStatusCode::TooManyRequests:
147 return QStringLiteral("Too Many Requests");
148 case HttpStatusCode::RequestHeaderFieldsTooLarge:
149 return QStringLiteral("Request Header Fields Too Large");
150 case HttpStatusCode::UnavailableForLegalReasons:
151 return QStringLiteral("Unavailable For Legal Reasons");
152 // 5xx Server Errors
153 case HttpStatusCode::InternalServerError:
154 return QStringLiteral("Internal Server Error");
155 case HttpStatusCode::NotImplemented:
156 return QStringLiteral("Not Implemented");
157 case HttpStatusCode::BadGateway:
158 return QStringLiteral("Bad Gateway");
159 case HttpStatusCode::ServiceUnavailable:
160 return QStringLiteral("Service Unavailable");
161 case HttpStatusCode::GatewayTimeout:
162 return QStringLiteral("Gateway Timeout");
163 case HttpStatusCode::HttpVersionNotSupported:
164 return QStringLiteral("HTTP Version Not Supported");
165 case HttpStatusCode::VariantAlsoNegotiates:
166 return QStringLiteral("Variant Also Negotiates");
167 case HttpStatusCode::InsufficientStorage:
168 return QStringLiteral("Insufficient Storage");
169 case HttpStatusCode::LoopDetected:
170 return QStringLiteral("Loop Detected");
171 case HttpStatusCode::NotExtended:
172 return QStringLiteral("Not Extended");
173 case HttpStatusCode::NetworkAuthenticationRequired:
174 return QStringLiteral("Network Authentication Required");
175 case HttpStatusCode::NetworkConnectTimeoutError:
176 return QStringLiteral("Network Connect Timeout Error");
177 default:
178 return QStringLiteral("Unknown Status");
179 }
180}
181
182QString httpStatusText(int statusCode)
183{
184 if (classifyHttpStatus(statusCode) == HttpStatusClass::Unknown) {
185 return QStringLiteral("Unknown Status (%1)").arg(statusCode);
186 }
187
188 return httpStatusText(static_cast<HttpStatusCode>(statusCode));
189}
190
191// ============================================================================
192// HTTP Methods
193// ============================================================================
194
196{
197 switch (method) {
198 case HttpMethod::Get:
199 return QStringLiteral("GET");
200 case HttpMethod::Post:
201 return QStringLiteral("POST");
202 case HttpMethod::Put:
203 return QStringLiteral("PUT");
204 case HttpMethod::Delete:
205 return QStringLiteral("DELETE");
206 case HttpMethod::Head:
207 return QStringLiteral("HEAD");
208 case HttpMethod::Options:
209 return QStringLiteral("OPTIONS");
210 case HttpMethod::Patch:
211 return QStringLiteral("PATCH");
212 case HttpMethod::Connect:
213 return QStringLiteral("CONNECT");
214 case HttpMethod::Trace:
215 return QStringLiteral("TRACE");
216 default:
217 return QStringLiteral("GET");
218 }
219}
220
221HttpMethod parseHttpMethod(const QString& methodStr)
222{
223 const QByteArray upper = methodStr.toUpper().toLatin1();
224 const char* str = upper.constData();
225
226 if (qstrcmp(str, "GET") == 0)
227 return HttpMethod::Get;
228 if (qstrcmp(str, "POST") == 0)
229 return HttpMethod::Post;
230 if (qstrcmp(str, "PUT") == 0)
231 return HttpMethod::Put;
232 if (qstrcmp(str, "DELETE") == 0)
233 return HttpMethod::Delete;
234 if (qstrcmp(str, "HEAD") == 0)
235 return HttpMethod::Head;
236 if (qstrcmp(str, "OPTIONS") == 0)
237 return HttpMethod::Options;
238 if (qstrcmp(str, "PATCH") == 0)
239 return HttpMethod::Patch;
240 if (qstrcmp(str, "CONNECT") == 0)
241 return HttpMethod::Connect;
242 if (qstrcmp(str, "TRACE") == 0)
243 return HttpMethod::Trace;
244
245 return HttpMethod::Get;
246}
247
248// ============================================================================
249// URL Utilities
250// ============================================================================
251
252bool isValidUrl(const QUrl& url)
253{
254 if (!url.isValid()) {
255 return false;
256 }
257
258 const QString scheme = url.scheme().toLower();
259 return scheme == QLatin1String("http") || scheme == QLatin1String("https") || scheme == QLatin1String("file") ||
260 scheme == QLatin1String("qrc") || scheme.isEmpty(); // Relative URL
261}
262
263bool isHttpUrl(const QUrl& url)
264{
265 const QString scheme = url.scheme().toLower();
266 return scheme == QLatin1String("http") || scheme == QLatin1String("https");
267}
268
269bool isHttpsUrl(const QUrl& url)
270{
271 return url.scheme().toLower() == QLatin1String("https");
272}
273
274QUrl normalizeUrl(const QUrl& url)
275{
276 if (!url.isValid()) {
277 return url;
278 }
279
280 QUrl normalized = url;
281
282 // Lowercase scheme and host
283 normalized.setScheme(normalized.scheme().toLower());
284 normalized.setHost(normalized.host().toLower());
285
286 // Remove default ports
287 const int port = normalized.port();
288 const QString scheme = normalized.scheme();
289 if ((scheme == QLatin1String("http") && port == 80) || (scheme == QLatin1String("https") && port == 443) ||
290 (scheme == QLatin1String("ftp") && port == 21)) {
291 normalized.setPort(-1);
292 }
293
294 // Remove trailing slash from path (except for root)
295 QString path = normalized.path();
296 if (path.length() > 1 && path.endsWith(QLatin1Char('/'))) {
297 path.chop(1);
298 normalized.setPath(path);
299 }
300
301 return normalized;
302}
303
304QUrl ensureScheme(const QUrl& url, const QString& defaultScheme)
305{
306 if (!url.isValid()) {
307 return url;
308 }
309
310 if (url.scheme().isEmpty()) {
311 QUrl withScheme = url;
312 withScheme.setScheme(defaultScheme);
313 return withScheme;
314 }
315
316 return url;
317}
318
319QUrl buildUrl(const QString& baseUrl, const QMap<QString, QString>& params)
320{
321 QUrl url(baseUrl);
322 if (!url.isValid()) {
323 return url;
324 }
325
326 QUrlQuery query;
327 for (auto it = params.constBegin(); it != params.constEnd(); ++it) {
328 query.addQueryItem(it.key(), it.value());
329 }
330 url.setQuery(query);
331
332 return url;
333}
334
335QUrl buildUrl(const QString& baseUrl, const QList<QPair<QString, QString>>& params)
336{
337 QUrl url(baseUrl);
338 if (!url.isValid()) {
339 return url;
340 }
341
342 QUrlQuery query;
343 for (const auto& [key, value] : params) {
344 query.addQueryItem(key, value);
345 }
346 url.setQuery(query);
347
348 return url;
349}
350
351QString urlFileName(const QUrl& url)
352{
353 const QString path = url.path();
354 const int lastSlash = path.lastIndexOf(QLatin1Char('/'));
355 if (lastSlash >= 0 && lastSlash < path.length() - 1) {
356 return path.mid(lastSlash + 1);
357 }
358 return path;
359}
360
361QUrl urlWithoutQuery(const QUrl& url)
362{
363 QUrl result = url;
364 result.setQuery(QString());
365 result.setFragment(QString());
366 return result;
367}
368
369// ============================================================================
370// Request Configuration
371// ============================================================================
372
373void configureRequest(QNetworkRequest& request, const RequestConfig& config)
374{
375 // Timeout
376 request.setTransferTimeout(config.timeoutMs);
377
378 // Redirect policy
379 if (config.allowRedirects) {
380 request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
381 } else {
382 request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy);
383 }
384
385 // HTTP/2
386 request.setAttribute(QNetworkRequest::Http2AllowedAttribute, config.http2Allowed);
387
388 // Caching
389 if (config.cacheEnabled) {
390 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
391 request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, true);
392 } else {
393 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
394 request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
395 }
396
397 // Background request
398 request.setAttribute(QNetworkRequest::BackgroundRequestAttribute, config.backgroundRequest);
399
400 // Headers
401 const QString userAgent = config.userAgent.isEmpty() ? defaultUserAgent() : config.userAgent;
402 request.setHeader(QNetworkRequest::UserAgentHeader, userAgent);
403
404 if (!config.accept.isEmpty()) {
405 request.setRawHeader("Accept", config.accept.toUtf8());
406 }
407
408 if (!config.acceptEncoding.isEmpty()) {
409 request.setRawHeader("Accept-Encoding", config.acceptEncoding.toUtf8());
410 }
411
412 if (!config.contentType.isEmpty()) {
413 request.setHeader(QNetworkRequest::ContentTypeHeader, config.contentType);
414 }
415
416 for (const auto &[attribute, value] : config.requestAttributes) {
417 request.setAttribute(attribute, value);
418 }
419
420 request.setRawHeader("Connection", "keep-alive");
421}
422
423QNetworkRequest createRequest(const QUrl& url, const RequestConfig& config)
424{
425 QNetworkRequest request(url);
426 configureRequest(request, config);
427 return request;
428}
429
430void setStandardHeaders(QNetworkRequest& request, const QString& userAgent)
431{
432 const QString ua = userAgent.isEmpty() ? defaultUserAgent() : userAgent;
433 request.setHeader(QNetworkRequest::UserAgentHeader, ua);
434 request.setRawHeader("Accept", "*/*");
435 request.setRawHeader("Accept-Encoding", "gzip, deflate");
436 request.setRawHeader("Connection", "keep-alive");
437}
438
439void setJsonHeaders(QNetworkRequest& request)
440{
441 request.setRawHeader("Accept", "application/json");
442 request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
443}
444
445void setFormHeaders(QNetworkRequest& request)
446{
447 request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded"));
448}
449
451{
452 static QString userAgent;
453 if (userAgent.isEmpty()) {
454 userAgent = QStringLiteral("%1/%2 (Qt %3)")
455 .arg(QCoreApplication::applicationName())
456 .arg(QCoreApplication::applicationVersion())
457 .arg(QString::fromLatin1(qVersion()));
458 }
459 return userAgent;
460}
461
462// ============================================================================
463// Authentication Helpers
464// ============================================================================
465
466void setBasicAuth(QNetworkRequest& request, const QString& credentials)
467{
468 request.setRawHeader("Authorization", ("Basic " + credentials).toUtf8());
469}
470
471void setBasicAuth(QNetworkRequest& request, const QString& username, const QString& password)
472{
473 setBasicAuth(request, createBasicAuthCredentials(username, password));
474}
475
476void setBearerToken(QNetworkRequest& request, const QString& token)
477{
478 request.setRawHeader("Authorization", ("Bearer " + token).toUtf8());
479}
480
481QString createBasicAuthCredentials(const QString& username, const QString& password)
482{
483 const QString credentials = username + QLatin1Char(':') + password;
484 return QString::fromLatin1(credentials.toUtf8().toBase64());
485}
486
487// ============================================================================
488// Multipart Form Data Helpers
489// ============================================================================
490
491QHttpPart createFormField(const QString& name, const QString& value)
492{
493 QHttpPart part;
494 part.setHeader(QNetworkRequest::ContentDispositionHeader, QStringLiteral("form-data; name=\"%1\"").arg(name));
495 part.setBody(value.toUtf8());
496 return part;
497}
498
499QHttpPart createFilePart(const QString& name, const QString& fileName, const QString& contentType, QIODevice* device)
500{
501 QHttpPart part;
502 part.setHeader(QNetworkRequest::ContentTypeHeader, contentType);
503 part.setHeader(QNetworkRequest::ContentDispositionHeader,
504 QStringLiteral("form-data; name=\"%1\"; filename=\"%2\"").arg(name, fileName));
505 part.setBodyDevice(device);
506 return part;
507}
508
509QHttpPart createFilePart(const QString& name, const QString& fileName, QIODevice* device)
510{
511 return createFilePart(name, fileName, kContentTypeOctetStream, device);
512}
513
514// ============================================================================
515// SSL/TLS Configuration Builders
516// ============================================================================
517
518QSslConfiguration createSslConfig(QSsl::SslProtocol protocol)
519{
520 QSslConfiguration config = QSslConfiguration::defaultConfiguration();
521 config.setProtocol(protocol);
522 return config;
523}
524
525QSslConfiguration createInsecureSslConfig()
526{
527 QSslConfiguration config = QSslConfiguration::defaultConfiguration();
528 config.setPeerVerifyMode(QSslSocket::VerifyNone);
529 return config;
530}
531
532void applySslConfig(QNetworkRequest& request, const QSslConfiguration& config)
533{
534 request.setSslConfiguration(config);
535}
536
537// ============================================================================
538// JSON Response Helpers
539// ============================================================================
540
541QJsonDocument parseJson(const QByteArray& data, QJsonParseError* error)
542{
543 QJsonParseError localError;
544 QJsonParseError* errorPtr = (error != nullptr) ? error : &localError;
545
546 QJsonDocument doc = QJsonDocument::fromJson(data, errorPtr);
547
548 if (errorPtr->error != QJsonParseError::NoError) {
549 qCWarning(QGCNetworkHelperLog) << "JSON parse error:" << errorPtr->errorString() << "at offset"
550 << errorPtr->offset;
551 return {};
552 }
553
554 return doc;
555}
556
557QJsonDocument parseJsonReply(QNetworkReply* reply, QJsonParseError* error)
558{
559 if (reply == nullptr) {
560 if (error != nullptr) {
561 error->error = QJsonParseError::UnterminatedObject;
562 error->offset = 0;
563 }
564 return {};
565 }
566
567 if (reply->error() != QNetworkReply::NoError) {
568 qCWarning(QGCNetworkHelperLog) << "Network error before JSON parse:" << reply->errorString();
569 if (error != nullptr) {
570 error->error = QJsonParseError::UnterminatedObject;
571 error->offset = 0;
572 }
573 return {};
574 }
575
576 return parseJson(reply->readAll(), error);
577}
578
579bool looksLikeJson(const QByteArray& data)
580{
581 if (data.isEmpty()) {
582 return false;
583 }
584
585 // Skip leading whitespace
586 for (int i = 0; i < data.size(); ++i) {
587 const char c = data.at(i);
588 if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
589 continue;
590 }
591 return c == '{' || c == '[';
592 }
593
594 return false;
595}
596
597// ============================================================================
598// Network Reply Helpers
599// ============================================================================
600
601int httpStatusCode(const QNetworkReply* reply)
602{
603 if (!reply) {
604 return -1;
605 }
606 const QVariant statusAttr = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
607 return statusAttr.isValid() ? statusAttr.toInt() : -1;
608}
609
610QUrl redirectUrl(const QNetworkReply* reply)
611{
612 if (!reply) {
613 return {};
614 }
615
616 const QVariant redirectAttr = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
617 if (redirectAttr.isNull()) {
618 return {};
619 }
620
621 const QUrl redirectTarget = redirectAttr.toUrl();
622 return reply->url().resolved(redirectTarget);
623}
624
625QString errorMessage(const QNetworkReply* reply)
626{
627 if (!reply) {
628 return QStringLiteral("No reply");
629 }
630
631 // Check for network error first
632 if (reply->error() != QNetworkReply::NoError) {
633 return reply->errorString();
634 }
635
636 // Check HTTP status code
637 const int status = httpStatusCode(reply);
638 if (status >= 400) {
639 return QStringLiteral("HTTP %1: %2").arg(status).arg(httpStatusText(status));
640 }
641
642 return {};
643}
644
645bool isSuccess(const QNetworkReply* reply)
646{
647 if (!reply) {
648 return false;
649 }
650
651 if (reply->error() != QNetworkReply::NoError) {
652 return false;
653 }
654
655 const int status = httpStatusCode(reply);
656 return status == -1 || isHttpSuccess(status); // -1 means non-HTTP (e.g., file://)
657}
658
659bool isRedirect(const QNetworkReply* reply)
660{
661 if (!reply) {
662 return false;
663 }
664
665 const int status = httpStatusCode(reply);
666 return isHttpRedirect(status) || !redirectUrl(reply).isEmpty();
667}
668
669QString contentType(const QNetworkReply* reply)
670{
671 if (!reply) {
672 return {};
673 }
674 return reply->header(QNetworkRequest::ContentTypeHeader).toString();
675}
676
677qint64 contentLength(const QNetworkReply* reply)
678{
679 if (!reply) {
680 return -1;
681 }
682 const QVariant lenAttr = reply->header(QNetworkRequest::ContentLengthHeader);
683 return lenAttr.isValid() ? lenAttr.toLongLong() : -1;
684}
685
686bool isJsonResponse(const QNetworkReply* reply)
687{
688 const QString type = contentType(reply);
689 return type.contains(QLatin1String("application/json"), Qt::CaseInsensitive) ||
690 type.contains(QLatin1String("+json"), Qt::CaseInsensitive);
691}
692
693// ============================================================================
694// Network Availability
695// ============================================================================
696
698{
699 if (!QNetworkInformation::loadDefaultBackend()) {
700 qCDebug(QGCNetworkHelperLog) << "Failed to load network information backend";
701 return true; // Assume available if we can't check
702 }
703
704 const QNetworkInformation* netInfo = QNetworkInformation::instance();
705 if (netInfo == nullptr) {
706 return true;
707 }
708
709 return netInfo->reachability() != QNetworkInformation::Reachability::Disconnected;
710}
711
713{
714 if (QNetworkInformation::availableBackends().isEmpty()) {
715 return false;
716 }
717
718 if (!QNetworkInformation::loadDefaultBackend()) {
719 return false;
720 }
721
722 if (!QNetworkInformation::loadBackendByFeatures(QNetworkInformation::Feature::Reachability)) {
723 return false;
724 }
725
726 const QNetworkInformation* netInfo = QNetworkInformation::instance();
727 if (netInfo == nullptr) {
728 return false;
729 }
730
731 return netInfo->reachability() == QNetworkInformation::Reachability::Online;
732}
733
735{
736 if (QNetworkInformation::availableBackends().isEmpty()) {
737 return false;
738 }
739
740 if (!QNetworkInformation::loadDefaultBackend()) {
741 return false;
742 }
743
744 const QNetworkInformation* netInfo = QNetworkInformation::instance();
745 if (netInfo == nullptr) {
746 return false;
747 }
748
749 return netInfo->transportMedium() == QNetworkInformation::TransportMedium::Ethernet;
750}
751
753{
754 const QList<QBluetoothHostInfo> devices = QBluetoothLocalDevice::allDevices();
755 return !devices.isEmpty();
756}
757
759{
760 if (!QNetworkInformation::loadDefaultBackend()) {
761 return ConnectionType::Unknown;
762 }
763
764 const QNetworkInformation* netInfo = QNetworkInformation::instance();
765 if (!netInfo) {
766 return ConnectionType::Unknown;
767 }
768
769 if (netInfo->reachability() == QNetworkInformation::Reachability::Disconnected) {
770 return ConnectionType::None;
771 }
772
773 switch (netInfo->transportMedium()) {
774 case QNetworkInformation::TransportMedium::Ethernet:
775 return ConnectionType::Ethernet;
776 case QNetworkInformation::TransportMedium::WiFi:
777 return ConnectionType::WiFi;
778 case QNetworkInformation::TransportMedium::Cellular:
779 return ConnectionType::Cellular;
780 case QNetworkInformation::TransportMedium::Bluetooth:
781 return ConnectionType::Bluetooth;
782 default:
783 return ConnectionType::Unknown;
784 }
785}
786
788{
789 switch (type) {
790 case ConnectionType::None:
791 return QStringLiteral("None");
792 case ConnectionType::Ethernet:
793 return QStringLiteral("Ethernet");
794 case ConnectionType::WiFi:
795 return QStringLiteral("WiFi");
796 case ConnectionType::Cellular:
797 return QStringLiteral("Cellular");
798 case ConnectionType::Bluetooth:
799 return QStringLiteral("Bluetooth");
800 case ConnectionType::Unknown:
801 default:
802 return QStringLiteral("Unknown");
803 }
804}
805
806// ============================================================================
807// SSL/TLS Helpers
808// ============================================================================
809
810void ignoreSslErrors(QNetworkReply* reply)
811{
812 if (!reply) {
813 return;
814 }
815
816 QObject::connect(reply, &QNetworkReply::sslErrors, reply, [reply](const QList<QSslError>& errors) {
817 qCWarning(QGCNetworkHelperLog) << "Ignoring SSL errors for" << reply->url();
818 for (const QSslError& error : errors) {
819 qCDebug(QGCNetworkHelperLog) << " -" << error.errorString();
820 }
821 reply->ignoreSslErrors();
822 });
823}
824
825void ignoreSslErrorsIfNeeded(QNetworkReply* reply)
826{
827 if (!reply) {
828 return;
829 }
830
831 // Check for OpenSSL version mismatch: Qt built with 1.x but running with 3.x
832 const bool sslLibraryBuildIs1x = ((QSslSocket::sslLibraryBuildVersionNumber() & 0xf0000000) == 0x10000000);
833 const bool sslLibraryIs3x = ((QSslSocket::sslLibraryVersionNumber() & 0xf0000000) == 0x30000000);
834 if (sslLibraryBuildIs1x && sslLibraryIs3x) {
835 qCWarning(QGCNetworkHelperLog) << "Ignoring SSL certificate errors due to OpenSSL version mismatch";
836 QList<QSslError> errorsThatCanBeIgnored;
837 errorsThatCanBeIgnored << QSslError(QSslError::NoPeerCertificate);
838 reply->ignoreSslErrors(errorsThatCanBeIgnored);
839 }
840}
841
843{
844 return QSslSocket::supportsSsl();
845}
846
847QString sslVersion()
848{
849 return QSslSocket::sslLibraryVersionString();
850}
851
852// ============================================================================
853// Network Access Manager Helpers
854// ============================================================================
855
857{
858 QNetworkProxyFactory::setUseSystemConfiguration(true);
859}
860
861QNetworkAccessManager* createNetworkManager(QObject* parent)
862{
863 auto* manager = new QNetworkAccessManager(parent);
864 configureProxy(manager);
865 return manager;
866}
867
868void configureProxy(QNetworkAccessManager* manager)
869{
870 if (!manager) {
871 return;
872 }
873
874#if !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID)
875 QNetworkProxy proxy = manager->proxy();
876 proxy.setType(QNetworkProxy::DefaultProxy);
877 manager->setProxy(proxy);
878#endif
879}
880
881// ============================================================================
882// Compressed Data Helpers
883// ============================================================================
884
889
890QJsonDocument parseCompressedJson(const QByteArray& data, QJsonParseError* error)
891{
892 QByteArray jsonData = data;
893
894 // Decompress if needed
895 if (looksLikeCompressedData(data)) {
896 jsonData = QGCCompression::decompressData(data);
897 if (jsonData.isEmpty()) {
898 if (error) {
899 error->error = QJsonParseError::IllegalValue;
900 error->offset = 0;
901 }
902 return {};
903 }
904 }
905
906 return QJsonDocument::fromJson(jsonData, error);
907}
908
909} // namespace QGCNetworkHelper
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
bool isCompressionFormat(Format format)
Check if format is a compression format (single stream)
QByteArray decompressData(const QByteArray &data, Format format, qint64 maxDecompressedBytes)
Format detectFormatFromData(const QByteArray &data)
HttpStatusClass classifyHttpStatus(int statusCode)
Classify an HTTP status code.
bool isSuccess(const QNetworkReply *reply)
Check if reply indicates success (no error and HTTP 2xx)
QHttpServerRequest::Method HttpMethod
HTTP request methods - uses Qt's QHttpServerRequest::Method enum.
HttpStatusClass
HTTP status code ranges.
void applySslConfig(QNetworkRequest &request, const QSslConfiguration &config)
QHttpServerResponder::StatusCode HttpStatusCode
HTTP status codes - uses Qt's QHttpServerResponder::StatusCode enum.
bool looksLikeJson(const QByteArray &data)
QJsonDocument parseCompressedJson(const QByteArray &data, QJsonParseError *error)
void configureProxy(QNetworkAccessManager *manager)
Set up default proxy configuration on a network manager.
bool isJsonResponse(const QNetworkReply *reply)
Check if response is JSON based on Content-Type.
const QString kContentTypeOctetStream
QNetworkAccessManager * createNetworkManager(QObject *parent)
ConnectionType
Network connection types.
QNetworkRequest createRequest(const QUrl &url, const RequestConfig &config)
bool isRedirect(const QNetworkReply *reply)
Check if reply indicates a redirect.
bool isNetworkEthernet()
Check if current network connection is Ethernet.
QString sslVersion()
Get SSL library version string.
bool isHttpRedirect(int statusCode)
Check if HTTP status indicates redirect (3xx)
HttpMethod parseHttpMethod(const QString &methodStr)
bool isHttpUrl(const QUrl &url)
Check if URL uses HTTP or HTTPS scheme.
QHttpPart createFormField(const QString &name, const QString &value)
void setFormHeaders(QNetworkRequest &request)
Set form data content headers.
QString urlFileName(const QUrl &url)
Extract filename from URL path (last path segment)
QUrl redirectUrl(const QNetworkReply *reply)
void ignoreSslErrorsIfNeeded(QNetworkReply *reply)
void setStandardHeaders(QNetworkRequest &request, const QString &userAgent)
Set standard browser-like headers on a request.
bool isBluetoothAvailable()
Check if Bluetooth is available on this device.
QString contentType(const QNetworkReply *reply)
Get Content-Type header from reply.
int httpStatusCode(const QNetworkReply *reply)
QSslConfiguration createInsecureSslConfig()
QString defaultUserAgent()
Get the default User-Agent string for QGC.
QUrl buildUrl(const QString &baseUrl, const QMap< QString, QString > &params)
Build URL with query parameters from a map.
bool isNetworkAvailable()
Check if network is available (not disconnected)
void setBasicAuth(QNetworkRequest &request, const QString &credentials)
QString createBasicAuthCredentials(const QString &username, const QString &password)
QString httpStatusText(HttpStatusCode statusCode)
bool looksLikeCompressedData(const QByteArray &data)
bool isHttpsUrl(const QUrl &url)
Check if URL uses secure HTTPS scheme.
QString httpMethodName(HttpMethod method)
Get string name for an HTTP method (e.g., "GET", "POST")
bool isHttpSuccess(int statusCode)
Check if HTTP status indicates success (2xx)
ConnectionType connectionType()
Get current network connection type.
QJsonDocument parseJson(const QByteArray &data, QJsonParseError *error)
QSslConfiguration createSslConfig(QSsl::SslProtocol protocol)
void setBearerToken(QNetworkRequest &request, const QString &token)
bool isValidUrl(const QUrl &url)
QHttpPart createFilePart(const QString &name, const QString &fileName, const QString &contentType, QIODevice *device)
QJsonDocument parseJsonReply(QNetworkReply *reply, QJsonParseError *error)
QUrl normalizeUrl(const QUrl &url)
Normalize URL (lowercase scheme/host, remove default ports, trailing slashes)
QUrl urlWithoutQuery(const QUrl &url)
Get URL without query string and fragment.
void setJsonHeaders(QNetworkRequest &request)
Set JSON content headers (Accept and Content-Type)
qint64 contentLength(const QNetworkReply *reply)
Get Content-Length header from reply (-1 if not present)
QString connectionTypeName(ConnectionType type)
Get human-readable name for connection type.
QString errorMessage(const QNetworkReply *reply)
QUrl ensureScheme(const QUrl &url, const QString &defaultScheme)
Ensure URL has scheme, defaulting to https:// if missing.
bool isInternetAvailable()
Check if internet is reachable (online state, stricter than isNetworkAvailable)
void ignoreSslErrors(QNetworkReply *reply)
void configureRequest(QNetworkRequest &request, const RequestConfig &config)
bool isSslAvailable()
Check if SSL is available.
Common request configuration options.
QList< QPair< QNetworkRequest::Attribute, QVariant > > requestAttributes