2
* httppoll.cpp - HTTP polling proxy
3
* Copyright (C) 2003 Justin Karneges
5
* This library is free software; you can redistribute it and/or
6
* modify it under the terms of the GNU Lesser General Public
7
* License as published by the Free Software Foundation; either
8
* version 2.1 of the License, or (at your option) any later version.
10
* This library is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
* Lesser General Public License for more details.
15
* You should have received a copy of the GNU Lesser General Public
16
* License along with this library; if not, write to the Free Software
17
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24
#include <qstringlist.h>
40
static QByteArray randomArray(int size)
44
for(int n = 0; n < size; ++n)
45
a[n] = (char)(256.0*rand()/(RAND_MAX+1.0));
49
//----------------------------------------------------------------------------
51
//----------------------------------------------------------------------------
52
static QString hpk(int n, const QString &s)
57
return QCA::Base64().arrayToString( QCA::Hash("sha1").hash( hpk(n - 1, s).toLatin1() ).toByteArray() );
60
class HttpPoll::Private
63
Private(HttpPoll *_q) :
83
QString key[POLL_KEYS];
89
HttpPoll::HttpPoll(QObject *parent)
92
d = new Private(this);
95
d->t = new QTimer(this);
96
d->t->setSingleShot(true);
97
connect(d->t, SIGNAL(timeout()), SLOT(do_sync()));
99
connect(&d->http, SIGNAL(result()), SLOT(http_result()));
100
connect(&d->http, SIGNAL(error(int)), SLOT(http_error(int)));
105
HttpPoll::~HttpPoll()
112
void HttpPoll::reset(bool clear)
114
if(d->http.isActive())
125
void HttpPoll::setAuth(const QString &user, const QString &pass)
131
void HttpPoll::connectToUrl(const QString &url)
133
connectToHost("", 0, url);
136
void HttpPoll::connectToHost(const QString &proxyHost, int proxyPort, const QString &url)
143
if(!proxyHost.isEmpty()) {
154
else if (u.scheme() == "https") {
158
d->url = u.path() + "?" + u.encodedQuery();
159
d->use_proxy = false;
164
QString key = getKey(&last);
167
fprintf(stderr, "HttpPoll: Connecting to %s:%d [%s]", d->host.latin1(), d->port, d->url.latin1());
168
if(d->user.isEmpty())
169
fprintf(stderr, "\n");
171
fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1());
173
QPointer<QObject> self = this;
179
d->http.setUseSsl(useSsl);
180
d->http.setAuth(d->user, d->pass);
181
d->http.post(d->host, d->port, d->url, makePacket("0", key, "", QByteArray()), d->use_proxy);
184
QByteArray HttpPoll::makePacket(const QString &ident, const QString &key, const QString &newkey, const QByteArray &block)
191
if(!newkey.isEmpty()) {
196
QByteArray cs = str.toLatin1();
197
int len = cs.length();
200
a.resize(len + block.size());
201
memcpy(a.data(), cs.data(), len);
202
memcpy(a.data() + len, block.data(), block.size());
206
int HttpPoll::pollInterval() const
211
void HttpPoll::setPollInterval(int seconds)
213
d->polltime = seconds;
216
bool HttpPoll::isOpen() const
218
return (d->state == 2 ? true: false);
221
void HttpPoll::close()
223
if(d->state == 0 || d->closing)
226
if(bytesToWrite() == 0)
232
void HttpPoll::http_result()
234
// check for death :)
235
QPointer<QObject> self = this;
242
QString cookie = d->http.getHeader("Set-Cookie");
243
int n = cookie.indexOf("ID=");
250
int n2 = cookie.indexOf(';', n);
252
id = cookie.mid(n, n2-n);
255
QByteArray block = d->http.body();
258
if(id.right(2) == ":0") {
259
if(id == "0:0" && d->state == 2) {
272
bool justNowConnected = false;
275
justNowConnected = true;
278
// sync up again soon
279
if(bytesToWrite() > 0 || !d->closing) {
280
d->t->start(d->polltime * 1000);
284
if(justNowConnected) {
288
if(!d->out.isEmpty()) {
289
int x = d->out.size();
299
if(!block.isEmpty()) {
307
if(bytesToWrite() > 0) {
313
delayedCloseFinished();
319
void HttpPoll::http_error(int x)
322
if(x == HttpProxyPost::ErrConnectionRefused)
323
error(ErrConnectionRefused);
324
else if(x == HttpProxyPost::ErrHostNotFound)
325
error(ErrHostNotFound);
326
else if(x == HttpProxyPost::ErrSocket)
328
else if(x == HttpProxyPost::ErrProxyConnect)
329
error(ErrProxyConnect);
330
else if(x == HttpProxyPost::ErrProxyNeg)
332
else if(x == HttpProxyPost::ErrProxyAuth)
336
int HttpPoll::tryWrite()
338
if(!d->http.isActive())
343
void HttpPoll::do_sync()
345
if(d->http.isActive())
349
d->out = takeWrite(0, false);
352
QString key = getKey(&last);
356
newkey = getKey(&last);
359
QPointer<QObject> self = this;
364
d->http.post(d->host, d->port, d->url, makePacket(d->ident, key, newkey, d->out), d->use_proxy);
367
void HttpPoll::resetKey()
370
fprintf(stderr, "HttpPoll: reset key!\n");
372
QByteArray a = randomArray(64);
373
QString str = QString::fromLatin1(a.data(), a.size());
375
d->key_n = POLL_KEYS;
376
for(int n = 0; n < POLL_KEYS; ++n)
377
d->key[n] = hpk(n+1, str);
380
const QString & HttpPoll::getKey(bool *last)
386
return d->key[d->key_n];
390
//----------------------------------------------------------------------------
392
//----------------------------------------------------------------------------
393
static QString extractLine(QByteArray *buf, bool *found)
397
for(n = 0; n < (int)buf->size()-1; ++n) {
398
if(buf->at(n) == '\r' && buf->at(n+1) == '\n') {
401
memcpy(cstr.data(), buf->data(), n);
402
n += 2; // hack off CR/LF
404
memmove(buf->data(), buf->data() + n, buf->size() - n);
405
buf->resize(buf->size() - n);
406
QString s = QString::fromUtf8(cstr);
419
static bool extractMainHeader(const QString &line, QString *proto, int *code, QString *msg)
421
int n = line.indexOf(' ');
425
*proto = line.mid(0, n);
427
int n2 = line.indexOf(' ', n);
431
*code = line.mid(n, n2-n).toInt();
438
class HttpProxyPost::Private
441
Private(HttpProxyPost *_q) :
453
QHostAddress lastAddress;
454
QByteArray postdata, recvBuf, body;
458
QStringList headerLines;
465
HttpProxyPost::HttpProxyPost(QObject *parent)
468
d = new Private(this);
469
connect(&d->sock, SIGNAL(connected()), SLOT(sock_connected()));
470
connect(&d->sock, SIGNAL(connectionClosed()), SLOT(sock_connectionClosed()));
471
connect(&d->sock, SIGNAL(readyRead()), SLOT(sock_readyRead()));
472
connect(&d->sock, SIGNAL(error(int)), SLOT(sock_error(int)));
476
HttpProxyPost::~HttpProxyPost()
482
void HttpProxyPost::setUseSsl(bool state)
487
void HttpProxyPost::reset(bool clear)
489
if(d->sock.state() != BSocket::Idle)
491
d->recvBuf.resize(0);
496
void HttpProxyPost::setAuth(const QString &user, const QString &pass)
502
bool HttpProxyPost::isActive() const
504
return (d->sock.state() == BSocket::Idle ? false: true);
507
void HttpProxyPost::post(const QString &proxyHost, int proxyPort, const QString &url, const QByteArray &data, bool asProxy)
514
d->asProxy = asProxy;
517
fprintf(stderr, "HttpProxyPost: Connecting to %s:%d", proxyHost.latin1(), proxyPort);
518
if(d->user.isEmpty())
519
fprintf(stderr, "\n");
521
fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1());
523
if (d->sock.state() != QAbstractSocket::ConnectingState) { // in case of http/1.1 it may be connected
524
if (d->lastAddress.isNull()) {
525
d->sock.connectToHost(proxyHost, proxyPort);
527
d->sock.connectToHost(d->lastAddress, proxyPort);
532
void HttpProxyPost::stop()
537
QByteArray HttpProxyPost::body() const
542
QString HttpProxyPost::getHeader(const QString &var) const
544
for(QStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it) {
545
const QString &s = *it;
546
int n = s.indexOf(": ");
549
QString v = s.mid(0, n);
550
if(v.toLower() == var.toLower())
556
void HttpProxyPost::sock_connected()
559
fprintf(stderr, "HttpProxyPost: Connected\n");
562
d->tls = new QCA::TLS(this);
563
connect(d->tls, SIGNAL(readyRead()), SLOT(tls_readyRead()));
564
connect(d->tls, SIGNAL(readyReadOutgoing()), SLOT(tls_readyReadOutgoing()));
565
connect(d->tls, SIGNAL(error()), SLOT(tls_error()));
566
d->tls->startClient();
569
d->lastAddress = d->sock.peerAddress();
571
d->headerLines.clear();
575
// connected, now send the request
577
s += QString("POST ") + d->url + " HTTP/1.1\r\n";
579
if(!d->user.isEmpty()) {
580
QString str = d->user + ':' + d->pass;
581
s += QString("Proxy-Authorization: Basic ") + QCA::Base64().encodeString(str) + "\r\n";
583
s += "Pragma: no-cache\r\n";
584
s += QString("Host: ") + u.host() + "\r\n";
587
s += QString("Host: ") + d->host + "\r\n";
589
s += "Content-Type: application/x-www-form-urlencoded\r\n";
590
s += QString("Content-Length: ") + QString::number(d->postdata.size()) + "\r\n";
595
d->tls->write(s.toUtf8());
598
d->tls->write(d->postdata);
601
d->sock.write(s.toUtf8());
604
d->sock.write(d->postdata);
608
void HttpProxyPost::sock_connectionClosed()
610
d->body = d->recvBuf;
615
void HttpProxyPost::tls_readyRead()
617
//printf("tls_readyRead\n");
618
processData(d->tls->read());
621
void HttpProxyPost::tls_readyReadOutgoing()
623
//printf("tls_readyReadOutgoing\n");
624
d->sock.write(d->tls->readOutgoing());
627
void HttpProxyPost::tls_error()
630
fprintf(stderr, "HttpProxyGetStream: ssl error: %d\n", d->tls->errorCode());
633
error(ErrConnectionRefused); // FIXME: bogus error
636
void HttpProxyPost::sock_readyRead()
638
QByteArray block = d->sock.read();
640
d->tls->writeIncoming(block);
645
void HttpProxyPost::processData(const QByteArray &block)
647
ByteStream::appendArray(&d->recvBuf, block);
650
// grab available lines
653
QString line = extractLine(&d->recvBuf, &found);
660
d->headerLines += line;
663
// done with grabbing the header?
665
QString str = d->headerLines.first();
666
d->headerLines.takeFirst();
671
if(!extractMainHeader(str, &proto, &code, &msg)) {
673
fprintf(stderr, "HttpProxyPost: invalid header!\n");
681
fprintf(stderr, "HttpProxyPost: header proto=[%s] code=[%d] msg=[%s]\n", proto.latin1(), code, msg.latin1());
682
for(QStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it)
683
fprintf(stderr, "HttpProxyPost: * [%s]\n", (*it).latin1());
687
if(code == 200) { // OK
689
fprintf(stderr, "HttpProxyPost: << Success >>\n");
695
if(code == 407) { // Authentication failed
697
errStr = tr("Authentication failed");
699
else if(code == 404) { // Host not found
700
err = ErrHostNotFound;
701
errStr = tr("Host not found");
703
else if(code == 403) { // Access denied
705
errStr = tr("Access denied");
707
else if(code == 503) { // Connection refused
708
err = ErrConnectionRefused;
709
errStr = tr("Connection refused");
711
else { // invalid reply
713
errStr = tr("Invalid reply");
717
fprintf(stderr, "HttpProxyPost: << Error >> [%s]\n", errStr.latin1());
727
void HttpProxyPost::sock_error(int x)
730
fprintf(stderr, "HttpProxyPost: socket error: %d\n", x);
733
if(x == BSocket::ErrHostNotFound)
734
error(ErrProxyConnect);
735
else if(x == BSocket::ErrConnectionRefused)
736
error(ErrProxyConnect);
737
else if(x == BSocket::ErrRead)
741
//----------------------------------------------------------------------------
742
// HttpProxyGetStream
743
//----------------------------------------------------------------------------
744
class HttpProxyGetStream::Private
747
Private(HttpProxyGetStream *_q) :
757
QStringList headerLines;
766
HttpProxyGetStream::HttpProxyGetStream(QObject *parent)
769
d = new Private(this);
771
connect(&d->sock, SIGNAL(connected()), SLOT(sock_connected()));
772
connect(&d->sock, SIGNAL(connectionClosed()), SLOT(sock_connectionClosed()));
773
connect(&d->sock, SIGNAL(readyRead()), SLOT(sock_readyRead()));
774
connect(&d->sock, SIGNAL(error(int)), SLOT(sock_error(int)));
778
HttpProxyGetStream::~HttpProxyGetStream()
784
void HttpProxyGetStream::reset(bool /*clear*/)
790
if(d->sock.state() != BSocket::Idle)
792
d->recvBuf.resize(0);
794
// d->body.resize(0);
798
void HttpProxyGetStream::setAuth(const QString &user, const QString &pass)
804
bool HttpProxyGetStream::isActive() const
806
return (d->sock.state() == BSocket::Idle ? false: true);
809
void HttpProxyGetStream::get(const QString &proxyHost, int proxyPort, const QString &url, bool ssl, bool asProxy)
816
d->asProxy = asProxy;
819
fprintf(stderr, "HttpProxyGetStream: Connecting to %s:%d", proxyHost.latin1(), proxyPort);
820
if(d->user.isEmpty())
821
fprintf(stderr, "\n");
823
fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1());
825
d->sock.connectToHost(proxyHost, proxyPort);
828
void HttpProxyGetStream::stop()
833
QString HttpProxyGetStream::getHeader(const QString &var) const
835
for(QStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it) {
836
const QString &s = *it;
837
int n = s.indexOf(": ");
840
QString v = s.mid(0, n);
841
if(v.toLower() == var.toLower())
847
int HttpProxyGetStream::length() const
852
void HttpProxyGetStream::sock_connected()
855
fprintf(stderr, "HttpProxyGetStream: Connected\n");
858
d->tls = new QCA::TLS(this);
859
connect(d->tls, SIGNAL(readyRead()), SLOT(tls_readyRead()));
860
connect(d->tls, SIGNAL(readyReadOutgoing()), SLOT(tls_readyReadOutgoing()));
861
connect(d->tls, SIGNAL(error()), SLOT(tls_error()));
862
d->tls->startClient();
866
d->headerLines.clear();
870
// connected, now send the request
872
s += QString("GET ") + d->url + " HTTP/1.0\r\n";
874
if(!d->user.isEmpty()) {
875
QString str = d->user + ':' + d->pass;
876
s += QString("Proxy-Authorization: Basic ") + QCA::Base64().encodeString(str) + "\r\n";
878
s += "Pragma: no-cache\r\n";
879
s += QString("Host: ") + u.host() + "\r\n";
882
s += QString("Host: ") + d->host + "\r\n";
888
d->tls->write(s.toUtf8());
890
d->sock.write(s.toUtf8());
893
void HttpProxyGetStream::sock_connectionClosed()
895
//d->body = d->recvBuf;
900
void HttpProxyGetStream::sock_readyRead()
902
QByteArray block = d->sock.read();
905
d->tls->writeIncoming(block);
910
void HttpProxyGetStream::processData(const QByteArray &block)
912
printf("processData: %d bytes\n", block.size());
914
emit dataReady(block);
918
ByteStream::appendArray(&d->recvBuf, block);
921
// grab available lines
924
QString line = extractLine(&d->recvBuf, &found);
928
printf("empty line\n");
932
d->headerLines += line;
933
printf("headerLine: [%s]\n", qPrintable(line));
936
// done with grabbing the header?
938
QString str = d->headerLines.first();
939
d->headerLines.takeFirst();
944
if(!extractMainHeader(str, &proto, &code, &msg)) {
946
fprintf(stderr, "HttpProxyGetStream: invalid header!\n");
954
fprintf(stderr, "HttpProxyGetStream: header proto=[%s] code=[%d] msg=[%s]\n", proto.latin1(), code, msg.latin1());
955
for(QStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it)
956
fprintf(stderr, "HttpProxyGetStream: * [%s]\n", (*it).latin1());
960
if(code == 200) { // OK
962
fprintf(stderr, "HttpProxyGetStream: << Success >>\n");
966
int x = getHeader("Content-Length").toInt(&ok);
970
QPointer<QObject> self = this;
978
if(code == 407) { // Authentication failed
980
errStr = tr("Authentication failed");
982
else if(code == 404) { // Host not found
983
err = ErrHostNotFound;
984
errStr = tr("Host not found");
986
else if(code == 403) { // Access denied
988
errStr = tr("Access denied");
990
else if(code == 503) { // Connection refused
991
err = ErrConnectionRefused;
992
errStr = tr("Connection refused");
994
else { // invalid reply
996
errStr = tr("Invalid reply");
1000
fprintf(stderr, "HttpProxyGetStream: << Error >> [%s]\n", errStr.latin1());
1007
if(!d->recvBuf.isEmpty()) {
1008
QByteArray a = d->recvBuf;
1016
void HttpProxyGetStream::sock_error(int x)
1019
fprintf(stderr, "HttpProxyGetStream: socket error: %d\n", x);
1022
if(x == BSocket::ErrHostNotFound)
1023
error(ErrProxyConnect);
1024
else if(x == BSocket::ErrConnectionRefused)
1025
error(ErrProxyConnect);
1026
else if(x == BSocket::ErrRead)
1030
void HttpProxyGetStream::tls_readyRead()
1032
//printf("tls_readyRead\n");
1033
processData(d->tls->read());
1036
void HttpProxyGetStream::tls_readyReadOutgoing()
1038
//printf("tls_readyReadOutgoing\n");
1039
d->sock.write(d->tls->readOutgoing());
1042
void HttpProxyGetStream::tls_error()
1045
fprintf(stderr, "HttpProxyGetStream: ssl error: %d\n", d->tls->errorCode());
1048
error(ErrConnectionRefused); // FIXME: bogus error