3
* Client to connect to HTTP server.
9
* Copyright (C) 2008-2009 Urs Fleisch
11
* This file is part of Kid3.
13
* Kid3 is free software; you can redistribute it and/or modify
14
* it under the terms of the GNU General Public License as published by
15
* the Free Software Foundation; either version 2 of the License, or
16
* (at your option) any later version.
18
* Kid3 is distributed in the hope that it will be useful,
19
* but WITHOUT ANY WARRANTY; without even the implied warranty of
20
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
* GNU General Public License for more details.
23
* You should have received a copy of the GNU General Public License
24
* along with this program. If not, see <http://www.gnu.org/licenses/>.
27
#include "httpclient.h"
30
/** Only defined for generation of KDE3 translation files */
31
#define FOR_KDE3_PO_1 I18N_NOOP("Data received: %1")
33
#if QT_VERSION >= 0x040000
38
HttpClient::HttpClient() :
42
connect(m_http, SIGNAL(stateChanged(int)),
43
this, SLOT(slotStateChanged(int)));
44
connect(m_http, SIGNAL(dataReadProgress(int, int)),
45
this, SLOT(slotDataReadProgress(int, int)));
46
connect(m_http, SIGNAL(done(bool)),
47
this, SLOT(slotDone(bool)));
48
connect(m_http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader&)),
49
this, SLOT(slotResponseHeaderReceived(const QHttpResponseHeader&)));
55
HttpClient::~HttpClient()
63
* Called when the connection state changes.
65
* @param state HTTP connection state
67
void HttpClient::slotStateChanged(int state)
70
case QHttp::HostLookup:
71
emitProgress(i18n("Ready."), CS_RequestConnection, CS_EstimatedBytes);
73
case QHttp::Connecting:
74
emitProgress(i18n("Connecting..."), CS_Connecting, CS_EstimatedBytes);
77
emitProgress(i18n("Host found..."), CS_HostFound, CS_EstimatedBytes);
80
emitProgress(i18n("Request sent..."), CS_RequestSent, CS_EstimatedBytes);
82
case QHttp::Connected:
83
emitProgress(i18n("Ready."), -1, -1);
85
case QHttp::Unconnected:
93
* Called to report connection progress.
95
* @param done bytes received
96
* @param total total bytes, 0 if unknown
98
void HttpClient::slotDataReadProgress(int done, int total)
100
emitProgress(KCM_i18n1("Data received: %1", done), done, total);
104
* Called when the request is finished.
106
* @param error true if error occurred
108
void HttpClient::slotDone(bool error)
111
QHttp::Error err = m_http->error();
112
if (err != QHttp::UnexpectedClose) {
113
QString msg(i18n("Socket error: "));
115
case QHttp::ConnectionRefused:
116
msg += i18n("Connection refused");
118
case QHttp::HostNotFound:
119
msg += i18n("Host not found");
122
msg += m_http->errorString();
124
emitProgress(msg, -1, -1);
127
emit bytesReceived(m_http->readAll());
129
emitProgress(i18n("Ready."), CS_EstimatedBytes, CS_EstimatedBytes);
134
* Called when the response header is available.
136
* @param resp HTTP response header
138
void HttpClient::slotResponseHeaderReceived(const QHttpResponseHeader& resp)
140
m_rcvBodyType = resp.contentType();
141
m_rcvBodyLen = resp.contentLength();
145
* Send a HTTP GET request.
147
* @param server host name
148
* @param path path of the URL
150
void HttpClient::sendRequest(const QString& server, const QString& path)
156
splitNamePort(server, dest, destPort);
157
m_http->setHost(dest, destPort);
158
QString proxy, username, password;
160
if (Kid3App::s_miscCfg.m_useProxy) {
161
splitNamePort(Kid3App::s_miscCfg.m_proxy, proxy, proxyPort);
163
if (Kid3App::s_miscCfg.m_useProxyAuthentication) {
164
username = Kid3App::s_miscCfg.m_proxyUserName;
165
password = Kid3App::s_miscCfg.m_proxyPassword;
167
m_http->setProxy(proxy, proxyPort, username, password);
168
m_http->setHost(dest, destPort);
172
void HttpClient::slotHostFound() {}
173
void HttpClient::slotConnected() {}
174
void HttpClient::slotConnectionClosed() {}
175
void HttpClient::slotReadyRead() {}
176
void HttpClient::slotError(int) {}
183
HttpClient::HttpClient() :
184
m_rcvIdx(0), m_rcvBodyIdx(0), m_rcvBodyLen(0)
186
m_sock = new QSocket();
187
connect(m_sock, SIGNAL(connectionClosed()),
188
this, SLOT(slotConnectionClosed()));
189
connect(m_sock, SIGNAL(error(int)),
190
this, SLOT(slotError(int)));
191
connect(m_sock, SIGNAL(hostFound()),
192
this, SLOT(slotHostFound()));
193
connect(m_sock, SIGNAL(connected()),
194
this, SLOT(slotConnected()));
195
connect(m_sock, SIGNAL(readyRead()),
196
this, SLOT(slotReadyRead()));
202
HttpClient::~HttpClient()
205
m_sock->disconnect();
210
* Emit a progress signal with bytes received/total bytes.
212
* @param text state text
214
void HttpClient::emitProgress(const QString& text)
216
emit progress(text, m_rcvIdx - m_rcvBodyIdx, m_rcvBodyLen);
220
* Display status if host is found.
222
void HttpClient::slotHostFound()
224
emitProgress(i18n("Host found..."), CS_HostFound, CS_EstimatedBytes);
228
* Display status if connection is established.
230
void HttpClient::slotConnected()
232
m_sock->QCM_writeBlock(m_request.QCM_latin1(), m_request.length());
233
emitProgress(i18n("Request sent..."), CS_RequestSent, CS_EstimatedBytes);
237
* Read the available bytes.
239
void HttpClient::readBytesAvailable()
241
unsigned long len = m_sock->bytesAvailable();
243
m_rcvBuf.resize(m_rcvIdx + len);
244
unsigned long bytesRead = m_sock->QCM_readBlock(m_rcvBuf.data() + m_rcvIdx,
247
m_rcvIdx += bytesRead;
248
if (bytesRead < len) {
249
m_rcvBuf.resize(m_rcvIdx);
251
if (m_rcvBodyIdx == 0 && m_rcvBodyLen == 0) {
252
QCString str(m_rcvBuf.data(), m_rcvIdx + 1);
253
int contentLengthPos = str.QCM_indexOf("Content-Length:");
254
if (contentLengthPos != -1) {
255
contentLengthPos += 15;
256
while (str[contentLengthPos] == ' ' ||
257
str[contentLengthPos] == '\t') {
260
int contentLengthLen = str.QCM_indexOf("\r\n", contentLengthPos);
261
if (contentLengthLen > contentLengthPos) {
262
contentLengthLen -= contentLengthPos;
264
m_rcvBodyLen = str.mid(contentLengthPos, contentLengthLen).toULong();
266
int contentTypePos = str.QCM_indexOf("Content-Type:");
267
if (contentTypePos != -1) {
268
contentTypePos += 13;
270
while (contentTypePos < static_cast<int>(m_rcvIdx) &&
271
((ch = str[contentTypePos]) == ' ' || ch == '\t')) {
274
int contentTypeEnd = contentTypePos;
275
while (contentTypeEnd < static_cast<int>(m_rcvIdx) &&
276
(ch = str[contentTypeEnd]) != ' ' && ch != '\t' &&
277
ch != '\r' && ch != '\n' && ch != ';') {
280
m_rcvBodyType = str.mid(contentTypePos,
281
contentTypeEnd - contentTypePos);
283
int bodyPos = str.QCM_indexOf("\r\n\r\n");
285
m_rcvBodyIdx = bodyPos + 4;
293
* Read received data when the server has closed the connection.
294
* A bytesReceived() signal is emitted.
296
void HttpClient::slotConnectionClosed()
298
readBytesAvailable();
300
body.duplicate(m_rcvBuf.data() + m_rcvBodyIdx,
301
m_rcvBuf.size() - m_rcvBodyIdx);
302
body.resize(m_rcvBuf.size() - m_rcvBodyIdx + 1);
303
body[m_rcvBuf.size() - m_rcvBodyIdx] = '\0';
304
emit bytesReceived(body);
306
emitProgress(i18n("Ready."), CS_EstimatedBytes, CS_EstimatedBytes);
310
* Display information about read progress.
312
void HttpClient::slotReadyRead()
314
readBytesAvailable();
315
emitProgress(KCM_i18n1("Data received: %1", m_rcvIdx));
319
* Display information about socket error.
321
void HttpClient::slotError(int err)
323
QString msg(i18n("Socket error: "));
325
case QSocket::ErrConnectionRefused:
326
msg += i18n("Connection refused");
328
case QSocket::ErrHostNotFound:
329
msg += i18n("Host not found");
331
case QSocket::ErrSocketRead:
332
msg += i18n("Read failed");
335
msg += QString::number(err);
341
* Send a HTTP GET request.
343
* @param server host name
344
* @param path path of the URL
346
void HttpClient::sendRequest(const QString& server, const QString& path)
350
QString destNamePort(getProxyOrDest(server));
351
splitNamePort(destNamePort, dest, destPort);
354
splitNamePort(server, serverName, serverPort);
356
if (dest != serverName) {
357
m_request += "http://";
358
m_request += serverName;
359
if (serverPort != 80) {
361
m_request += QString::number(serverPort);
365
m_request += " HTTP/1.1\r\nUser-Agent: Kid3/" VERSION "\r\nHost: ";
366
m_request += serverName;
367
m_request += "\r\nConnection: close\r\n\r\n";
375
m_sock->connectToHost(dest, destPort);
376
emitProgress(i18n("Connecting..."), CS_Connecting, CS_EstimatedBytes);
380
* Get string with proxy or destination and port.
381
* If a proxy is set, the proxy is returned, else the real destination.
383
* @param dst real destination
385
* @return "destinationname:port".
387
QString HttpClient::getProxyOrDest(const QString& dst)
390
if (Kid3App::s_miscCfg.m_useProxy) {
391
dest = Kid3App::s_miscCfg.m_proxy;
393
if (dest.isEmpty()) {
399
void HttpClient::slotStateChanged(int) {}
400
void HttpClient::slotDataReadProgress(int, int) {}
401
void HttpClient::slotDone(bool) {}
402
void HttpClient::slotResponseHeaderReceived(const QHttpResponseHeader&) {}
407
* Emit a progress signal with step/total steps.
409
* @param text state text
410
* @param step current step
411
* @param totalSteps total number of steps
413
void HttpClient::emitProgress(const QString& text, int step, int totalSteps)
415
emit progress(text, step, totalSteps);
419
* Extract name and port from string.
421
* @param namePort input string with "name:port"
422
* @param name output string with "name"
423
* @param port output integer with port
425
void HttpClient::splitNamePort(const QString& namePort,
426
QString& name, int& port)
428
int colPos = namePort.QCM_lastIndexOf(':');
431
port = namePort.mid(colPos + 1).toInt(&ok);
433
name = namePort.left(colPos);