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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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
80
QString key[POLL_KEYS];
86
HttpPoll::HttpPoll(QObject *parent)
93
d->t->setSingleShot(true);
94
connect(d->t, SIGNAL(timeout()), SLOT(do_sync()));
96
connect(&d->http, SIGNAL(result()), SLOT(http_result()));
97
connect(&d->http, SIGNAL(error(int)), SLOT(http_error(int)));
102
HttpPoll::~HttpPoll()
109
void HttpPoll::reset(bool clear)
111
if(d->http.isActive())
122
void HttpPoll::setAuth(const QString &user, const QString &pass)
128
void HttpPoll::connectToUrl(const QString &url)
130
connectToHost("", 0, url);
133
void HttpPoll::connectToHost(const QString &proxyHost, int proxyPort, const QString &url)
138
if(!proxyHost.isEmpty()) {
151
d->url = u.path() + "?" + u.encodedQuery();
152
d->use_proxy = false;
157
QString key = getKey(&last);
160
fprintf(stderr, "HttpPoll: Connecting to %s:%d [%s]", d->host.latin1(), d->port, d->url.latin1());
161
if(d->user.isEmpty())
162
fprintf(stderr, "\n");
164
fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1());
166
QPointer<QObject> self = this;
172
d->http.setAuth(d->user, d->pass);
173
d->http.post(d->host, d->port, d->url, makePacket("0", key, "", QByteArray()), d->use_proxy);
176
QByteArray HttpPoll::makePacket(const QString &ident, const QString &key, const QString &newkey, const QByteArray &block)
183
if(!newkey.isEmpty()) {
188
QByteArray cs = str.toLatin1();
189
int len = cs.length();
192
a.resize(len + block.size());
193
memcpy(a.data(), cs.data(), len);
194
memcpy(a.data() + len, block.data(), block.size());
198
int HttpPoll::pollInterval() const
203
void HttpPoll::setPollInterval(int seconds)
205
d->polltime = seconds;
208
bool HttpPoll::isOpen() const
210
return (d->state == 2 ? true: false);
213
void HttpPoll::close()
215
if(d->state == 0 || d->closing)
218
if(bytesToWrite() == 0)
224
void HttpPoll::http_result()
226
// check for death :)
227
QPointer<QObject> self = this;
234
QString cookie = d->http.getHeader("Set-Cookie");
235
int n = cookie.indexOf("ID=");
242
int n2 = cookie.indexOf(';', n);
244
id = cookie.mid(n, n2-n);
247
QByteArray block = d->http.body();
250
if(id.right(2) == ":0") {
251
if(id == "0:0" && d->state == 2) {
264
bool justNowConnected = false;
267
justNowConnected = true;
270
// sync up again soon
271
if(bytesToWrite() > 0 || !d->closing) {
272
d->t->start(d->polltime * 1000);
276
if(justNowConnected) {
280
if(!d->out.isEmpty()) {
281
int x = d->out.size();
291
if(!block.isEmpty()) {
299
if(bytesToWrite() > 0) {
305
delayedCloseFinished();
311
void HttpPoll::http_error(int x)
314
if(x == HttpProxyPost::ErrConnectionRefused)
315
error(ErrConnectionRefused);
316
else if(x == HttpProxyPost::ErrHostNotFound)
317
error(ErrHostNotFound);
318
else if(x == HttpProxyPost::ErrSocket)
320
else if(x == HttpProxyPost::ErrProxyConnect)
321
error(ErrProxyConnect);
322
else if(x == HttpProxyPost::ErrProxyNeg)
324
else if(x == HttpProxyPost::ErrProxyAuth)
328
int HttpPoll::tryWrite()
330
if(!d->http.isActive())
335
void HttpPoll::do_sync()
337
if(d->http.isActive())
341
d->out = takeWrite(0, false);
344
QString key = getKey(&last);
348
newkey = getKey(&last);
351
QPointer<QObject> self = this;
356
d->http.post(d->host, d->port, d->url, makePacket(d->ident, key, newkey, d->out), d->use_proxy);
359
void HttpPoll::resetKey()
362
fprintf(stderr, "HttpPoll: reset key!\n");
364
QByteArray a = randomArray(64);
365
QString str = QString::fromLatin1(a.data(), a.size());
367
d->key_n = POLL_KEYS;
368
for(int n = 0; n < POLL_KEYS; ++n)
369
d->key[n] = hpk(n+1, str);
372
const QString & HttpPoll::getKey(bool *last)
378
return d->key[d->key_n];
382
//----------------------------------------------------------------------------
384
//----------------------------------------------------------------------------
385
static QString extractLine(QByteArray *buf, bool *found)
389
for(n = 0; n < (int)buf->size()-1; ++n) {
390
if(buf->at(n) == '\r' && buf->at(n+1) == '\n') {
393
memcpy(cstr.data(), buf->data(), n);
394
n += 2; // hack off CR/LF
396
memmove(buf->data(), buf->data() + n, buf->size() - n);
397
buf->resize(buf->size() - n);
398
QString s = QString::fromUtf8(cstr);
411
static bool extractMainHeader(const QString &line, QString *proto, int *code, QString *msg)
413
int n = line.indexOf(' ');
417
*proto = line.mid(0, n);
419
int n2 = line.indexOf(' ', n);
423
*code = line.mid(n, n2-n).toInt();
430
class HttpProxyPost::Private
436
QByteArray postdata, recvBuf, body;
440
QStringList headerLines;
445
HttpProxyPost::HttpProxyPost(QObject *parent)
449
connect(&d->sock, SIGNAL(connected()), SLOT(sock_connected()));
450
connect(&d->sock, SIGNAL(connectionClosed()), SLOT(sock_connectionClosed()));
451
connect(&d->sock, SIGNAL(readyRead()), SLOT(sock_readyRead()));
452
connect(&d->sock, SIGNAL(error(int)), SLOT(sock_error(int)));
456
HttpProxyPost::~HttpProxyPost()
462
void HttpProxyPost::reset(bool clear)
464
if(d->sock.state() != BSocket::Idle)
466
d->recvBuf.resize(0);
471
void HttpProxyPost::setAuth(const QString &user, const QString &pass)
477
bool HttpProxyPost::isActive() const
479
return (d->sock.state() == BSocket::Idle ? false: true);
482
void HttpProxyPost::post(const QString &proxyHost, int proxyPort, const QString &url, const QByteArray &data, bool asProxy)
489
d->asProxy = asProxy;
492
fprintf(stderr, "HttpProxyPost: Connecting to %s:%d", proxyHost.latin1(), proxyPort);
493
if(d->user.isEmpty())
494
fprintf(stderr, "\n");
496
fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1());
498
d->sock.connectToHost(proxyHost, proxyPort);
501
void HttpProxyPost::stop()
506
QByteArray HttpProxyPost::body() const
511
QString HttpProxyPost::getHeader(const QString &var) const
513
for(QStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it) {
514
const QString &s = *it;
515
int n = s.indexOf(": ");
518
QString v = s.mid(0, n);
519
if(v.toLower() == var.toLower())
525
void HttpProxyPost::sock_connected()
528
fprintf(stderr, "HttpProxyPost: Connected\n");
531
d->headerLines.clear();
535
// connected, now send the request
537
s += QString("POST ") + d->url + " HTTP/1.0\r\n";
539
if(!d->user.isEmpty()) {
540
QString str = d->user + ':' + d->pass;
541
s += QString("Proxy-Authorization: Basic ") + QCA::Base64().encodeString(str) + "\r\n";
543
s += "Pragma: no-cache\r\n";
544
s += QString("Host: ") + u.host() + "\r\n";
547
s += QString("Host: ") + d->host + "\r\n";
549
s += "Content-Type: application/x-www-form-urlencoded\r\n";
550
s += QString("Content-Length: ") + QString::number(d->postdata.size()) + "\r\n";
554
d->sock.write(s.toUtf8());
557
d->sock.write(d->postdata);
560
void HttpProxyPost::sock_connectionClosed()
562
d->body = d->recvBuf;
567
void HttpProxyPost::sock_readyRead()
569
QByteArray block = d->sock.read();
570
ByteStream::appendArray(&d->recvBuf, block);
573
// grab available lines
576
QString line = extractLine(&d->recvBuf, &found);
583
d->headerLines += line;
586
// done with grabbing the header?
588
QString str = d->headerLines.first();
589
d->headerLines.takeFirst();
594
if(!extractMainHeader(str, &proto, &code, &msg)) {
596
fprintf(stderr, "HttpProxyPost: invalid header!\n");
604
fprintf(stderr, "HttpProxyPost: header proto=[%s] code=[%d] msg=[%s]\n", proto.latin1(), code, msg.latin1());
605
for(QStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it)
606
fprintf(stderr, "HttpProxyPost: * [%s]\n", (*it).latin1());
610
if(code == 200) { // OK
612
fprintf(stderr, "HttpProxyPost: << Success >>\n");
618
if(code == 407) { // Authentication failed
620
errStr = tr("Authentication failed");
622
else if(code == 404) { // Host not found
623
err = ErrHostNotFound;
624
errStr = tr("Host not found");
626
else if(code == 403) { // Access denied
628
errStr = tr("Access denied");
630
else if(code == 503) { // Connection refused
631
err = ErrConnectionRefused;
632
errStr = tr("Connection refused");
634
else { // invalid reply
636
errStr = tr("Invalid reply");
640
fprintf(stderr, "HttpProxyPost: << Error >> [%s]\n", errStr.latin1());
650
void HttpProxyPost::sock_error(int x)
653
fprintf(stderr, "HttpProxyPost: socket error: %d\n", x);
656
if(x == BSocket::ErrHostNotFound)
657
error(ErrProxyConnect);
658
else if(x == BSocket::ErrConnectionRefused)
659
error(ErrProxyConnect);
660
else if(x == BSocket::ErrRead)
664
//----------------------------------------------------------------------------
665
// HttpProxyGetStream
666
//----------------------------------------------------------------------------
667
class HttpProxyGetStream::Private
677
QStringList headerLines;
686
HttpProxyGetStream::HttpProxyGetStream(QObject *parent)
691
connect(&d->sock, SIGNAL(connected()), SLOT(sock_connected()));
692
connect(&d->sock, SIGNAL(connectionClosed()), SLOT(sock_connectionClosed()));
693
connect(&d->sock, SIGNAL(readyRead()), SLOT(sock_readyRead()));
694
connect(&d->sock, SIGNAL(error(int)), SLOT(sock_error(int)));
698
HttpProxyGetStream::~HttpProxyGetStream()
704
void HttpProxyGetStream::reset(bool /*clear*/)
710
if(d->sock.state() != BSocket::Idle)
712
d->recvBuf.resize(0);
714
// d->body.resize(0);
718
void HttpProxyGetStream::setAuth(const QString &user, const QString &pass)
724
bool HttpProxyGetStream::isActive() const
726
return (d->sock.state() == BSocket::Idle ? false: true);
729
void HttpProxyGetStream::get(const QString &proxyHost, int proxyPort, const QString &url, bool ssl, bool asProxy)
736
d->asProxy = asProxy;
739
fprintf(stderr, "HttpProxyGetStream: Connecting to %s:%d", proxyHost.latin1(), proxyPort);
740
if(d->user.isEmpty())
741
fprintf(stderr, "\n");
743
fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1());
745
d->sock.connectToHost(proxyHost, proxyPort);
748
void HttpProxyGetStream::stop()
753
QString HttpProxyGetStream::getHeader(const QString &var) const
755
for(QStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it) {
756
const QString &s = *it;
757
int n = s.indexOf(": ");
760
QString v = s.mid(0, n);
761
if(v.toLower() == var.toLower())
767
int HttpProxyGetStream::length() const
772
void HttpProxyGetStream::sock_connected()
775
fprintf(stderr, "HttpProxyGetStream: Connected\n");
778
d->tls = new QCA::TLS;
779
connect(d->tls, SIGNAL(readyRead()), SLOT(tls_readyRead()));
780
connect(d->tls, SIGNAL(readyReadOutgoing()), SLOT(tls_readyReadOutgoing()));
781
connect(d->tls, SIGNAL(error()), SLOT(tls_error()));
782
d->tls->startClient();
786
d->headerLines.clear();
790
// connected, now send the request
792
s += QString("GET ") + d->url + " HTTP/1.0\r\n";
794
if(!d->user.isEmpty()) {
795
QString str = d->user + ':' + d->pass;
796
s += QString("Proxy-Authorization: Basic ") + QCA::Base64().encodeString(str) + "\r\n";
798
s += "Pragma: no-cache\r\n";
799
s += QString("Host: ") + u.host() + "\r\n";
802
s += QString("Host: ") + d->host + "\r\n";
808
d->tls->write(s.toUtf8());
810
d->sock.write(s.toUtf8());
813
void HttpProxyGetStream::sock_connectionClosed()
815
//d->body = d->recvBuf;
820
void HttpProxyGetStream::sock_readyRead()
822
QByteArray block = d->sock.read();
825
d->tls->writeIncoming(block);
830
void HttpProxyGetStream::processData(const QByteArray &block)
832
printf("processData: %d bytes\n", block.size());
834
emit dataReady(block);
838
ByteStream::appendArray(&d->recvBuf, block);
841
// grab available lines
844
QString line = extractLine(&d->recvBuf, &found);
848
printf("empty line\n");
852
d->headerLines += line;
853
printf("headerLine: [%s]\n", qPrintable(line));
856
// done with grabbing the header?
858
QString str = d->headerLines.first();
859
d->headerLines.takeFirst();
864
if(!extractMainHeader(str, &proto, &code, &msg)) {
866
fprintf(stderr, "HttpProxyGetStream: invalid header!\n");
874
fprintf(stderr, "HttpProxyGetStream: header proto=[%s] code=[%d] msg=[%s]\n", proto.latin1(), code, msg.latin1());
875
for(QStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it)
876
fprintf(stderr, "HttpProxyGetStream: * [%s]\n", (*it).latin1());
880
if(code == 200) { // OK
882
fprintf(stderr, "HttpProxyGetStream: << Success >>\n");
886
int x = getHeader("Content-Length").toInt(&ok);
890
QPointer<QObject> self = this;
898
if(code == 407) { // Authentication failed
900
errStr = tr("Authentication failed");
902
else if(code == 404) { // Host not found
903
err = ErrHostNotFound;
904
errStr = tr("Host not found");
906
else if(code == 403) { // Access denied
908
errStr = tr("Access denied");
910
else if(code == 503) { // Connection refused
911
err = ErrConnectionRefused;
912
errStr = tr("Connection refused");
914
else { // invalid reply
916
errStr = tr("Invalid reply");
920
fprintf(stderr, "HttpProxyGetStream: << Error >> [%s]\n", errStr.latin1());
927
if(!d->recvBuf.isEmpty()) {
928
QByteArray a = d->recvBuf;
936
void HttpProxyGetStream::sock_error(int x)
939
fprintf(stderr, "HttpProxyGetStream: socket error: %d\n", x);
942
if(x == BSocket::ErrHostNotFound)
943
error(ErrProxyConnect);
944
else if(x == BSocket::ErrConnectionRefused)
945
error(ErrProxyConnect);
946
else if(x == BSocket::ErrRead)
950
void HttpProxyGetStream::tls_readyRead()
952
//printf("tls_readyRead\n");
953
processData(d->tls->read());
956
void HttpProxyGetStream::tls_readyReadOutgoing()
958
//printf("tls_readyReadOutgoing\n");
959
d->sock.write(d->tls->readOutgoing());
962
void HttpProxyGetStream::tls_error()
965
fprintf(stderr, "HttpProxyGetStream: ssl error: %d\n", d->tls->errorCode());
968
error(ErrConnectionRefused); // FIXME: bogus error