swift
networkutils.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2013 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QAbstractSocket>
7 #include <QDateTime>
8 #include <QHostAddress>
9 #include <QList>
10 #include <QMetaEnum>
11 #include <QNetworkInterface>
12 #include <QNetworkReply>
13 #include <QObject>
14 #include <QRegularExpression>
15 #include <QSignalMapper>
16 #include <QStringBuilder>
17 #include <QTcpSocket>
18 #include <QUrl>
19 #include <QUrlQuery>
20 #include <QVariant>
21 
22 #include "config/buildconfig.h"
23 #include "misc/eventloop.h"
24 #include "misc/network/server.h"
25 #include "misc/stringutils.h"
26 
27 using namespace swift::config;
28 using namespace swift::misc;
29 using namespace swift::misc::network;
30 
31 namespace swift::misc::network
32 {
33  int CNetworkUtils::getTimeoutMs() { return 5000; }
34 
35  int CNetworkUtils::getLongTimeoutMs() { return 3 * getTimeoutMs(); }
36 
37  QStringList CNetworkUtils::getKnownLocalIpV4Addresses()
38  {
39  QStringList ips;
40  const QList<QHostAddress> allAddresses = QNetworkInterface::allAddresses();
41  for (const QHostAddress &address : allAddresses)
42  {
43  if (address.isNull()) { continue; }
44  if (address.protocol() == QAbstractSocket::IPv4Protocol)
45  {
46  const QString a = address.toString();
47  ips.append(a);
48  }
49  }
50  ips.sort();
51  return ips;
52  }
53 
54  bool CNetworkUtils::canConnect(const QString &hostAddress, int port, QString &message, int timeoutMs)
55  {
56  if (timeoutMs < 0) { timeoutMs = getTimeoutMs(); } // external functions might call with -1
57  QTcpSocket socket;
58  QSignalMapper mapper;
59  QObject::connect(&socket, &QTcpSocket::connected, &mapper, qOverload<>(&QSignalMapper::map));
60  QObject::connect(&socket, &QAbstractSocket::errorOccurred, &mapper, qOverload<>(&QSignalMapper::map));
61  mapper.setMapping(&socket, 0);
62 
63  CEventLoop eventLoop;
64  eventLoop.stopWhen(&mapper, &QSignalMapper::mappedInt);
65  socket.connectToHost(hostAddress, static_cast<quint16>(port));
66  const bool timedOut = !eventLoop.exec(timeoutMs);
67 
68  if (socket.state() != QTcpSocket::ConnectedState)
69  {
70  static const QString e("Connection failed: '%1'");
71  message = timedOut ? e.arg("Timed out") : e.arg(socket.errorString());
72  return false;
73  }
74  else
75  {
76  static const QString ok("OK, connected");
77  message = ok;
78  return true;
79  }
80  }
81 
82  bool CNetworkUtils::canConnect(const network::CServer &server, QString &message, int timeoutMs)
83  {
84  return CNetworkUtils::canConnect(server.getAddress(), server.getPort(), message, timeoutMs);
85  }
86 
87  bool CNetworkUtils::canConnect(const QString &url, QString &message, int timeoutMs)
88  {
89  if (url.isEmpty())
90  {
91  message = QObject::tr("Missing URL", "Misc");
92  return false;
93  }
94  return canConnect(QUrl(url), message, timeoutMs);
95  }
96 
97  bool CNetworkUtils::canConnect(const QUrl &url, QString &message, int timeoutMs)
98  {
99  if (!url.isValid())
100  {
101  message = QObject::tr("Invalid URL: %1", "Misc").arg(url.toString());
102  return false;
103  }
104 
105  if (url.isRelative())
106  {
107  message = QObject::tr("Relative URL cannot be tested: %1", "Misc").arg(url.toString());
108  return false;
109  }
110 
111  const QString host(url.host());
112  const QString scheme(url.scheme().toLower());
113  int p = url.port();
114  if (p < 0) { p = scheme.contains("https") ? 443 : 80; }
115  return canConnect(host, p, message, timeoutMs);
116  }
117 
118  bool CNetworkUtils::canConnect(const QUrl &url, int timeoutMs)
119  {
120  QString m;
121  return canConnect(url, m, timeoutMs);
122  }
123 
124  bool CNetworkUtils::isValidIPv4Address(const QString &candidate)
125  {
126  QHostAddress address(candidate);
127  return (QAbstractSocket::IPv4Protocol == address.protocol());
128  }
129 
130  bool CNetworkUtils::isValidIPv6Address(const QString &candidate)
131  {
132  QHostAddress address(candidate);
133  return (QAbstractSocket::IPv6Protocol == address.protocol());
134  }
135 
136  bool CNetworkUtils::isValidPort(const QString &port)
137  {
138  bool success;
139  int p = port.toInt(&success);
140  if (!success) return false;
141  return (p >= 1 && p <= 65535);
142  }
143 
144  QString CNetworkUtils::buildUrl(const QString &protocol, const QString &server, const QString &baseUrl,
145  const QString &serviceUrl)
146  {
147  Q_ASSERT_X(protocol.length() > 3, Q_FUNC_INFO, "worng protocol");
148  Q_ASSERT_X(!server.isEmpty(), Q_FUNC_INFO, "missing server");
149  Q_ASSERT_X(!serviceUrl.isEmpty(), Q_FUNC_INFO, "missing service URL");
150 
151  QString url(server);
152  if (!baseUrl.isEmpty()) { url.append("/").append(baseUrl); }
153  url.append("/").append(serviceUrl);
154  url.replace("//", "/");
155  return protocol + "://" + url;
156  }
157 
158  void CNetworkUtils::setSwiftUserAgent(QNetworkRequest &request, const QString &userAgentDetails)
159  {
160  static const QString defaultUserAgent("swift/" + CBuildConfig::getVersionString());
161 
162  // User-Agent is known header, we use high level setHeader not setRawHeader
163  const QVariant agent = QVariant::fromValue(
164  userAgentDetails.isEmpty() ? defaultUserAgent : defaultUserAgent + "/" + userAgentDetails);
165  request.setHeader(QNetworkRequest::UserAgentHeader, agent);
166  }
167 
168  void CNetworkUtils::addDebugFlag(QUrlQuery &qurl) { qurl.addQueryItem("XDEBUG_SESSION_START", "ECLIPSE_DBGP"); }
169 
170  QNetworkRequest CNetworkUtils::getSwiftNetworkRequest(const QUrl &url, RequestType type,
171  const QString &userAgentDetails)
172  {
173  QNetworkRequest request(url);
174  switch (type)
175  {
176  case PostUrlEncoded:
177  request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
178  break;
179  default: break;
180  }
181  CNetworkUtils::setSwiftUserAgent(request, userAgentDetails);
182  return request;
183  }
184 
185  QNetworkRequest CNetworkUtils::getSwiftNetworkRequest(const QNetworkRequest &request,
186  const QString &userAgentDetails)
187  {
188  QNetworkRequest req(request); // copy
189  CNetworkUtils::setSwiftUserAgent(req, userAgentDetails);
190  return req;
191  }
192 
193  qint64 CNetworkUtils::lastModifiedMsSinceEpoch(const QNetworkReply *nwReply)
194  {
195  Q_ASSERT(nwReply);
196  const QDateTime lm = CNetworkUtils::lastModifiedDateTime(nwReply);
197  return lm.isValid() ? lm.toMSecsSinceEpoch() : -1;
198  }
199 
200  QDateTime CNetworkUtils::lastModifiedDateTime(const QNetworkReply *nwReply)
201  {
202  Q_ASSERT(nwReply);
203  const QVariant lastModifiedQv = nwReply->header(QNetworkRequest::LastModifiedHeader);
204  if (lastModifiedQv.isValid() && lastModifiedQv.canConvert<QDateTime>())
205  {
206  return lastModifiedQv.value<QDateTime>();
207  }
208  return QDateTime();
209  }
210 
211  qint64 CNetworkUtils::lastModifiedSinceNow(const QNetworkReply *nwReply)
212  {
213  const qint64 sinceEpoch = CNetworkUtils::lastModifiedMsSinceEpoch(nwReply);
214  return sinceEpoch > 0 ? std::max(0LL, QDateTime::currentMSecsSinceEpoch() - sinceEpoch) :
215  QDateTime::currentMSecsSinceEpoch();
216  }
217 
218  qint64 CNetworkUtils::requestDuration(const QNetworkReply *nwReply)
219  {
220  if (!nwReply) { return -1; }
221  const QVariant started = nwReply->property("started");
222  if (started.isValid() && started.canConvert<qint64>())
223  {
224  const qint64 now = QDateTime::currentMSecsSinceEpoch();
225  const qint64 start = started.value<qint64>();
226  return (now - start);
227  }
228  return -1;
229  }
230 
231  int CNetworkUtils::getHttpStatusCode(QNetworkReply *nwReply)
232  {
233  if (!nwReply) { return -1; }
234  const QVariant statusCode = nwReply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
235  if (!statusCode.isValid()) { return -1; }
236  const int status = statusCode.toInt();
237  return status;
238  }
239 
240  bool CNetworkUtils::isHttpStatusRedirect(QNetworkReply *nwReply)
241  {
242  if (!nwReply) { return false; }
243  const int code = CNetworkUtils::getHttpStatusCode(nwReply);
244  return code == 301 || code == 302 || code == 303 || code == 307;
245  }
246 
247  QUrl CNetworkUtils::getHttpRedirectUrl(QNetworkReply *nwReply)
248  {
249  if (!nwReply) { return QUrl(); }
250  const QVariant possibleRedirectUrl = nwReply->attribute(QNetworkRequest::RedirectionTargetAttribute);
251  if (!possibleRedirectUrl.isValid()) { return QUrl(); }
252  QUrl redirectUrl = possibleRedirectUrl.toUrl();
253  if (redirectUrl.isRelative()) { redirectUrl = nwReply->url().resolved(redirectUrl); }
254  return redirectUrl;
255  }
256 
257  QString CNetworkUtils::removeHtmlPartsFromPhpErrorMessage(const QString &errorMessage)
258  {
259  if (errorMessage.isEmpty()) { return errorMessage; }
260  QString phpError(errorMessage);
261  thread_local const QRegularExpression regEx("<[^>]*>");
262  return phpError.remove(regEx);
263  }
264 
265  bool CNetworkUtils::looksLikePhpErrorMessage(const QString &errorMessage)
266  {
267  if (errorMessage.length() < 50) { return false; }
268  if (errorMessage.contains("xdebug", Qt::CaseInsensitive)) { return true; }
269  return false;
270  }
271 
272  const QString &CNetworkUtils::networkOperationToString(QNetworkAccessManager::Operation operation)
273  {
274  static const QString h("HEAD");
275  static const QString g("GET");
276  static const QString put("PUT");
277  static const QString d("DELETE");
278  static const QString post("POST");
279  static const QString c("custom");
280  static const QString u("unknown");
281 
282  switch (operation)
283  {
284  case QNetworkAccessManager::HeadOperation: return h;
285  case QNetworkAccessManager::GetOperation: return g;
286  case QNetworkAccessManager::PutOperation: return put;
287  case QNetworkAccessManager::PostOperation: return post;
288  case QNetworkAccessManager::DeleteOperation: return d;
289  case QNetworkAccessManager::CustomOperation: return c;
290  case QNetworkAccessManager::UnknownOperation:
291  default: break;
292  }
293  return u;
294  }
295 } // namespace swift::misc::network
static const QString & getVersionString()
Version as QVersionNumber.
Utility class which blocks until a signal is emitted or timeout reached.
Definition: eventloop.h:20
bool exec(int timeoutMs)
Begin processing events until the timeout or stop condition occurs.
Definition: eventloop.h:51
void stopWhen(const T *sender, F signal)
Event loop will stop if the given signal is received.
Definition: eventloop.h:34
Value object encapsulating information of a server.
Definition: server.h:28
int getPort() const
Get port.
Definition: server.h:116
const QString & getAddress() const
Get address.
Definition: server.h:80
Free functions in swift::misc.