You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
667 lines
13 KiB
667 lines
13 KiB
/*
|
|
* httppoll.cpp - HTTP polling proxy
|
|
* Copyright (C) 2003 Justin Karneges
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#include "httppoll.h"
|
|
|
|
#include <tqstringlist.h>
|
|
#include <tqurl.h>
|
|
#include <tqtimer.h>
|
|
#include <tqguardedptr.h>
|
|
#include <tqca.h>
|
|
#include <stdlib.h>
|
|
#include "bsocket.h"
|
|
#include "base64.h"
|
|
|
|
#ifdef PROX_DEBUG
|
|
#include <stdio.h>
|
|
#endif
|
|
|
|
#define POLL_KEYS 64
|
|
|
|
// CS_NAMESPACE_BEGIN
|
|
|
|
static TQByteArray randomArray(int size)
|
|
{
|
|
TQByteArray a(size);
|
|
for(int n = 0; n < size; ++n)
|
|
a[n] = (char)(256.0*rand()/(RAND_MAX+1.0));
|
|
return a;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// HttpPoll
|
|
//----------------------------------------------------------------------------
|
|
static TQString hpk(int n, const TQString &s)
|
|
{
|
|
if(n == 0)
|
|
return s;
|
|
else
|
|
return Base64::arrayToString( TQCA::SHA1::hash( TQCString(hpk(n - 1, s).latin1()) ) );
|
|
}
|
|
|
|
class HttpPoll::Private
|
|
{
|
|
public:
|
|
Private() {}
|
|
|
|
HttpProxyPost http;
|
|
TQString host;
|
|
int port;
|
|
TQString user, pass;
|
|
TQString url;
|
|
bool use_proxy;
|
|
|
|
TQByteArray out;
|
|
|
|
int state;
|
|
bool closing;
|
|
TQString ident;
|
|
|
|
TQTimer *t;
|
|
|
|
TQString key[POLL_KEYS];
|
|
int key_n;
|
|
|
|
int polltime;
|
|
};
|
|
|
|
HttpPoll::HttpPoll(TQObject *parent)
|
|
:ByteStream(parent)
|
|
{
|
|
d = new Private;
|
|
|
|
d->polltime = 30;
|
|
d->t = new TQTimer;
|
|
connect(d->t, TQT_SIGNAL(timeout()), TQT_SLOT(do_sync()));
|
|
|
|
connect(&d->http, TQT_SIGNAL(result()), TQT_SLOT(http_result()));
|
|
connect(&d->http, TQT_SIGNAL(error(int)), TQT_SLOT(http_error(int)));
|
|
|
|
reset(true);
|
|
}
|
|
|
|
HttpPoll::~HttpPoll()
|
|
{
|
|
reset(true);
|
|
delete d->t;
|
|
delete d;
|
|
}
|
|
|
|
void HttpPoll::reset(bool clear)
|
|
{
|
|
if(d->http.isActive())
|
|
d->http.stop();
|
|
if(clear)
|
|
clearReadBuffer();
|
|
clearWriteBuffer();
|
|
d->out.resize(0);
|
|
d->state = 0;
|
|
d->closing = false;
|
|
d->t->stop();
|
|
}
|
|
|
|
void HttpPoll::setAuth(const TQString &user, const TQString &pass)
|
|
{
|
|
d->user = user;
|
|
d->pass = pass;
|
|
}
|
|
|
|
void HttpPoll::connectToUrl(const TQString &url)
|
|
{
|
|
connectToHost("", 0, url);
|
|
}
|
|
|
|
void HttpPoll::connectToHost(const TQString &proxyHost, int proxyPort, const TQString &url)
|
|
{
|
|
reset(true);
|
|
|
|
// using proxy?
|
|
if(!proxyHost.isEmpty()) {
|
|
d->host = proxyHost;
|
|
d->port = proxyPort;
|
|
d->url = url;
|
|
d->use_proxy = true;
|
|
}
|
|
else {
|
|
TQUrl u = url;
|
|
d->host = u.host();
|
|
if(u.hasPort())
|
|
d->port = u.port();
|
|
else
|
|
d->port = 80;
|
|
d->url = u.encodedPathAndQuery();
|
|
d->use_proxy = false;
|
|
}
|
|
|
|
resetKey();
|
|
bool last;
|
|
TQString key = getKey(&last);
|
|
|
|
#ifdef PROX_DEBUG
|
|
fprintf(stderr, "HttpPoll: Connecting to %s:%d [%s]", d->host.latin1(), d->port, d->url.latin1());
|
|
if(d->user.isEmpty())
|
|
fprintf(stderr, "\n");
|
|
else
|
|
fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1());
|
|
#endif
|
|
TQGuardedPtr<TQObject> self = this;
|
|
syncStarted();
|
|
if(!self)
|
|
return;
|
|
|
|
d->state = 1;
|
|
d->http.setAuth(d->user, d->pass);
|
|
d->http.post(d->host, d->port, d->url, makePacket("0", key, "", TQByteArray()), d->use_proxy);
|
|
}
|
|
|
|
TQByteArray HttpPoll::makePacket(const TQString &ident, const TQString &key, const TQString &newkey, const TQByteArray &block)
|
|
{
|
|
TQString str = ident;
|
|
if(!key.isEmpty()) {
|
|
str += ';';
|
|
str += key;
|
|
}
|
|
if(!newkey.isEmpty()) {
|
|
str += ';';
|
|
str += newkey;
|
|
}
|
|
str += ',';
|
|
TQCString cs = str.latin1();
|
|
int len = cs.length();
|
|
|
|
TQByteArray a(len + block.size());
|
|
memcpy(a.data(), cs.data(), len);
|
|
memcpy(a.data() + len, block.data(), block.size());
|
|
return a;
|
|
}
|
|
|
|
int HttpPoll::pollInterval() const
|
|
{
|
|
return d->polltime;
|
|
}
|
|
|
|
void HttpPoll::setPollInterval(int seconds)
|
|
{
|
|
d->polltime = seconds;
|
|
}
|
|
|
|
bool HttpPoll::isOpen() const
|
|
{
|
|
return (d->state == 2 ? true: false);
|
|
}
|
|
|
|
void HttpPoll::close()
|
|
{
|
|
if(d->state == 0 || d->closing)
|
|
return;
|
|
|
|
if(bytesToWrite() == 0)
|
|
reset();
|
|
else
|
|
d->closing = true;
|
|
}
|
|
|
|
void HttpPoll::http_result()
|
|
{
|
|
// check for death :)
|
|
TQGuardedPtr<TQObject> self = this;
|
|
syncFinished();
|
|
if(!self)
|
|
return;
|
|
|
|
// get id and packet
|
|
TQString id;
|
|
TQString cookie = d->http.getHeader("Set-Cookie");
|
|
int n = cookie.find("ID=");
|
|
if(n == -1) {
|
|
reset();
|
|
error(ErrRead);
|
|
return;
|
|
}
|
|
n += 3;
|
|
int n2 = cookie.find(';', n);
|
|
if(n2 != -1)
|
|
id = cookie.mid(n, n2-n);
|
|
else
|
|
id = cookie.mid(n);
|
|
TQByteArray block = d->http.body();
|
|
|
|
// session error?
|
|
if(id.right(2) == ":0") {
|
|
if(id == "0:0" && d->state == 2) {
|
|
reset();
|
|
connectionClosed();
|
|
return;
|
|
}
|
|
else {
|
|
reset();
|
|
error(ErrRead);
|
|
return;
|
|
}
|
|
}
|
|
|
|
d->ident = id;
|
|
bool justNowConnected = false;
|
|
if(d->state == 1) {
|
|
d->state = 2;
|
|
justNowConnected = true;
|
|
}
|
|
|
|
// sync up again soon
|
|
if(bytesToWrite() > 0 || !d->closing)
|
|
d->t->start(d->polltime * 1000, true);
|
|
|
|
// connecting
|
|
if(justNowConnected) {
|
|
connected();
|
|
}
|
|
else {
|
|
if(!d->out.isEmpty()) {
|
|
int x = d->out.size();
|
|
d->out.resize(0);
|
|
takeWrite(x);
|
|
bytesWritten(x);
|
|
}
|
|
}
|
|
|
|
if(!self)
|
|
return;
|
|
|
|
if(!block.isEmpty()) {
|
|
appendRead(block);
|
|
readyRead();
|
|
}
|
|
|
|
if(!self)
|
|
return;
|
|
|
|
if(bytesToWrite() > 0) {
|
|
do_sync();
|
|
}
|
|
else {
|
|
if(d->closing) {
|
|
reset();
|
|
delayedCloseFinished();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void HttpPoll::http_error(int x)
|
|
{
|
|
reset();
|
|
if(x == HttpProxyPost::ErrConnectionRefused)
|
|
error(ErrConnectionRefused);
|
|
else if(x == HttpProxyPost::ErrHostNotFound)
|
|
error(ErrHostNotFound);
|
|
else if(x == HttpProxyPost::ErrSocket)
|
|
error(ErrRead);
|
|
else if(x == HttpProxyPost::ErrProxyConnect)
|
|
error(ErrProxyConnect);
|
|
else if(x == HttpProxyPost::ErrProxyNeg)
|
|
error(ErrProxyNeg);
|
|
else if(x == HttpProxyPost::ErrProxyAuth)
|
|
error(ErrProxyAuth);
|
|
}
|
|
|
|
int HttpPoll::tryWrite()
|
|
{
|
|
if(!d->http.isActive())
|
|
do_sync();
|
|
return 0;
|
|
}
|
|
|
|
void HttpPoll::do_sync()
|
|
{
|
|
if(d->http.isActive())
|
|
return;
|
|
|
|
d->t->stop();
|
|
d->out = takeWrite(0, false);
|
|
|
|
bool last;
|
|
TQString key = getKey(&last);
|
|
TQString newkey;
|
|
if(last) {
|
|
resetKey();
|
|
newkey = getKey(&last);
|
|
}
|
|
|
|
TQGuardedPtr<TQObject> self = this;
|
|
syncStarted();
|
|
if(!self)
|
|
return;
|
|
|
|
d->http.post(d->host, d->port, d->url, makePacket(d->ident, key, newkey, d->out), d->use_proxy);
|
|
}
|
|
|
|
void HttpPoll::resetKey()
|
|
{
|
|
#ifdef PROX_DEBUG
|
|
fprintf(stderr, "HttpPoll: reset key!\n");
|
|
#endif
|
|
TQByteArray a = randomArray(64);
|
|
TQString str = TQString::fromLatin1(a.data(), a.size());
|
|
|
|
d->key_n = POLL_KEYS;
|
|
for(int n = 0; n < POLL_KEYS; ++n)
|
|
d->key[n] = hpk(n+1, str);
|
|
}
|
|
|
|
const TQString & HttpPoll::getKey(bool *last)
|
|
{
|
|
*last = false;
|
|
--(d->key_n);
|
|
if(d->key_n == 0)
|
|
*last = true;
|
|
return d->key[d->key_n];
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
// HttpProxyPost
|
|
//----------------------------------------------------------------------------
|
|
static TQString extractLine(TQByteArray *buf, bool *found)
|
|
{
|
|
// scan for newline
|
|
int n;
|
|
for(n = 0; n < (int)buf->size()-1; ++n) {
|
|
if(buf->at(n) == '\r' && buf->at(n+1) == '\n') {
|
|
TQCString cstr;
|
|
cstr.resize(n+1);
|
|
memcpy(cstr.data(), buf->data(), n);
|
|
n += 2; // hack off CR/LF
|
|
|
|
memmove(buf->data(), buf->data() + n, buf->size() - n);
|
|
buf->resize(buf->size() - n);
|
|
TQString s = TQString::fromUtf8(cstr);
|
|
|
|
if(found)
|
|
*found = true;
|
|
return s;
|
|
}
|
|
}
|
|
|
|
if(found)
|
|
*found = false;
|
|
return "";
|
|
}
|
|
|
|
static bool extractMainHeader(const TQString &line, TQString *proto, int *code, TQString *msg)
|
|
{
|
|
int n = line.find(' ');
|
|
if(n == -1)
|
|
return false;
|
|
if(proto)
|
|
*proto = line.mid(0, n);
|
|
++n;
|
|
int n2 = line.find(' ', n);
|
|
if(n2 == -1)
|
|
return false;
|
|
if(code)
|
|
*code = line.mid(n, n2-n).toInt();
|
|
n = n2+1;
|
|
if(msg)
|
|
*msg = line.mid(n);
|
|
return true;
|
|
}
|
|
|
|
class HttpProxyPost::Private
|
|
{
|
|
public:
|
|
Private() {}
|
|
|
|
BSocket sock;
|
|
TQByteArray postdata, recvBuf, body;
|
|
TQString url;
|
|
TQString user, pass;
|
|
bool inHeader;
|
|
TQStringList headerLines;
|
|
bool asProxy;
|
|
TQString host;
|
|
};
|
|
|
|
HttpProxyPost::HttpProxyPost(TQObject *parent)
|
|
:TQObject(parent)
|
|
{
|
|
d = new Private;
|
|
connect(&d->sock, TQT_SIGNAL(connected()), TQT_SLOT(sock_connected()));
|
|
connect(&d->sock, TQT_SIGNAL(connectionClosed()), TQT_SLOT(sock_connectionClosed()));
|
|
connect(&d->sock, TQT_SIGNAL(readyRead()), TQT_SLOT(sock_readyRead()));
|
|
connect(&d->sock, TQT_SIGNAL(error(int)), TQT_SLOT(sock_error(int)));
|
|
reset(true);
|
|
}
|
|
|
|
HttpProxyPost::~HttpProxyPost()
|
|
{
|
|
reset(true);
|
|
delete d;
|
|
}
|
|
|
|
void HttpProxyPost::reset(bool clear)
|
|
{
|
|
if(d->sock.state() != BSocket::Idle)
|
|
d->sock.close();
|
|
d->recvBuf.resize(0);
|
|
if(clear)
|
|
d->body.resize(0);
|
|
}
|
|
|
|
void HttpProxyPost::setAuth(const TQString &user, const TQString &pass)
|
|
{
|
|
d->user = user;
|
|
d->pass = pass;
|
|
}
|
|
|
|
bool HttpProxyPost::isActive() const
|
|
{
|
|
return (d->sock.state() == BSocket::Idle ? false: true);
|
|
}
|
|
|
|
void HttpProxyPost::post(const TQString &proxyHost, int proxyPort, const TQString &url, const TQByteArray &data, bool asProxy)
|
|
{
|
|
reset(true);
|
|
|
|
d->host = proxyHost;
|
|
d->url = url;
|
|
d->postdata = data;
|
|
d->asProxy = asProxy;
|
|
|
|
#ifdef PROX_DEBUG
|
|
fprintf(stderr, "HttpProxyPost: Connecting to %s:%d", proxyHost.latin1(), proxyPort);
|
|
if(d->user.isEmpty())
|
|
fprintf(stderr, "\n");
|
|
else
|
|
fprintf(stderr, ", auth {%s,%s}\n", d->user.latin1(), d->pass.latin1());
|
|
#endif
|
|
d->sock.connectToHost(proxyHost, proxyPort);
|
|
}
|
|
|
|
void HttpProxyPost::stop()
|
|
{
|
|
reset();
|
|
}
|
|
|
|
TQByteArray HttpProxyPost::body() const
|
|
{
|
|
return d->body;
|
|
}
|
|
|
|
TQString HttpProxyPost::getHeader(const TQString &var) const
|
|
{
|
|
for(TQStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it) {
|
|
const TQString &s = *it;
|
|
int n = s.find(": ");
|
|
if(n == -1)
|
|
continue;
|
|
TQString v = s.mid(0, n);
|
|
if(v == var)
|
|
return s.mid(n+2);
|
|
}
|
|
return "";
|
|
}
|
|
|
|
void HttpProxyPost::sock_connected()
|
|
{
|
|
#ifdef PROX_DEBUG
|
|
fprintf(stderr, "HttpProxyPost: Connected\n");
|
|
#endif
|
|
d->inHeader = true;
|
|
d->headerLines.clear();
|
|
|
|
TQUrl u = d->url;
|
|
|
|
// connected, now send the request
|
|
TQString s;
|
|
s += TQString("POST ") + d->url + " HTTP/1.0\r\n";
|
|
if(d->asProxy) {
|
|
if(!d->user.isEmpty()) {
|
|
TQString str = d->user + ':' + d->pass;
|
|
s += TQString("Proxy-Authorization: Basic ") + Base64::encodeString(str) + "\r\n";
|
|
}
|
|
s += "Proxy-Connection: Keep-Alive\r\n";
|
|
s += "Pragma: no-cache\r\n";
|
|
s += TQString("Host: ") + u.host() + "\r\n";
|
|
}
|
|
else {
|
|
s += TQString("Host: ") + d->host + "\r\n";
|
|
}
|
|
s += "Content-Type: application/x-www-form-urlencoded\r\n";
|
|
s += TQString("Content-Length: ") + TQString::number(d->postdata.size()) + "\r\n";
|
|
s += "\r\n";
|
|
|
|
// write request
|
|
TQCString cs = s.utf8();
|
|
TQByteArray block(cs.length());
|
|
memcpy(block.data(), cs.data(), block.size());
|
|
d->sock.write(block);
|
|
|
|
// write postdata
|
|
d->sock.write(d->postdata);
|
|
}
|
|
|
|
void HttpProxyPost::sock_connectionClosed()
|
|
{
|
|
d->body = d->recvBuf.copy();
|
|
reset();
|
|
result();
|
|
}
|
|
|
|
void HttpProxyPost::sock_readyRead()
|
|
{
|
|
TQByteArray block = d->sock.read();
|
|
ByteStream::appendArray(&d->recvBuf, block);
|
|
|
|
if(d->inHeader) {
|
|
// grab available lines
|
|
while(1) {
|
|
bool found;
|
|
TQString line = extractLine(&d->recvBuf, &found);
|
|
if(!found)
|
|
break;
|
|
if(line.isEmpty()) {
|
|
d->inHeader = false;
|
|
break;
|
|
}
|
|
d->headerLines += line;
|
|
}
|
|
|
|
// done with grabbing the header?
|
|
if(!d->inHeader) {
|
|
TQString str = d->headerLines.first();
|
|
d->headerLines.remove(d->headerLines.begin());
|
|
|
|
TQString proto;
|
|
int code;
|
|
TQString msg;
|
|
if(!extractMainHeader(str, &proto, &code, &msg)) {
|
|
#ifdef PROX_DEBUG
|
|
fprintf(stderr, "HttpProxyPost: invalid header!\n");
|
|
#endif
|
|
reset(true);
|
|
error(ErrProxyNeg);
|
|
return;
|
|
}
|
|
else {
|
|
#ifdef PROX_DEBUG
|
|
fprintf(stderr, "HttpProxyPost: header proto=[%s] code=[%d] msg=[%s]\n", proto.latin1(), code, msg.latin1());
|
|
for(TQStringList::ConstIterator it = d->headerLines.begin(); it != d->headerLines.end(); ++it)
|
|
fprintf(stderr, "HttpProxyPost: * [%s]\n", (*it).latin1());
|
|
#endif
|
|
}
|
|
|
|
if(code == 200) { // OK
|
|
#ifdef PROX_DEBUG
|
|
fprintf(stderr, "HttpProxyPost: << Success >>\n");
|
|
#endif
|
|
}
|
|
else {
|
|
int err;
|
|
TQString errStr;
|
|
if(code == 407) { // Authentication failed
|
|
err = ErrProxyAuth;
|
|
errStr = tr("Authentication failed");
|
|
}
|
|
else if(code == 404) { // Host not found
|
|
err = ErrHostNotFound;
|
|
errStr = tr("Host not found");
|
|
}
|
|
else if(code == 403) { // Access denied
|
|
err = ErrProxyNeg;
|
|
errStr = tr("Access denied");
|
|
}
|
|
else if(code == 503) { // Connection refused
|
|
err = ErrConnectionRefused;
|
|
errStr = tr("Connection refused");
|
|
}
|
|
else { // invalid reply
|
|
err = ErrProxyNeg;
|
|
errStr = tr("Invalid reply");
|
|
}
|
|
|
|
#ifdef PROX_DEBUG
|
|
fprintf(stderr, "HttpProxyPost: << Error >> [%s]\n", errStr.latin1());
|
|
#endif
|
|
reset(true);
|
|
error(err);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void HttpProxyPost::sock_error(int x)
|
|
{
|
|
#ifdef PROX_DEBUG
|
|
fprintf(stderr, "HttpProxyPost: socket error: %d\n", x);
|
|
#endif
|
|
reset(true);
|
|
if(x == BSocket::ErrHostNotFound)
|
|
error(ErrProxyConnect);
|
|
else if(x == BSocket::ErrConnectionRefused)
|
|
error(ErrProxyConnect);
|
|
else if(x == BSocket::ErrRead)
|
|
error(ErrProxyNeg);
|
|
}
|
|
|
|
// CS_NAMESPACE_END
|
|
|
|
#include "httppoll.moc"
|