File indexing completed on 2024-05-19 04:55:59
0001 /** 0002 * \file httpclient.cpp 0003 * Client to connect to HTTP server. 0004 * 0005 * \b Project: Kid3 0006 * \author Urs Fleisch 0007 * \date 30 Dec 2008 0008 * 0009 * Copyright (C) 2008-2024 Urs Fleisch 0010 * 0011 * This file is part of Kid3. 0012 * 0013 * Kid3 is free software; you can redistribute it and/or modify 0014 * it under the terms of the GNU General Public License as published by 0015 * the Free Software Foundation; either version 2 of the License, or 0016 * (at your option) any later version. 0017 * 0018 * Kid3 is distributed in the hope that it will be useful, 0019 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0021 * GNU General Public License for more details. 0022 * 0023 * You should have received a copy of the GNU General Public License 0024 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0025 */ 0026 0027 #include "httpclient.h" 0028 #include <QNetworkAccessManager> 0029 #include <QNetworkRequest> 0030 #include <QNetworkProxy> 0031 #include <QByteArray> 0032 #include <QTimer> 0033 #include <QDateTime> 0034 #include "networkconfig.h" 0035 0036 0037 /** Time when last request was sent to server */ 0038 QMap<QString, QDateTime> HttpClient::s_lastRequestTime; 0039 /** Minimum interval between two requests to server in ms */ 0040 QMap<QString, int> HttpClient::s_minimumRequestInterval; 0041 0042 /** 0043 * Used to initialize the minimum interval for some servers 0044 * at static initialization time. 0045 * 0046 * Rate limit requests to servers, MusicBrainz and Discogs impose a limit of 0047 * one request per second 0048 * http://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#Source_IP_address 0049 * http://www.discogs.com/developers/accessing.html#rate-limiting 0050 */ 0051 static struct MinimumRequestIntervalInitializer { 0052 MinimumRequestIntervalInitializer() { 0053 HttpClient::s_minimumRequestInterval[QLatin1String("musicbrainz.org")] = 1000; 0054 HttpClient::s_minimumRequestInterval[QLatin1String("api.discogs.com")] = 1000; 0055 HttpClient::s_minimumRequestInterval[QLatin1String("www.discogs.com")] = 1000; 0056 HttpClient::s_minimumRequestInterval[QLatin1String("www.amazon.com")] = 1000; 0057 HttpClient::s_minimumRequestInterval[QLatin1String("images.amazon.com")] = 1000; 0058 HttpClient::s_minimumRequestInterval[QLatin1String("www.gnudb.org")] = 1000; 0059 HttpClient::s_minimumRequestInterval[QLatin1String("gnudb.gnudb.org")] = 1000; 0060 HttpClient::s_minimumRequestInterval[QLatin1String("api.acoustid.org")] = 1000; 0061 } 0062 } minimumRequestIntervalInitializer; 0063 0064 /** 0065 * Constructor. 0066 * 0067 * @param netMgr network access manager 0068 */ 0069 HttpClient::HttpClient(QNetworkAccessManager* netMgr) 0070 : QObject(netMgr), m_netMgr(netMgr), m_rcvBodyLen(0), 0071 m_requestTimer(new QTimer(this)) 0072 { 0073 setObjectName(QLatin1String("HttpClient")); 0074 m_requestTimer->setSingleShot(true); 0075 connect(m_requestTimer, &QTimer::timeout, this, &HttpClient::delayedSendRequest); 0076 } 0077 0078 /** 0079 * Destructor. 0080 */ 0081 HttpClient::~HttpClient() 0082 { 0083 if (m_reply) { 0084 m_reply->close(); 0085 m_reply->disconnect(); 0086 m_reply->deleteLater(); 0087 } 0088 } 0089 0090 /** Only defined for generation of translation files */ 0091 #define DATA_RECEIVED_FOR_PO QT_TRANSLATE_NOOP("@default", "Data received: %1") 0092 0093 /** 0094 * Called when the request is finished. 0095 */ 0096 void HttpClient::networkReplyFinished() 0097 { 0098 if (auto reply = qobject_cast<QNetworkReply*>(sender())) { 0099 QByteArray data(reply->readAll()); 0100 m_rcvBodyType = reply->header(QNetworkRequest::ContentTypeHeader).toString(); 0101 m_rcvBodyLen = reply->header(QNetworkRequest::ContentLengthHeader).toUInt(); 0102 QString msg(tr("Ready.")); 0103 if (reply->error() != QNetworkReply::NoError) { 0104 msg = tr("Error"); 0105 msg += QLatin1String(": "); 0106 msg += reply->errorString(); 0107 } else { 0108 if (QVariant redirect = 0109 reply->attribute(QNetworkRequest::RedirectionTargetAttribute); 0110 !redirect.isNull()) { 0111 QUrl redirectUrl = redirect.toUrl(); 0112 if (redirectUrl.isRelative()) { 0113 redirectUrl = reply->url().resolved(redirectUrl); 0114 } 0115 if (redirectUrl.isValid()) { 0116 reply->deleteLater(); 0117 0118 QNetworkRequest request(redirectUrl); 0119 reply = m_netMgr->get(request); 0120 m_reply = reply; 0121 connect(reply, &QNetworkReply::finished, 0122 this, &HttpClient::networkReplyFinished); 0123 connect(reply, &QNetworkReply::downloadProgress, 0124 this, &HttpClient::networkReplyProgress); 0125 #if QT_VERSION >= 0x050f00 0126 connect(reply, &QNetworkReply::errorOccurred, 0127 this, &HttpClient::networkReplyError); 0128 #else 0129 connect(reply, 0130 static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>( 0131 &QNetworkReply::error), 0132 this, &HttpClient::networkReplyError); 0133 #endif 0134 return; 0135 } 0136 } 0137 } 0138 emit bytesReceived(data); 0139 emitProgress(msg, data.size(), data.size()); 0140 reply->deleteLater(); 0141 } 0142 } 0143 0144 /** 0145 * Called to report connection progress. 0146 * 0147 * @param received bytes received 0148 * @param total total bytes 0149 */ 0150 void HttpClient::networkReplyProgress(qint64 received, qint64 total) 0151 { 0152 emitProgress(tr("Data received: %1").arg(received), received, total); 0153 } 0154 0155 /** 0156 * Called when an error occurred. 0157 */ 0158 void HttpClient::networkReplyError(QNetworkReply::NetworkError) 0159 { 0160 if (auto reply = qobject_cast<QNetworkReply*>(sender())) { 0161 emitProgress(reply->errorString(), -1, -1); 0162 } 0163 } 0164 0165 /** 0166 * Send a HTTP GET request. 0167 * 0168 * @param url URL 0169 * @param headers optional raw headers to send 0170 */ 0171 void HttpClient::sendRequest(const QUrl& url, const RawHeaderMap& headers) 0172 { 0173 QString host = url.host(); 0174 qint64 msSinceLastRequest; 0175 int minimumRequestInterval; 0176 QDateTime now = QDateTime::currentDateTime(); 0177 QDateTime lastRequestTime = s_lastRequestTime.value(host); 0178 if (lastRequestTime.isValid() && 0179 (minimumRequestInterval = s_minimumRequestInterval.value(host)) > 0 && 0180 (msSinceLastRequest = lastRequestTime.msecsTo(now)) < 0181 minimumRequestInterval) { 0182 // Delay request to comply with minimum interval 0183 m_delayedSendRequestContext.url = url; 0184 m_delayedSendRequestContext.headers = headers; 0185 m_requestTimer->start(minimumRequestInterval - 0186 static_cast<int>(msSinceLastRequest)); 0187 return; 0188 } 0189 0190 m_rcvBodyLen = 0; 0191 m_rcvBodyType = QLatin1String(""); 0192 QString proxy, username, password; 0193 int proxyPort = 0; 0194 QNetworkProxy::ProxyType proxyType = QNetworkProxy::NoProxy; 0195 const NetworkConfig& networkCfg = NetworkConfig::instance(); 0196 if (networkCfg.useProxy()) { 0197 splitNamePort(networkCfg.proxy(), proxy, proxyPort); 0198 proxyType = QNetworkProxy::HttpProxy; 0199 } 0200 if (networkCfg.useProxyAuthentication()) { 0201 username = networkCfg.proxyUserName(); 0202 password = networkCfg.proxyPassword(); 0203 } 0204 m_netMgr->setProxy(QNetworkProxy(proxyType, proxy, 0205 static_cast<quint16>(proxyPort), 0206 username, password)); 0207 0208 QNetworkRequest request(url); 0209 for (auto it = headers.constBegin(); it != headers.constEnd(); ++it) { 0210 request.setRawHeader(it.key(), it.value()); 0211 } 0212 QNetworkReply* reply = m_netMgr->get(request); 0213 m_reply = reply; 0214 connect(reply, &QNetworkReply::finished, 0215 this, &HttpClient::networkReplyFinished); 0216 connect(reply, &QNetworkReply::downloadProgress, 0217 this, &HttpClient::networkReplyProgress); 0218 #if QT_VERSION >= 0x050f00 0219 connect(reply, &QNetworkReply::errorOccurred, 0220 this, &HttpClient::networkReplyError); 0221 #else 0222 connect(reply, 0223 static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>( 0224 &QNetworkReply::error), 0225 this, &HttpClient::networkReplyError); 0226 #endif 0227 s_lastRequestTime[host] = now; 0228 emitProgress(tr("Request sent..."), 0, 0); 0229 } 0230 0231 /** 0232 * Send a HTTP GET request. 0233 * 0234 * @param server host name 0235 * @param path path of the URL 0236 * @param scheme scheme, default is "http" 0237 * @param headers optional raw headers to send 0238 */ 0239 void HttpClient::sendRequest(const QString& server, const QString& path, 0240 const QString& scheme, const RawHeaderMap& headers) 0241 { 0242 QString host(server); 0243 if (host.endsWith(QLatin1String(":80"))) { 0244 host.chop(3); 0245 } 0246 QUrl url; 0247 url.setUrl(scheme + QLatin1String("://") + host + path); 0248 sendRequest(url, headers); 0249 } 0250 0251 /** 0252 * Called to start delayed sendRequest(). 0253 */ 0254 void HttpClient::delayedSendRequest() 0255 { 0256 sendRequest(m_delayedSendRequestContext.url, 0257 m_delayedSendRequestContext.headers); 0258 } 0259 0260 /** 0261 * Abort request. 0262 */ 0263 void HttpClient::abort() 0264 { 0265 if (m_reply) { 0266 m_reply->abort(); 0267 } 0268 } 0269 0270 /** 0271 * Emit a progress signal with step/total steps. 0272 * 0273 * @param text state text 0274 * @param step current step 0275 * @param totalSteps total number of steps 0276 */ 0277 void HttpClient::emitProgress(const QString& text, int step, int totalSteps) 0278 { 0279 emit progress(text, step, totalSteps); 0280 } 0281 0282 /** 0283 * Extract name and port from string. 0284 * 0285 * @param namePort input string with "name:port" 0286 * @param name output string with "name" 0287 * @param port output integer with port 0288 */ 0289 void HttpClient::splitNamePort(const QString& namePort, 0290 QString& name, int& port) 0291 { 0292 if (int colPos = namePort.lastIndexOf(QLatin1Char(':')); colPos >= 0) { 0293 bool ok; 0294 #if QT_VERSION >= 0x060000 0295 port = namePort.mid(colPos + 1).toInt(&ok); 0296 #else 0297 port = namePort.midRef(colPos + 1).toInt(&ok); 0298 #endif 0299 if (!ok) port = 80; 0300 name = namePort.left(colPos); 0301 } else { 0302 name = namePort; 0303 port = 80; 0304 } 0305 }