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.
639 lines
13 KiB
639 lines
13 KiB
/*
|
|
* ibb.cpp - Inband bytestream
|
|
* Copyright (C) 2001, 2002 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 "xmpp_ibb.h"
|
|
|
|
#include <tqtimer.h>
|
|
#include "xmpp_xmlcommon.h"
|
|
#include "base64.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
#define IBB_PACKET_SIZE 4096
|
|
#define IBB_PACKET_DELAY 0
|
|
|
|
using namespace XMPP;
|
|
|
|
static int num_conn = 0;
|
|
static int id_conn = 0;
|
|
|
|
//----------------------------------------------------------------------------
|
|
// IBBConnection
|
|
//----------------------------------------------------------------------------
|
|
class IBBConnection::Private
|
|
{
|
|
public:
|
|
Private() {}
|
|
|
|
int state;
|
|
Jid peer;
|
|
TQString sid;
|
|
IBBManager *m;
|
|
JT_IBB *j;
|
|
TQDomElement comment;
|
|
TQString iq_id;
|
|
|
|
int blockSize;
|
|
TQByteArray recvbuf, sendbuf;
|
|
bool closePending, closing;
|
|
|
|
int id;
|
|
};
|
|
|
|
IBBConnection::IBBConnection(IBBManager *m)
|
|
:ByteStream(m)
|
|
{
|
|
d = new Private;
|
|
d->m = m;
|
|
d->j = 0;
|
|
reset();
|
|
|
|
++num_conn;
|
|
d->id = id_conn++;
|
|
TQString dstr; dstr.sprintf("IBBConnection[%d]: constructing, count=%d\n", d->id, num_conn);
|
|
d->m->client()->debug(dstr);
|
|
}
|
|
|
|
void IBBConnection::reset(bool clear)
|
|
{
|
|
d->m->unlink(this);
|
|
d->state = Idle;
|
|
d->closePending = false;
|
|
d->closing = false;
|
|
|
|
delete d->j;
|
|
d->j = 0;
|
|
|
|
d->sendbuf.resize(0);
|
|
if(clear)
|
|
d->recvbuf.resize(0);
|
|
}
|
|
|
|
IBBConnection::~IBBConnection()
|
|
{
|
|
reset(true);
|
|
|
|
--num_conn;
|
|
TQString dstr; dstr.sprintf("IBBConnection[%d]: destructing, count=%d\n", d->id, num_conn);
|
|
d->m->client()->debug(dstr);
|
|
|
|
delete d;
|
|
}
|
|
|
|
void IBBConnection::connectToJid(const Jid &peer, const TQDomElement &comment)
|
|
{
|
|
close();
|
|
reset(true);
|
|
|
|
d->state = Requesting;
|
|
d->peer = peer;
|
|
d->comment = comment;
|
|
|
|
TQString dstr; dstr.sprintf("IBBConnection[%d]: initiating request to %s\n", d->id, peer.full().latin1());
|
|
d->m->client()->debug(dstr);
|
|
|
|
d->j = new JT_IBB(d->m->client()->rootTask());
|
|
connect(d->j, TQT_SIGNAL(finished()), TQT_SLOT(ibb_finished()));
|
|
d->j->request(d->peer, comment);
|
|
d->j->go(true);
|
|
}
|
|
|
|
void IBBConnection::accept()
|
|
{
|
|
if(d->state != WaitingForAccept)
|
|
return;
|
|
|
|
TQString dstr; dstr.sprintf("IBBConnection[%d]: accepting %s [%s]\n", d->id, d->peer.full().latin1(), d->sid.latin1());
|
|
d->m->client()->debug(dstr);
|
|
|
|
d->m->doAccept(this, d->iq_id);
|
|
d->state = Active;
|
|
d->m->link(this);
|
|
}
|
|
|
|
void IBBConnection::close()
|
|
{
|
|
if(d->state == Idle)
|
|
return;
|
|
|
|
if(d->state == WaitingForAccept) {
|
|
d->m->doReject(this, d->iq_id, 403, "Rejected");
|
|
reset();
|
|
return;
|
|
}
|
|
|
|
TQString dstr; dstr.sprintf("IBBConnection[%d]: closing\n", d->id);
|
|
d->m->client()->debug(dstr);
|
|
|
|
if(d->state == Active) {
|
|
// if there is data pending to be written, then pend the closing
|
|
if(bytesToWrite() > 0) {
|
|
d->closePending = true;
|
|
trySend();
|
|
return;
|
|
}
|
|
|
|
// send a close packet
|
|
JT_IBB *j = new JT_IBB(d->m->client()->rootTask());
|
|
j->sendData(d->peer, d->sid, TQByteArray(), true);
|
|
j->go(true);
|
|
}
|
|
|
|
reset();
|
|
}
|
|
|
|
int IBBConnection::state() const
|
|
{
|
|
return d->state;
|
|
}
|
|
|
|
Jid IBBConnection::peer() const
|
|
{
|
|
return d->peer;
|
|
}
|
|
|
|
TQString IBBConnection::streamid() const
|
|
{
|
|
return d->sid;
|
|
}
|
|
|
|
TQDomElement IBBConnection::comment() const
|
|
{
|
|
return d->comment;
|
|
}
|
|
|
|
bool IBBConnection::isOpen() const
|
|
{
|
|
if(d->state == Active)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void IBBConnection::write(const TQByteArray &a)
|
|
{
|
|
if(d->state != Active || d->closePending || d->closing)
|
|
return;
|
|
|
|
// append to the end of our send buffer
|
|
int oldsize = d->sendbuf.size();
|
|
d->sendbuf.resize(oldsize + a.size());
|
|
memcpy(d->sendbuf.data() + oldsize, a.data(), a.size());
|
|
|
|
trySend();
|
|
}
|
|
|
|
TQByteArray IBBConnection::read(int)
|
|
{
|
|
// TODO: obey argument
|
|
TQByteArray a = d->recvbuf.copy();
|
|
d->recvbuf.resize(0);
|
|
return a;
|
|
}
|
|
|
|
int IBBConnection::bytesAvailable() const
|
|
{
|
|
return d->recvbuf.size();
|
|
}
|
|
|
|
int IBBConnection::bytesToWrite() const
|
|
{
|
|
return d->sendbuf.size();
|
|
}
|
|
|
|
void IBBConnection::waitForAccept(const Jid &peer, const TQString &sid, const TQDomElement &comment, const TQString &iq_id)
|
|
{
|
|
close();
|
|
reset(true);
|
|
|
|
d->state = WaitingForAccept;
|
|
d->peer = peer;
|
|
d->sid = sid;
|
|
d->comment = comment;
|
|
d->iq_id = iq_id;
|
|
}
|
|
|
|
void IBBConnection::takeIncomingData(const TQByteArray &a, bool close)
|
|
{
|
|
// append to the end of our recv buffer
|
|
int oldsize = d->recvbuf.size();
|
|
d->recvbuf.resize(oldsize + a.size());
|
|
memcpy(d->recvbuf.data() + oldsize, a.data(), a.size());
|
|
|
|
readyRead();
|
|
|
|
if(close) {
|
|
reset();
|
|
connectionClosed();
|
|
}
|
|
}
|
|
|
|
void IBBConnection::ibb_finished()
|
|
{
|
|
JT_IBB *j = d->j;
|
|
d->j = 0;
|
|
|
|
if(j->success()) {
|
|
if(j->mode() == JT_IBB::ModeRequest) {
|
|
d->sid = j->streamid();
|
|
|
|
TQString dstr; dstr.sprintf("IBBConnection[%d]: %s [%s] accepted.\n", d->id, d->peer.full().latin1(), d->sid.latin1());
|
|
d->m->client()->debug(dstr);
|
|
|
|
d->state = Active;
|
|
d->m->link(this);
|
|
connected();
|
|
}
|
|
else {
|
|
bytesWritten(d->blockSize);
|
|
|
|
if(d->closing) {
|
|
reset();
|
|
delayedCloseFinished();
|
|
}
|
|
|
|
if(!d->sendbuf.isEmpty() || d->closePending)
|
|
TQTimer::singleShot(IBB_PACKET_DELAY, this, TQT_SLOT(trySend()));
|
|
}
|
|
}
|
|
else {
|
|
if(j->mode() == JT_IBB::ModeRequest) {
|
|
TQString dstr; dstr.sprintf("IBBConnection[%d]: %s refused.\n", d->id, d->peer.full().latin1());
|
|
d->m->client()->debug(dstr);
|
|
|
|
reset(true);
|
|
error(ErrRequest);
|
|
}
|
|
else {
|
|
reset(true);
|
|
error(ErrData);
|
|
}
|
|
}
|
|
}
|
|
|
|
void IBBConnection::trySend()
|
|
{
|
|
// if we already have an active task, then don't do anything
|
|
if(d->j)
|
|
return;
|
|
|
|
TQByteArray a;
|
|
if(!d->sendbuf.isEmpty()) {
|
|
// take a chunk
|
|
if(d->sendbuf.size() < IBB_PACKET_SIZE)
|
|
a.resize(d->sendbuf.size());
|
|
else
|
|
a.resize(IBB_PACKET_SIZE);
|
|
memcpy(a.data(), d->sendbuf.data(), a.size());
|
|
d->sendbuf.resize(d->sendbuf.size() - a.size());
|
|
}
|
|
|
|
bool doClose = false;
|
|
if(d->sendbuf.isEmpty() && d->closePending)
|
|
doClose = true;
|
|
|
|
// null operation?
|
|
if(a.isEmpty() && !doClose)
|
|
return;
|
|
|
|
printf("IBBConnection[%d]: sending [%d] bytes ", d->id, a.size());
|
|
if(doClose)
|
|
printf("and closing.\n");
|
|
else
|
|
printf("(%d bytes left)\n", d->sendbuf.size());
|
|
|
|
if(doClose) {
|
|
d->closePending = false;
|
|
d->closing = true;
|
|
}
|
|
|
|
d->blockSize = a.size();
|
|
d->j = new JT_IBB(d->m->client()->rootTask());
|
|
connect(d->j, TQT_SIGNAL(finished()), TQT_SLOT(ibb_finished()));
|
|
d->j->sendData(d->peer, d->sid, a, doClose);
|
|
d->j->go(true);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
// IBBManager
|
|
//----------------------------------------------------------------------------
|
|
class IBBManager::Private
|
|
{
|
|
public:
|
|
Private() {}
|
|
|
|
Client *client;
|
|
IBBConnectionList activeConns;
|
|
IBBConnectionList incomingConns;
|
|
JT_IBB *ibb;
|
|
};
|
|
|
|
IBBManager::IBBManager(Client *parent)
|
|
:TQObject(parent)
|
|
{
|
|
d = new Private;
|
|
d->client = parent;
|
|
|
|
d->ibb = new JT_IBB(d->client->rootTask(), true);
|
|
connect(d->ibb, TQT_SIGNAL(incomingRequest(const Jid &, const TQString &, const TQDomElement &)), TQT_SLOT(ibb_incomingRequest(const Jid &, const TQString &, const TQDomElement &)));
|
|
connect(d->ibb, TQT_SIGNAL(incomingData(const Jid &, const TQString &, const TQString &, const TQByteArray &, bool)), TQT_SLOT(ibb_incomingData(const Jid &, const TQString &, const TQString &, const TQByteArray &, bool)));
|
|
}
|
|
|
|
IBBManager::~IBBManager()
|
|
{
|
|
d->incomingConns.setAutoDelete(true);
|
|
d->incomingConns.clear();
|
|
delete d->ibb;
|
|
delete d;
|
|
}
|
|
|
|
Client *IBBManager::client() const
|
|
{
|
|
return d->client;
|
|
}
|
|
|
|
IBBConnection *IBBManager::takeIncoming()
|
|
{
|
|
if(d->incomingConns.isEmpty())
|
|
return 0;
|
|
|
|
IBBConnection *c = d->incomingConns.getFirst();
|
|
d->incomingConns.removeRef(c);
|
|
return c;
|
|
}
|
|
|
|
void IBBManager::ibb_incomingRequest(const Jid &from, const TQString &id, const TQDomElement &comment)
|
|
{
|
|
TQString sid = genUniqueKey();
|
|
|
|
// create a "waiting" connection
|
|
IBBConnection *c = new IBBConnection(this);
|
|
c->waitForAccept(from, sid, comment, id);
|
|
d->incomingConns.append(c);
|
|
incomingReady();
|
|
}
|
|
|
|
void IBBManager::ibb_incomingData(const Jid &from, const TQString &streamid, const TQString &id, const TQByteArray &data, bool close)
|
|
{
|
|
IBBConnection *c = findConnection(streamid, from);
|
|
if(!c) {
|
|
d->ibb->respondError(from, id, 404, "No such stream");
|
|
}
|
|
else {
|
|
d->ibb->respondAck(from, id);
|
|
c->takeIncomingData(data, close);
|
|
}
|
|
}
|
|
|
|
TQString IBBManager::genKey() const
|
|
{
|
|
TQString key = "ibb_";
|
|
|
|
for(int i = 0; i < 4; ++i) {
|
|
int word = rand() & 0xffff;
|
|
for(int n = 0; n < 4; ++n) {
|
|
TQString s;
|
|
s.sprintf("%x", (word >> (n * 4)) & 0xf);
|
|
key.append(s);
|
|
}
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
TQString IBBManager::genUniqueKey() const
|
|
{
|
|
// get unused key
|
|
TQString key;
|
|
while(1) {
|
|
key = genKey();
|
|
|
|
if(!findConnection(key))
|
|
break;
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
void IBBManager::link(IBBConnection *c)
|
|
{
|
|
d->activeConns.append(c);
|
|
}
|
|
|
|
void IBBManager::unlink(IBBConnection *c)
|
|
{
|
|
d->activeConns.removeRef(c);
|
|
}
|
|
|
|
IBBConnection *IBBManager::findConnection(const TQString &sid, const Jid &peer) const
|
|
{
|
|
IBBConnectionListIt it(d->activeConns);
|
|
for(IBBConnection *c; (c = it.current()); ++it) {
|
|
if(c->streamid() == sid && (peer.isEmpty() || c->peer().compare(peer)) )
|
|
return c;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void IBBManager::doAccept(IBBConnection *c, const TQString &id)
|
|
{
|
|
d->ibb->respondSuccess(c->peer(), id, c->streamid());
|
|
}
|
|
|
|
void IBBManager::doReject(IBBConnection *c, const TQString &id, int code, const TQString &str)
|
|
{
|
|
d->ibb->respondError(c->peer(), id, code, str);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
// JT_IBB
|
|
//----------------------------------------------------------------------------
|
|
class JT_IBB::Private
|
|
{
|
|
public:
|
|
Private() {}
|
|
|
|
TQDomElement iq;
|
|
int mode;
|
|
bool serve;
|
|
Jid to;
|
|
TQString streamid;
|
|
};
|
|
|
|
JT_IBB::JT_IBB(Task *parent, bool serve)
|
|
:Task(parent)
|
|
{
|
|
d = new Private;
|
|
d->serve = serve;
|
|
}
|
|
|
|
JT_IBB::~JT_IBB()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
void JT_IBB::request(const Jid &to, const TQDomElement &comment)
|
|
{
|
|
d->mode = ModeRequest;
|
|
TQDomElement iq;
|
|
d->to = to;
|
|
iq = createIQ(doc(), "set", to.full(), id());
|
|
TQDomElement query = doc()->createElement("query");
|
|
query.setAttribute("xmlns", "http://jabber.org/protocol/ibb");
|
|
iq.appendChild(query);
|
|
query.appendChild(comment);
|
|
d->iq = iq;
|
|
}
|
|
|
|
void JT_IBB::sendData(const Jid &to, const TQString &streamid, const TQByteArray &a, bool close)
|
|
{
|
|
d->mode = ModeSendData;
|
|
TQDomElement iq;
|
|
d->to = to;
|
|
iq = createIQ(doc(), "set", to.full(), id());
|
|
TQDomElement query = doc()->createElement("query");
|
|
query.setAttribute("xmlns", "http://jabber.org/protocol/ibb");
|
|
iq.appendChild(query);
|
|
query.appendChild(textTag(doc(), "streamid", streamid));
|
|
if(!a.isEmpty())
|
|
query.appendChild(textTag(doc(), "data", Base64::arrayToString(a)));
|
|
if(close) {
|
|
TQDomElement c = doc()->createElement("close");
|
|
query.appendChild(c);
|
|
}
|
|
d->iq = iq;
|
|
}
|
|
|
|
void JT_IBB::respondSuccess(const Jid &to, const TQString &id, const TQString &streamid)
|
|
{
|
|
TQDomElement iq = createIQ(doc(), "result", to.full(), id);
|
|
TQDomElement query = doc()->createElement("query");
|
|
query.setAttribute("xmlns", "http://jabber.org/protocol/ibb");
|
|
iq.appendChild(query);
|
|
query.appendChild(textTag(doc(), "streamid", streamid));
|
|
send(iq);
|
|
}
|
|
|
|
void JT_IBB::respondError(const Jid &to, const TQString &id, int code, const TQString &str)
|
|
{
|
|
TQDomElement iq = createIQ(doc(), "error", to.full(), id);
|
|
TQDomElement err = textTag(doc(), "error", str);
|
|
err.setAttribute("code", TQString::number(code));
|
|
iq.appendChild(err);
|
|
send(iq);
|
|
}
|
|
|
|
void JT_IBB::respondAck(const Jid &to, const TQString &id)
|
|
{
|
|
TQDomElement iq = createIQ(doc(), "result", to.full(), id);
|
|
send(iq);
|
|
}
|
|
|
|
void JT_IBB::onGo()
|
|
{
|
|
send(d->iq);
|
|
}
|
|
|
|
bool JT_IBB::take(const TQDomElement &e)
|
|
{
|
|
if(d->serve) {
|
|
// must be an iq-set tag
|
|
if(e.tagName() != "iq" || e.attribute("type") != "set")
|
|
return false;
|
|
|
|
if(queryNS(e) != "http://jabber.org/protocol/ibb")
|
|
return false;
|
|
|
|
Jid from(e.attribute("from"));
|
|
TQString id = e.attribute("id");
|
|
TQDomElement q = queryTag(e);
|
|
|
|
bool found;
|
|
TQDomElement s = findSubTag(q, "streamid", &found);
|
|
if(!found) {
|
|
TQDomElement comment = findSubTag(q, "comment", &found);
|
|
incomingRequest(from, id, comment);
|
|
}
|
|
else {
|
|
TQString sid = tagContent(s);
|
|
TQByteArray a;
|
|
bool close = false;
|
|
s = findSubTag(q, "data", &found);
|
|
if(found)
|
|
a = Base64::stringToArray(tagContent(s));
|
|
s = findSubTag(q, "close", &found);
|
|
if(found)
|
|
close = true;
|
|
|
|
incomingData(from, sid, id, a, close);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else {
|
|
Jid from(e.attribute("from"));
|
|
if(e.attribute("id") != id() || !d->to.compare(from))
|
|
return false;
|
|
|
|
if(e.attribute("type") == "result") {
|
|
TQDomElement q = queryTag(e);
|
|
|
|
// request
|
|
if(d->mode == ModeRequest) {
|
|
bool found;
|
|
TQDomElement s = findSubTag(q, "streamid", &found);
|
|
if(found)
|
|
d->streamid = tagContent(s);
|
|
else
|
|
d->streamid = "";
|
|
setSuccess();
|
|
}
|
|
// sendData
|
|
else {
|
|
// thank you for the ack, kind sir
|
|
setSuccess();
|
|
}
|
|
}
|
|
else {
|
|
setError(e);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
TQString JT_IBB::streamid() const
|
|
{
|
|
return d->streamid;
|
|
}
|
|
|
|
Jid JT_IBB::jid() const
|
|
{
|
|
return d->to;
|
|
}
|
|
|
|
int JT_IBB::mode() const
|
|
{
|
|
return d->mode;
|
|
}
|
|
|