initial commit

This commit is contained in:
mikhail "synzr" 2025-12-25 01:37:49 +05:00
commit 9d20827c46
2469 changed files with 470994 additions and 0 deletions

View file

@ -0,0 +1,200 @@
#include <QtCore> // for qWarning()
#include <QObject>
#include <QIODevice>
#include <zlib.h>
#include "compress.h"
#define CHUNK_SIZE 1024
static void initZStream(z_stream* z)
{
z->next_in = NULL;
z->avail_in = 0;
z->total_in = 0;
z->next_out = NULL;
z->avail_out = 0;
z->total_out = 0;
z->msg = NULL;
z->state = NULL;
z->zalloc = Z_NULL;
z->zfree = Z_NULL;
z->opaque = Z_NULL;
z->data_type = Z_BINARY;
z->adler = 0;
z->reserved = 0;
}
Compressor::Compressor(QIODevice* device, int compression) : device_(device)
{
zlib_stream_ = (z_stream*) malloc(sizeof(z_stream));
initZStream(zlib_stream_);
int result = deflateInit(zlib_stream_, compression);
Q_ASSERT(result == Z_OK);
Q_UNUSED(result);
connect(device, SIGNAL(aboutToClose()), this, SLOT(flush()));
flushed_ = false;
}
Compressor::~Compressor()
{
flush();
free(zlib_stream_);
}
void Compressor::flush()
{
if (flushed_)
return;
// Flush
write(QByteArray(),true);
int result = deflateEnd(zlib_stream_);
if (result != Z_OK)
qWarning("compressor.c: deflateEnd failed (%d)", result);
flushed_ = true;
}
int Compressor::write(const QByteArray& input)
{
return write(input,false);
}
int Compressor::write(const QByteArray& input, bool flush)
{
int result;
zlib_stream_->avail_in = input.size();
zlib_stream_->next_in = (Bytef*) input.data();
QByteArray output;
// Write the data
int output_position = 0;
do {
output.resize(output_position + CHUNK_SIZE);
zlib_stream_->avail_out = CHUNK_SIZE;
zlib_stream_->next_out = (Bytef*) (output.data() + output_position);
result = deflate(zlib_stream_,(flush ? Z_FINISH : Z_NO_FLUSH));
if (result == Z_STREAM_ERROR) {
qWarning("compressor.cpp: Error ('%s')", zlib_stream_->msg);
return result;
}
output_position += CHUNK_SIZE;
}
while (zlib_stream_->avail_out == 0);
if (zlib_stream_->avail_in != 0) {
qWarning("Compressor: avail_in != 0");
}
output_position -= zlib_stream_->avail_out;
// Flush the data
if (!flush) {
do {
output.resize(output_position + CHUNK_SIZE);
zlib_stream_->avail_out = CHUNK_SIZE;
zlib_stream_->next_out = (Bytef*) (output.data() + output_position);
result = deflate(zlib_stream_,Z_SYNC_FLUSH);
if (result == Z_STREAM_ERROR) {
qWarning("compressor.cpp: Error ('%s')", zlib_stream_->msg);
return result;
}
output_position += CHUNK_SIZE;
}
while (zlib_stream_->avail_out == 0);
output_position -= zlib_stream_->avail_out;
}
output.resize(output_position);
// Write the compressed data
device_->write(output);
return 0;
}
// -----------------------------------------------------------------------------
Decompressor::Decompressor(QIODevice* device) : device_(device)
{
zlib_stream_ = (z_stream*) malloc(sizeof(z_stream));
initZStream(zlib_stream_);
int result = inflateInit(zlib_stream_);
Q_ASSERT(result == Z_OK);
Q_UNUSED(result);
connect(device, SIGNAL(aboutToClose()), this, SLOT(flush()));
flushed_ = false;
}
Decompressor::~Decompressor()
{
flush();
free(zlib_stream_);
}
void Decompressor::flush()
{
if (flushed_)
return;
// Flush
write(QByteArray(),true);
int result = inflateEnd(zlib_stream_);
if (result != Z_OK)
qWarning("compressor.c: inflateEnd failed (%d)", result);
flushed_ = true;
}
int Decompressor::write(const QByteArray& input)
{
return write(input,false);
}
int Decompressor::write(const QByteArray& input, bool flush)
{
int result;
zlib_stream_->avail_in = input.size();
zlib_stream_->next_in = (Bytef*) input.data();
QByteArray output;
// Write the data
int output_position = 0;
do {
output.resize(output_position + CHUNK_SIZE);
zlib_stream_->avail_out = CHUNK_SIZE;
zlib_stream_->next_out = (Bytef*) (output.data() + output_position);
result = inflate(zlib_stream_,(flush ? Z_FINISH : Z_NO_FLUSH));
if (result == Z_STREAM_ERROR) {
qWarning("compressor.cpp: Error ('%s')", zlib_stream_->msg);
return result;
}
output_position += CHUNK_SIZE;
}
while (zlib_stream_->avail_out == 0);
//Q_ASSERT(zlib_stream_->avail_in == 0);
if (zlib_stream_->avail_in != 0) {
qWarning() << "Decompressor: Unexpected state: avail_in=" << zlib_stream_->avail_in << ",avail_out=" << zlib_stream_->avail_out << ",result=" << result;
return Z_STREAM_ERROR; // FIXME: Should probably return 'result'
}
output_position -= zlib_stream_->avail_out;
// Flush the data
if (!flush) {
do {
output.resize(output_position + CHUNK_SIZE);
zlib_stream_->avail_out = CHUNK_SIZE;
zlib_stream_->next_out = (Bytef*) (output.data() + output_position);
result = inflate(zlib_stream_,Z_SYNC_FLUSH);
if (result == Z_STREAM_ERROR) {
qWarning("compressor.cpp: Error ('%s')", zlib_stream_->msg);
return result;
}
output_position += CHUNK_SIZE;
}
while (zlib_stream_->avail_out == 0);
output_position -= zlib_stream_->avail_out;
}
output.resize(output_position);
// Write the compressed data
device_->write(output);
return 0;
}

View file

@ -0,0 +1,54 @@
#ifndef COMPRESS_H
#define COMPRESS_H
#include <QObject>
#include "zlib.h"
class QIODevice;
class Compressor : public QObject
{
Q_OBJECT
public:
Compressor(QIODevice* device, int compression = Z_DEFAULT_COMPRESSION);
~Compressor();
int write(const QByteArray&);
protected slots:
void flush();
protected:
int write(const QByteArray&, bool flush);
private:
QIODevice* device_;
z_stream* zlib_stream_;
bool flushed_;
};
class Decompressor : public QObject
{
Q_OBJECT
public:
Decompressor(QIODevice* device);
~Decompressor();
int write(const QByteArray&);
protected slots:
void flush();
protected:
int write(const QByteArray&, bool flush);
private:
QIODevice* device_;
z_stream* zlib_stream_;
bool flushed_;
};
#endif

View file

@ -0,0 +1,67 @@
#include <QTimer>
#include <QDebug>
#include "compressionhandler.h"
#include "compress.h"
CompressionHandler::CompressionHandler()
: errorCode_(0)
{
outgoing_buffer_.open(QIODevice::ReadWrite);
compressor_ = new Compressor(&outgoing_buffer_);
incoming_buffer_.open(QIODevice::ReadWrite);
decompressor_ = new Decompressor(&incoming_buffer_);
}
CompressionHandler::~CompressionHandler()
{
delete compressor_;
delete decompressor_;
}
void CompressionHandler::writeIncoming(const QByteArray& a)
{
//qDebug("CompressionHandler::writeIncoming");
//qDebug() << (QString("Incoming %1 bytes").arg(a.size()).toAscii());
errorCode_ = decompressor_->write(a);
if (!errorCode_)
QTimer::singleShot(0, this, SIGNAL(readyRead()));
else
QTimer::singleShot(0, this, SIGNAL(error()));
}
void CompressionHandler::write(const QByteArray& a)
{
//qDebug() << (QString("CompressionHandler::write(%1)").arg(a.size()).toAscii());
errorCode_ = compressor_->write(a);
if (!errorCode_)
QTimer::singleShot(0, this, SIGNAL(readyReadOutgoing()));
else
QTimer::singleShot(0, this, SIGNAL(error()));
}
QByteArray CompressionHandler::read()
{
//qDebug("CompressionHandler::read");
QByteArray b = incoming_buffer_.buffer();
incoming_buffer_.buffer().clear();
incoming_buffer_.reset();
return b;
}
QByteArray CompressionHandler::readOutgoing(int* i)
{
//qDebug("CompressionHandler::readOutgoing");
//qDebug() << (QString("Outgoing %1 bytes").arg(outgoing_buffer_.size()).toAscii());
QByteArray b = outgoing_buffer_.buffer();
outgoing_buffer_.buffer().clear();
outgoing_buffer_.reset();
*i = b.size();
return b;
}
int CompressionHandler::errorCode()
{
return errorCode_;
}

View file

@ -0,0 +1,35 @@
#ifndef COMPRESSIONHANDLER_H
#define COMPRESSIONHANDLER_H
#include <QObject>
#include <QBuffer>
class Compressor;
class Decompressor;
class CompressionHandler : public QObject
{
Q_OBJECT
public:
CompressionHandler();
~CompressionHandler();
void writeIncoming(const QByteArray& a);
void write(const QByteArray& a);
QByteArray read();
QByteArray readOutgoing(int*);
int errorCode();
signals:
void readyRead();
void readyReadOutgoing();
void error();
private:
Compressor* compressor_;
Decompressor* decompressor_;
QBuffer outgoing_buffer_, incoming_buffer_;
int errorCode_;
};
#endif

View file

@ -0,0 +1,742 @@
/*
* connector.cpp - establish a connection to an XMPP server
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*
TODO:
- Test and analyze all possible branches
XMPP::AdvancedConnector is "good for now." The only real issue is that
most of what it provides is just to work around the old Jabber/XMPP 0.9
connection behavior. When XMPP 1.0 has taken over the world, we can
greatly simplify this class. - Sep 3rd, 2003.
*/
#include "xmpp.h"
#include <qpointer.h>
#include <qca.h>
#include <QList>
#include <QUrl>
#include "safedelete.h"
#include <libidn/idna.h>
#ifdef NO_NDNS
#include <q3dns.h>
#else
#include "ndns.h"
#endif
#include "bsocket.h"
#include "httpconnect.h"
#include "httppoll.h"
#include "socks.h"
#include "srvresolver.h"
//#define XMPP_DEBUG
using namespace XMPP;
//----------------------------------------------------------------------------
// Connector
//----------------------------------------------------------------------------
Connector::Connector(QObject *parent)
:QObject(parent)
{
setUseSSL(false);
setPeerAddressNone();
}
Connector::~Connector()
{
}
bool Connector::useSSL() const
{
return ssl;
}
bool Connector::havePeerAddress() const
{
return haveaddr;
}
QHostAddress Connector::peerAddress() const
{
return addr;
}
Q_UINT16 Connector::peerPort() const
{
return port;
}
void Connector::setUseSSL(bool b)
{
ssl = b;
}
void Connector::setPeerAddressNone()
{
haveaddr = false;
addr = QHostAddress();
port = 0;
}
void Connector::setPeerAddress(const QHostAddress &_addr, Q_UINT16 _port)
{
haveaddr = true;
addr = _addr;
port = _port;
}
//----------------------------------------------------------------------------
// AdvancedConnector::Proxy
//----------------------------------------------------------------------------
AdvancedConnector::Proxy::Proxy()
{
t = None;
v_poll = 30;
}
AdvancedConnector::Proxy::~Proxy()
{
}
int AdvancedConnector::Proxy::type() const
{
return t;
}
QString AdvancedConnector::Proxy::host() const
{
return v_host;
}
Q_UINT16 AdvancedConnector::Proxy::port() const
{
return v_port;
}
QString AdvancedConnector::Proxy::url() const
{
return v_url;
}
QString AdvancedConnector::Proxy::user() const
{
return v_user;
}
QString AdvancedConnector::Proxy::pass() const
{
return v_pass;
}
int AdvancedConnector::Proxy::pollInterval() const
{
return v_poll;
}
void AdvancedConnector::Proxy::setHttpConnect(const QString &host, Q_UINT16 port)
{
t = HttpConnect;
v_host = host;
v_port = port;
}
void AdvancedConnector::Proxy::setHttpPoll(const QString &host, Q_UINT16 port, const QString &url)
{
t = HttpPoll;
v_host = host;
v_port = port;
v_url = url;
}
void AdvancedConnector::Proxy::setSocks(const QString &host, Q_UINT16 port)
{
t = Socks;
v_host = host;
v_port = port;
}
void AdvancedConnector::Proxy::setUserPass(const QString &user, const QString &pass)
{
v_user = user;
v_pass = pass;
}
void AdvancedConnector::Proxy::setPollInterval(int secs)
{
v_poll = secs;
}
//----------------------------------------------------------------------------
// AdvancedConnector
//----------------------------------------------------------------------------
enum { Idle, Connecting, Connected };
class AdvancedConnector::Private
{
public:
int mode;
ByteStream *bs;
#ifdef NO_NDNS
Q3Dns *qdns;
#else
NDns dns;
#endif
SrvResolver srv;
QString server;
QString opt_host;
int opt_port;
bool opt_probe, opt_ssl;
Proxy proxy;
QString host;
int port;
QList<Q3Dns::Server> servers;
int errorCode;
bool multi, using_srv;
bool will_be_ssl;
int probe_mode;
bool aaaa;
SafeDelete sd;
};
AdvancedConnector::AdvancedConnector(QObject *parent)
:Connector(parent)
{
d = new Private;
d->bs = 0;
#ifdef NO_NDNS
d->qdns = 0;
#else
connect(&d->dns, SIGNAL(resultsReady()), SLOT(dns_done()));
#endif
connect(&d->srv, SIGNAL(resultsReady()), SLOT(srv_done()));
d->opt_probe = false;
d->opt_ssl = false;
cleanup();
d->errorCode = 0;
}
AdvancedConnector::~AdvancedConnector()
{
cleanup();
delete d;
}
void AdvancedConnector::cleanup()
{
d->mode = Idle;
// stop any dns
#ifdef NO_NDNS
if(d->qdns) {
d->qdns->disconnect(this);
d->qdns->deleteLater();
//d->sd.deleteLater(d->qdns);
d->qdns = 0;
}
#else
if(d->dns.isBusy())
d->dns.stop();
#endif
if(d->srv.isBusy())
d->srv.stop();
// destroy the bytestream, if there is one
delete d->bs;
d->bs = 0;
d->multi = false;
d->using_srv = false;
d->will_be_ssl = false;
d->probe_mode = -1;
setUseSSL(false);
setPeerAddressNone();
}
void AdvancedConnector::setProxy(const Proxy &proxy)
{
if(d->mode != Idle)
return;
d->proxy = proxy;
}
void AdvancedConnector::setOptHostPort(const QString &host, Q_UINT16 _port)
{
if(d->mode != Idle)
return;
d->opt_host = host;
d->opt_port = _port;
}
void AdvancedConnector::setOptProbe(bool b)
{
if(d->mode != Idle)
return;
d->opt_probe = b;
}
void AdvancedConnector::setOptSSL(bool b)
{
if(d->mode != Idle)
return;
d->opt_ssl = b;
}
void AdvancedConnector::connectToServer(const QString &server)
{
if(d->mode != Idle)
return;
if(server.isEmpty())
return;
d->errorCode = 0;
d->mode = Connecting;
d->aaaa = true;
// Encode the servername
d->server = QUrl::toAce(server);
//char* server_encoded;
//if (!idna_to_ascii_8z(server.utf8().data(), &server_encoded, 0)) {
// d->server = QString(server_encoded);
// free(server_encoded);
//}
//else {
// d->server = server;
//}
if(d->proxy.type() == Proxy::HttpPoll) {
// need SHA1 here
//if(!QCA::isSupported(QCA::CAP_SHA1))
// QCA::insertProvider(createProviderHash());
HttpPoll *s = new HttpPoll;
d->bs = s;
connect(s, SIGNAL(connected()), SLOT(bs_connected()));
connect(s, SIGNAL(syncStarted()), SLOT(http_syncStarted()));
connect(s, SIGNAL(syncFinished()), SLOT(http_syncFinished()));
connect(s, SIGNAL(error(int)), SLOT(bs_error(int)));
if(!d->proxy.user().isEmpty())
s->setAuth(d->proxy.user(), d->proxy.pass());
s->setPollInterval(d->proxy.pollInterval());
if(d->proxy.host().isEmpty())
s->connectToUrl(d->proxy.url());
else
s->connectToHost(d->proxy.host(), d->proxy.port(), d->proxy.url());
}
else if (d->proxy.type() == Proxy::HttpConnect) {
if(!d->opt_host.isEmpty()) {
d->host = d->opt_host;
d->port = d->opt_port;
}
else {
d->host = server;
d->port = 5222;
}
do_connect();
}
else {
if(!d->opt_host.isEmpty()) {
d->host = d->opt_host;
d->port = d->opt_port;
do_resolve();
}
else {
d->multi = true;
QPointer<QObject> self = this;
emit srvLookup(d->server);
if(!self)
return;
d->srv.resolveSrvOnly(d->server, "xmpp-client", "tcp");
}
}
}
void AdvancedConnector::changePollInterval(int secs)
{
if(d->bs && (d->bs->inherits("XMPP::HttpPoll") || d->bs->inherits("HttpPoll"))) {
HttpPoll *s = static_cast<HttpPoll*>(d->bs);
s->setPollInterval(secs);
}
}
ByteStream *AdvancedConnector::stream() const
{
if(d->mode == Connected)
return d->bs;
else
return 0;
}
void AdvancedConnector::done()
{
cleanup();
}
int AdvancedConnector::errorCode() const
{
return d->errorCode;
}
void AdvancedConnector::do_resolve()
{
#ifdef NO_NDNS
printf("resolving (aaaa=%d)\n", d->aaaa);
d->qdns = new Q3Dns;
connect(d->qdns, SIGNAL(resultsReady()), SLOT(dns_done()));
if(d->aaaa)
d->qdns->setRecordType(Q3Dns::Aaaa); // IPv6
else
d->qdns->setRecordType(Q3Dns::A); // IPv4
d->qdns->setLabel(d->host);
#else
d->dns.resolve(d->host);
#endif
}
void AdvancedConnector::dns_done()
{
bool failed = false;
QHostAddress addr;
#ifdef NO_NDNS
//if(!d->qdns)
// return;
// apparently we sometimes get this signal even though the results aren' t ready
//if(d->qdns->isWorking())
// return;
//SafeDeleteLock s(&d->sd);
// grab the address list and destroy the qdns object
QList<QHostAddress> list = d->qdns->addresses();
d->qdns->disconnect(this);
d->qdns->deleteLater();
//d->sd.deleteLater(d->qdns);
d->qdns = 0;
if(list.isEmpty()) {
if(d->aaaa) {
d->aaaa = false;
do_resolve();
return;
}
//do_resolve();
//return;
failed = true;
}
else
addr = list.first();
#else
if(d->dns.result().isNull ())
failed = true;
else
addr = QHostAddress(d->dns.result());
#endif
if(failed) {
#ifdef XMPP_DEBUG
printf("dns1\n");
#endif
// using proxy? then try the unresolved host through the proxy
if(d->proxy.type() != Proxy::None) {
#ifdef XMPP_DEBUG
printf("dns1.1\n");
#endif
do_connect();
}
else if(d->using_srv) {
#ifdef XMPP_DEBUG
printf("dns1.2\n");
#endif
if(d->servers.isEmpty()) {
#ifdef XMPP_DEBUG
printf("dns1.2.1\n");
#endif
cleanup();
d->errorCode = ErrConnectionRefused;
error();
}
else {
#ifdef XMPP_DEBUG
printf("dns1.2.2\n");
#endif
tryNextSrv();
return;
}
}
else {
#ifdef XMPP_DEBUG
printf("dns1.3\n");
#endif
cleanup();
d->errorCode = ErrHostNotFound;
error();
}
}
else {
#ifdef XMPP_DEBUG
printf("dns2\n");
#endif
d->host = addr.toString();
do_connect();
}
}
void AdvancedConnector::do_connect()
{
#ifdef XMPP_DEBUG
printf("trying %s:%d\n", d->host.latin1(), d->port);
#endif
int t = d->proxy.type();
if(t == Proxy::None) {
#ifdef XMPP_DEBUG
printf("do_connect1\n");
#endif
BSocket *s = new BSocket;
d->bs = s;
connect(s, SIGNAL(connected()), SLOT(bs_connected()));
connect(s, SIGNAL(error(int)), SLOT(bs_error(int)));
s->connectToHost(d->host, d->port);
}
else if(t == Proxy::HttpConnect) {
#ifdef XMPP_DEBUG
printf("do_connect2\n");
#endif
HttpConnect *s = new HttpConnect;
d->bs = s;
connect(s, SIGNAL(connected()), SLOT(bs_connected()));
connect(s, SIGNAL(error(int)), SLOT(bs_error(int)));
if(!d->proxy.user().isEmpty())
s->setAuth(d->proxy.user(), d->proxy.pass());
s->connectToHost(d->proxy.host(), d->proxy.port(), d->host, d->port);
}
else if(t == Proxy::Socks) {
#ifdef XMPP_DEBUG
printf("do_connect3\n");
#endif
SocksClient *s = new SocksClient;
d->bs = s;
connect(s, SIGNAL(connected()), SLOT(bs_connected()));
connect(s, SIGNAL(error(int)), SLOT(bs_error(int)));
if(!d->proxy.user().isEmpty())
s->setAuth(d->proxy.user(), d->proxy.pass());
s->connectToHost(d->proxy.host(), d->proxy.port(), d->host, d->port);
}
}
void AdvancedConnector::tryNextSrv()
{
#ifdef XMPP_DEBUG
printf("trying next srv\n");
#endif
d->host = d->servers.first().name;
d->port = d->servers.first().port;
d->servers.remove(d->servers.begin());
do_resolve();
}
void AdvancedConnector::srv_done()
{
QPointer<QObject> self = this;
#ifdef XMPP_DEBUG
printf("srv_done1\n");
#endif
d->servers = d->srv.servers();
if(d->servers.isEmpty()) {
srvResult(false);
if(!self)
return;
#ifdef XMPP_DEBUG
printf("srv_done1.1\n");
#endif
// fall back to A record
d->using_srv = false;
d->host = d->server;
if(d->opt_probe) {
#ifdef XMPP_DEBUG
printf("srv_done1.1.1\n");
#endif
d->probe_mode = 0;
d->port = 5223;
d->will_be_ssl = true;
}
else {
#ifdef XMPP_DEBUG
printf("srv_done1.1.2\n");
#endif
d->probe_mode = 1;
d->port = 5222;
}
do_resolve();
return;
}
srvResult(true);
if(!self)
return;
d->using_srv = true;
tryNextSrv();
}
void AdvancedConnector::bs_connected()
{
if(d->proxy.type() == Proxy::None) {
QHostAddress h = (static_cast<BSocket*>(d->bs))->peerAddress();
int p = (static_cast<BSocket*>(d->bs))->peerPort();
setPeerAddress(h, p);
}
// only allow ssl override if proxy==poll or host:port
if((d->proxy.type() == Proxy::HttpPoll || !d->opt_host.isEmpty()) && d->opt_ssl)
setUseSSL(true);
else if(d->will_be_ssl)
setUseSSL(true);
d->mode = Connected;
connected();
}
void AdvancedConnector::bs_error(int x)
{
if(d->mode == Connected) {
d->errorCode = ErrStream;
error();
return;
}
bool proxyError = false;
int err = ErrConnectionRefused;
int t = d->proxy.type();
#ifdef XMPP_DEBUG
printf("bse1\n");
#endif
// figure out the error
if(t == Proxy::None) {
if(x == BSocket::ErrHostNotFound)
err = ErrHostNotFound;
else
err = ErrConnectionRefused;
}
else if(t == Proxy::HttpConnect) {
if(x == HttpConnect::ErrConnectionRefused)
err = ErrConnectionRefused;
else if(x == HttpConnect::ErrHostNotFound)
err = ErrHostNotFound;
else {
proxyError = true;
if(x == HttpConnect::ErrProxyAuth)
err = ErrProxyAuth;
else if(x == HttpConnect::ErrProxyNeg)
err = ErrProxyNeg;
else
err = ErrProxyConnect;
}
}
else if(t == Proxy::HttpPoll) {
if(x == HttpPoll::ErrConnectionRefused)
err = ErrConnectionRefused;
else if(x == HttpPoll::ErrHostNotFound)
err = ErrHostNotFound;
else {
proxyError = true;
if(x == HttpPoll::ErrProxyAuth)
err = ErrProxyAuth;
else if(x == HttpPoll::ErrProxyNeg)
err = ErrProxyNeg;
else
err = ErrProxyConnect;
}
}
else if(t == Proxy::Socks) {
if(x == SocksClient::ErrConnectionRefused)
err = ErrConnectionRefused;
else if(x == SocksClient::ErrHostNotFound)
err = ErrHostNotFound;
else {
proxyError = true;
if(x == SocksClient::ErrProxyAuth)
err = ErrProxyAuth;
else if(x == SocksClient::ErrProxyNeg)
err = ErrProxyNeg;
else
err = ErrProxyConnect;
}
}
// no-multi or proxy error means we quit
if(!d->multi || proxyError) {
cleanup();
d->errorCode = err;
error();
return;
}
if(d->using_srv && !d->servers.isEmpty()) {
#ifdef XMPP_DEBUG
printf("bse1.1\n");
#endif
tryNextSrv();
}
else if(!d->using_srv && d->opt_probe && d->probe_mode == 0) {
#ifdef XMPP_DEBUG
printf("bse1.2\n");
#endif
d->probe_mode = 1;
d->port = 5222;
d->will_be_ssl = false;
do_connect();
}
else {
#ifdef XMPP_DEBUG
printf("bse1.3\n");
#endif
cleanup();
d->errorCode = ErrConnectionRefused;
error();
}
}
void AdvancedConnector::http_syncStarted()
{
httpSyncStarted();
}
void AdvancedConnector::http_syncFinished()
{
httpSyncFinished();
}

View file

@ -0,0 +1,441 @@
/*
* jid.cpp - class for verifying and manipulating Jabber IDs
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "xmpp_jid.h"
#include <QApplication>
#include <QByteArray>
#include <QHash>
#include <libidn/stringprep.h>
using namespace XMPP;
uint qHash(const XMPP::Jid& jid)
{
return qHash(jid.full());
}
//----------------------------------------------------------------------------
// StringPrepCache
//----------------------------------------------------------------------------
class StringPrepCache : public QObject
{
public:
static bool nameprep(const QString &in, int maxbytes, QString *out)
{
if(in.isEmpty())
{
if(out)
*out = QString();
return true;
}
StringPrepCache *that = get_instance();
Result *r = that->nameprep_table[in];
if(r)
{
if(!r->norm)
return false;
if(out)
*out = *(r->norm);
return true;
}
QByteArray cs = in.toUtf8();
cs.resize(maxbytes);
if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_nameprep) != 0)
{
that->nameprep_table.insert(in, new Result);
return false;
}
QString norm = QString::fromUtf8(cs);
that->nameprep_table.insert(in, new Result(norm));
if(out)
*out = norm;
return true;
}
static bool nodeprep(const QString &in, int maxbytes, QString *out)
{
if(in.isEmpty())
{
if(out)
*out = QString();
return true;
}
StringPrepCache *that = get_instance();
Result *r = that->nodeprep_table[in];
if(r)
{
if(!r->norm)
return false;
if(out)
*out = *(r->norm);
return true;
}
QByteArray cs = in.toUtf8();
cs.resize(maxbytes);
if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_xmpp_nodeprep) != 0)
{
that->nodeprep_table.insert(in, new Result);
return false;
}
QString norm = QString::fromUtf8(cs);
that->nodeprep_table.insert(in, new Result(norm));
if(out)
*out = norm;
return true;
}
static bool resourceprep(const QString &in, int maxbytes, QString *out)
{
if(in.isEmpty())
{
if(out)
*out = QString();
return true;
}
StringPrepCache *that = get_instance();
Result *r = that->resourceprep_table[in];
if(r)
{
if(!r->norm)
return false;
if(out)
*out = *(r->norm);
return true;
}
QByteArray cs = in.toUtf8();
cs.resize(maxbytes);
if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_xmpp_resourceprep) != 0)
{
that->resourceprep_table.insert(in, new Result);
return false;
}
QString norm = QString::fromUtf8(cs);
that->resourceprep_table.insert(in, new Result(norm));
if(out)
*out = norm;
return true;
}
private:
class Result
{
public:
QString *norm;
Result() : norm(0)
{
}
Result(const QString &s) : norm(new QString(s))
{
}
~Result()
{
delete norm;
}
};
QHash<QString,Result*> nameprep_table;
QHash<QString,Result*> nodeprep_table;
QHash<QString,Result*> resourceprep_table;
static StringPrepCache *instance;
static StringPrepCache *get_instance()
{
if(!instance)
instance = new StringPrepCache;
return instance;
}
StringPrepCache()
: QObject(qApp)
{
}
~StringPrepCache()
{
foreach(Result* r, nameprep_table) {
delete r;
}
nameprep_table.clear();
foreach(Result* r, nodeprep_table) {
delete r;
}
nodeprep_table.clear();
foreach(Result* r, resourceprep_table) {
delete r;
}
resourceprep_table.clear();
}
};
StringPrepCache *StringPrepCache::instance = 0;
//----------------------------------------------------------------------------
// Jid
//----------------------------------------------------------------------------
Jid::Jid()
{
valid = false;
null = true;
}
Jid::~Jid()
{
}
Jid::Jid(const QString &s)
{
set(s);
}
Jid::Jid(const char *s)
{
set(QString(s));
}
Jid & Jid::operator=(const QString &s)
{
set(s);
return *this;
}
Jid & Jid::operator=(const char *s)
{
set(QString(s));
return *this;
}
void Jid::reset()
{
f = QString();
b = QString();
d = QString();
n = QString();
r = QString();
valid = false;
null = true;
}
void Jid::update()
{
// build 'bare' and 'full' jids
if(n.isEmpty())
b = d;
else
b = n + '@' + d;
if(r.isEmpty())
f = b;
else
f = b + '/' + r;
if(f.isEmpty())
valid = false;
null = f.isEmpty() && r.isEmpty();
}
void Jid::set(const QString &s)
{
QString rest, domain, node, resource;
QString norm_domain, norm_node, norm_resource;
int x = s.indexOf('/');
if(x != -1) {
rest = s.mid(0, x);
resource = s.mid(x+1);
}
else {
rest = s;
resource = QString();
}
if(!validResource(resource, &norm_resource)) {
reset();
return;
}
x = rest.indexOf('@');
if(x != -1) {
node = rest.mid(0, x);
domain = rest.mid(x+1);
}
else {
node = QString();
domain = rest;
}
if(!validDomain(domain, &norm_domain) || !validNode(node, &norm_node)) {
reset();
return;
}
valid = true;
null = false;
d = norm_domain;
n = norm_node;
r = norm_resource;
update();
}
void Jid::set(const QString &domain, const QString &node, const QString &resource)
{
QString norm_domain, norm_node, norm_resource;
if(!validDomain(domain, &norm_domain) || !validNode(node, &norm_node) || !validResource(resource, &norm_resource)) {
reset();
return;
}
valid = true;
null = false;
d = norm_domain;
n = norm_node;
r = norm_resource;
update();
}
void Jid::setDomain(const QString &s)
{
if(!valid)
return;
QString norm;
if(!validDomain(s, &norm)) {
reset();
return;
}
d = norm;
update();
}
void Jid::setNode(const QString &s)
{
if(!valid)
return;
QString norm;
if(!validNode(s, &norm)) {
reset();
return;
}
n = norm;
update();
}
void Jid::setResource(const QString &s)
{
if(!valid)
return;
QString norm;
if(!validResource(s, &norm)) {
reset();
return;
}
r = norm;
update();
}
Jid Jid::withNode(const QString &s) const
{
Jid j = *this;
j.setNode(s);
return j;
}
Jid Jid::withResource(const QString &s) const
{
Jid j = *this;
j.setResource(s);
return j;
}
bool Jid::isValid() const
{
return valid;
}
bool Jid::isEmpty() const
{
return f.isEmpty();
}
bool Jid::compare(const Jid &a, bool compareRes) const
{
if(null && a.null)
return true;
// only compare valid jids
if(!valid || !a.valid)
return false;
if(compareRes ? (f != a.f) : (b != a.b))
return false;
return true;
}
bool Jid::validDomain(const QString &s, QString *norm)
{
/*QCString cs = s.utf8();
cs.resize(1024);
if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_nameprep) != 0)
return false;
if(norm)
*norm = QString::fromUtf8(cs);
return true;*/
return StringPrepCache::nameprep(s, 1024, norm);
}
bool Jid::validNode(const QString &s, QString *norm)
{
/*QCString cs = s.utf8();
cs.resize(1024);
if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_xmpp_nodeprep) != 0)
return false;
if(norm)
*norm = QString::fromUtf8(cs);
return true;*/
return StringPrepCache::nodeprep(s, 1024, norm);
}
bool Jid::validResource(const QString &s, QString *norm)
{
/*QCString cs = s.utf8();
cs.resize(1024);
if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_xmpp_resourceprep) != 0)
return false;
if(norm)
*norm = QString::fromUtf8(cs);
return true;*/
return StringPrepCache::resourceprep(s, 1024, norm);
}
bool Jid::operator==(const Jid& other) const
{
return compare(other);
}

View file

@ -0,0 +1,798 @@
/*
* parser.cpp - parse an XMPP "document"
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*
TODO:
For XMPP::Parser to be "perfect", some things must be solved/changed in the
Qt library:
- Fix weird QDomElement::haveAttributeNS() bug (patch submitted to
Trolltech on Aug 31st, 2003).
- Fix weird behavior in QXmlSimpleReader of reporting endElement() when
the '/' character of a self-closing tag is reached, instead of when
the final '>' is reached.
- Fix incremental parsing bugs in QXmlSimpleReader. At the moment, the
only bug I've found is related to attribute parsing, but there might
be more (search for '###' in $QTDIR/src/xml/qxml.cpp).
We have workarounds for all of the above problems in the code below.
- Deal with the <?xml?> processing instruction as an event type, so that we
can feed it back to the application properly. Right now it is completely
untrackable and is simply tacked into the first event's actualString. We
can't easily do this because QXmlSimpleReader eats an extra byte beyond
the processing instruction before reporting it.
- Make QXmlInputSource capable of accepting data incrementally, to ensure
proper text encoding detection and processing over a network. This is
technically not a bug, as we have our own subclass below to do it, but
it would be nice if Qt had this already.
*/
#include "parser.h"
#include <qtextcodec.h>
#include <q3ptrlist.h>
#include <string.h>
using namespace XMPP;
static bool qt_bug_check = false;
static bool qt_bug_have;
//----------------------------------------------------------------------------
// StreamInput
//----------------------------------------------------------------------------
class StreamInput : public QXmlInputSource
{
public:
StreamInput()
{
dec = 0;
reset();
}
~StreamInput()
{
delete dec;
}
void reset()
{
delete dec;
dec = 0;
in.resize(0);
out = "";
at = 0;
paused = false;
mightChangeEncoding = true;
checkBad = true;
last = QChar();
v_encoding = "";
resetLastData();
}
void resetLastData()
{
last_string = "";
}
QString lastString() const
{
return last_string;
}
void appendData(const QByteArray &a)
{
int oldsize = in.size();
in.resize(oldsize + a.size());
memcpy(in.data() + oldsize, a.data(), a.size());
processBuf();
}
QChar lastRead()
{
return last;
}
QChar next()
{
if(paused)
return EndOfData;
else
return readNext();
}
// NOTE: setting 'peek' to true allows the same char to be read again,
// however this still advances the internal byte processing.
QChar readNext(bool peek=false)
{
QChar c;
if(mightChangeEncoding)
c = EndOfData;
else {
if(out.isEmpty()) {
QString s;
if(!tryExtractPart(&s))
c = EndOfData;
else {
out = s;
c = out[0];
}
}
else
c = out[0];
if(!peek)
out.remove(0, 1);
}
if(c == EndOfData) {
#ifdef XMPP_PARSER_DEBUG
printf("next() = EOD\n");
#endif
}
else {
#ifdef XMPP_PARSER_DEBUG
printf("next() = [%c]\n", c.latin1());
#endif
last = c;
}
return c;
}
QByteArray unprocessed() const
{
QByteArray a(in.size() - at);
memcpy(a.data(), in.data() + at, a.size());
return a;
}
void pause(bool b)
{
paused = b;
}
bool isPaused()
{
return paused;
}
QString encoding() const
{
return v_encoding;
}
private:
QTextDecoder *dec;
QByteArray in;
QString out;
int at;
bool paused;
bool mightChangeEncoding;
QChar last;
QString v_encoding;
QString last_string;
bool checkBad;
void processBuf()
{
#ifdef XMPP_PARSER_DEBUG
printf("processing. size=%d, at=%d\n", in.size(), at);
#endif
if(!dec) {
QTextCodec *codec = 0;
uchar *p = (uchar *)in.data() + at;
int size = in.size() - at;
// do we have enough information to determine the encoding?
if(size == 0)
return;
bool utf16 = false;
if(p[0] == 0xfe || p[0] == 0xff) {
// probably going to be a UTF-16 byte order mark
if(size < 2)
return;
if((p[0] == 0xfe && p[1] == 0xff) || (p[0] == 0xff && p[1] == 0xfe)) {
// ok it is UTF-16
utf16 = true;
}
}
if(utf16)
codec = QTextCodec::codecForMib(1000); // UTF-16
else
codec = QTextCodec::codecForMib(106); // UTF-8
v_encoding = codec->name();
dec = codec->makeDecoder();
// for utf16, put in the byte order mark
if(utf16) {
out += dec->toUnicode((const char *)p, 2);
at += 2;
}
}
if(mightChangeEncoding) {
while(1) {
int n = out.find('<');
if(n != -1) {
// we need a closing bracket
int n2 = out.find('>', n);
if(n2 != -1) {
++n2;
QString h = out.mid(n, n2-n);
QString enc = processXmlHeader(h);
QTextCodec *codec = 0;
if(!enc.isEmpty())
codec = QTextCodec::codecForName(enc.latin1());
// changing codecs
if(codec) {
v_encoding = codec->name();
delete dec;
dec = codec->makeDecoder();
}
mightChangeEncoding = false;
out.truncate(0);
at = 0;
resetLastData();
break;
}
}
QString s;
if(!tryExtractPart(&s))
break;
if(checkBad && checkForBadChars(s)) {
// go to the parser
mightChangeEncoding = false;
out.truncate(0);
at = 0;
resetLastData();
break;
}
out += s;
}
}
}
QString processXmlHeader(const QString &h)
{
if(h.left(5) != "<?xml")
return "";
int endPos = h.find(">");
int startPos = h.find("encoding");
if(startPos < endPos && startPos != -1) {
QString encoding;
do {
startPos++;
if(startPos > endPos) {
return "";
}
} while(h[startPos] != '"' && h[startPos] != '\'');
startPos++;
while(h[startPos] != '"' && h[startPos] != '\'') {
encoding += h[startPos];
startPos++;
if(startPos > endPos) {
return "";
}
}
return encoding;
}
else
return "";
}
bool tryExtractPart(QString *s)
{
int size = in.size() - at;
if(size == 0)
return false;
uchar *p = (uchar *)in.data() + at;
QString nextChars;
while(1) {
nextChars = dec->toUnicode((const char *)p, 1);
++p;
++at;
if(!nextChars.isEmpty())
break;
if(at == (int)in.size())
return false;
}
last_string += nextChars;
*s = nextChars;
// free processed data?
if(at >= 1024) {
char *p = in.data();
int size = in.size() - at;
memmove(p, p + at, size);
in.resize(size);
at = 0;
}
return true;
}
bool checkForBadChars(const QString &s)
{
int len = s.find('<');
if(len == -1)
len = s.length();
else
checkBad = false;
for(int n = 0; n < len; ++n) {
if(!s.at(n).isSpace())
return true;
}
return false;
}
};
//----------------------------------------------------------------------------
// ParserHandler
//----------------------------------------------------------------------------
namespace XMPP
{
class ParserHandler : public QXmlDefaultHandler
{
public:
ParserHandler(StreamInput *_in, QDomDocument *_doc)
{
in = _in;
doc = _doc;
needMore = false;
}
~ParserHandler()
{
eventList.setAutoDelete(true);
eventList.clear();
}
bool startDocument()
{
depth = 0;
return true;
}
bool endDocument()
{
return true;
}
bool startPrefixMapping(const QString &prefix, const QString &uri)
{
if(depth == 0) {
nsnames += prefix;
nsvalues += uri;
}
return true;
}
bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts)
{
if(depth == 0) {
Parser::Event *e = new Parser::Event;
QXmlAttributes a;
for(int n = 0; n < atts.length(); ++n) {
QString uri = atts.uri(n);
QString ln = atts.localName(n);
if(a.index(uri, ln) == -1)
a.append(atts.qName(n), uri, ln, atts.value(n));
}
e->setDocumentOpen(namespaceURI, localName, qName, a, nsnames, nsvalues);
nsnames.clear();
nsvalues.clear();
e->setActualString(in->lastString());
in->resetLastData();
eventList.append(e);
in->pause(true);
}
else {
QDomElement e = doc->createElementNS(namespaceURI, qName);
for(int n = 0; n < atts.length(); ++n) {
QString uri = atts.uri(n);
QString ln = atts.localName(n);
bool have;
if(!uri.isEmpty()) {
have = e.hasAttributeNS(uri, ln);
if(qt_bug_have)
have = !have;
}
else
have = e.hasAttribute(ln);
if(!have)
e.setAttributeNS(uri, atts.qName(n), atts.value(n));
}
if(depth == 1) {
elem = e;
current = e;
}
else {
current.appendChild(e);
current = e;
}
}
++depth;
return true;
}
bool endElement(const QString &namespaceURI, const QString &localName, const QString &qName)
{
--depth;
if(depth == 0) {
Parser::Event *e = new Parser::Event;
e->setDocumentClose(namespaceURI, localName, qName);
e->setActualString(in->lastString());
in->resetLastData();
eventList.append(e);
in->pause(true);
}
else {
// done with a depth 1 element?
if(depth == 1) {
Parser::Event *e = new Parser::Event;
e->setElement(elem);
e->setActualString(in->lastString());
in->resetLastData();
eventList.append(e);
in->pause(true);
elem = QDomElement();
current = QDomElement();
}
else
current = current.parentNode().toElement();
}
if(in->lastRead() == '/')
checkNeedMore();
return true;
}
bool characters(const QString &str)
{
if(depth >= 1) {
QString content = str;
if(content.isEmpty())
return true;
if(!current.isNull()) {
QDomText text = doc->createTextNode(content);
current.appendChild(text);
}
}
return true;
}
/*bool processingInstruction(const QString &target, const QString &data)
{
printf("Processing: [%s], [%s]\n", target.latin1(), data.latin1());
in->resetLastData();
return true;
}*/
void checkNeedMore()
{
// Here we will work around QXmlSimpleReader strangeness and self-closing tags.
// The problem is that endElement() is called when the '/' is read, not when
// the final '>' is read. This is a potential problem when obtaining unprocessed
// bytes from StreamInput after this event, as the '>' character will end up
// in the unprocessed chunk. To work around this, we need to advance StreamInput's
// internal byte processing, but not the xml character data. This way, the '>'
// will get processed and will no longer be in the unprocessed return, but
// QXmlSimpleReader can still read it. To do this, we call StreamInput::readNext
// with 'peek' mode.
QChar c = in->readNext(true); // peek
if(c == QXmlInputSource::EndOfData) {
needMore = true;
}
else {
// We'll assume the next char is a '>'. If it isn't, then
// QXmlSimpleReader will deal with that problem on the next
// parse. We don't need to take any action here.
needMore = false;
// there should have been a pending event
Parser::Event *e = eventList.getFirst();
if(e) {
e->setActualString(e->actualString() + '>');
in->resetLastData();
}
}
}
Parser::Event *takeEvent()
{
if(needMore)
return 0;
if(eventList.isEmpty())
return 0;
Parser::Event *e = eventList.getFirst();
eventList.removeRef(e);
in->pause(false);
return e;
}
StreamInput *in;
QDomDocument *doc;
int depth;
QStringList nsnames, nsvalues;
QDomElement elem, current;
Q3PtrList<Parser::Event> eventList;
bool needMore;
};
};
//----------------------------------------------------------------------------
// Event
//----------------------------------------------------------------------------
class Parser::Event::Private
{
public:
int type;
QString ns, ln, qn;
QXmlAttributes a;
QDomElement e;
QString str;
QStringList nsnames, nsvalues;
};
Parser::Event::Event()
{
d = 0;
}
Parser::Event::Event(const Event &from)
{
d = 0;
*this = from;
}
Parser::Event & Parser::Event::operator=(const Event &from)
{
delete d;
d = 0;
if(from.d)
d = new Private(*from.d);
return *this;
}
Parser::Event::~Event()
{
delete d;
}
bool Parser::Event::isNull() const
{
return (d ? false: true);
}
int Parser::Event::type() const
{
if(isNull())
return -1;
return d->type;
}
QString Parser::Event::nsprefix(const QString &s) const
{
QStringList::ConstIterator it = d->nsnames.begin();
QStringList::ConstIterator it2 = d->nsvalues.begin();
for(; it != d->nsnames.end(); ++it) {
if((*it) == s)
return (*it2);
++it2;
}
return QString::null;
}
QString Parser::Event::namespaceURI() const
{
return d->ns;
}
QString Parser::Event::localName() const
{
return d->ln;
}
QString Parser::Event::qName() const
{
return d->qn;
}
QXmlAttributes Parser::Event::atts() const
{
return d->a;
}
QString Parser::Event::actualString() const
{
return d->str;
}
QDomElement Parser::Event::element() const
{
return d->e;
}
void Parser::Event::setDocumentOpen(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts, const QStringList &nsnames, const QStringList &nsvalues)
{
if(!d)
d = new Private;
d->type = DocumentOpen;
d->ns = namespaceURI;
d->ln = localName;
d->qn = qName;
d->a = atts;
d->nsnames = nsnames;
d->nsvalues = nsvalues;
}
void Parser::Event::setDocumentClose(const QString &namespaceURI, const QString &localName, const QString &qName)
{
if(!d)
d = new Private;
d->type = DocumentClose;
d->ns = namespaceURI;
d->ln = localName;
d->qn = qName;
}
void Parser::Event::setElement(const QDomElement &elem)
{
if(!d)
d = new Private;
d->type = Element;
d->e = elem;
}
void Parser::Event::setError()
{
if(!d)
d = new Private;
d->type = Error;
}
void Parser::Event::setActualString(const QString &str)
{
d->str = str;
}
//----------------------------------------------------------------------------
// Parser
//----------------------------------------------------------------------------
class Parser::Private
{
public:
Private()
{
doc = 0;
in = 0;
handler = 0;
reader = 0;
reset();
}
~Private()
{
reset(false);
}
void reset(bool create=true)
{
delete reader;
delete handler;
delete in;
delete doc;
if(create) {
doc = new QDomDocument;
in = new StreamInput;
handler = new ParserHandler(in, doc);
reader = new QXmlSimpleReader;
reader->setContentHandler(handler);
// initialize the reader
in->pause(true);
reader->parse(in, true);
in->pause(false);
}
}
QDomDocument *doc;
StreamInput *in;
ParserHandler *handler;
QXmlSimpleReader *reader;
};
Parser::Parser()
{
d = new Private;
// check for evil bug in Qt <= 3.2.1
if(!qt_bug_check) {
qt_bug_check = true;
QDomElement e = d->doc->createElementNS("someuri", "somename");
if(e.hasAttributeNS("someuri", "somename"))
qt_bug_have = true;
else
qt_bug_have = false;
}
}
Parser::~Parser()
{
delete d;
}
void Parser::reset()
{
d->reset();
}
void Parser::appendData(const QByteArray &a)
{
d->in->appendData(a);
// if handler was waiting for more, give it a kick
if(d->handler->needMore)
d->handler->checkNeedMore();
}
Parser::Event Parser::readNext()
{
Event e;
if(d->handler->needMore)
return e;
Event *ep = d->handler->takeEvent();
if(!ep) {
if(!d->reader->parseContinue()) {
e.setError();
return e;
}
ep = d->handler->takeEvent();
if(!ep)
return e;
}
e = *ep;
delete ep;
return e;
}
QByteArray Parser::unprocessed() const
{
return d->in->unprocessed();
}
QString Parser::encoding() const
{
return d->in->encoding();
}

View file

@ -0,0 +1,86 @@
/*
* parser.h - parse an XMPP "document"
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef PARSER_H
#define PARSER_H
#include <qdom.h>
#include <qxml.h>
namespace XMPP
{
class Parser
{
public:
Parser();
~Parser();
class Event
{
public:
enum Type { DocumentOpen, DocumentClose, Element, Error };
Event();
Event(const Event &);
Event & operator=(const Event &);
~Event();
bool isNull() const;
int type() const;
// for document open
QString nsprefix(const QString &s=QString::null) const;
// for document open / close
QString namespaceURI() const;
QString localName() const;
QString qName() const;
QXmlAttributes atts() const;
// for element
QDomElement element() const;
// for any
QString actualString() const;
// setup
void setDocumentOpen(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts, const QStringList &nsnames, const QStringList &nsvalues);
void setDocumentClose(const QString &namespaceURI, const QString &localName, const QString &qName);
void setElement(const QDomElement &elem);
void setError();
void setActualString(const QString &);
private:
class Private;
Private *d;
};
void reset();
void appendData(const QByteArray &a);
Event readNext();
QByteArray unprocessed() const;
QString encoding() const;
private:
class Private;
Private *d;
};
}
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,361 @@
/*
* protocol.h - XMPP-Core protocol state machine
* Copyright (C) 2004 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef PROTOCOL_H
#define PROTOCOL_H
#include <qpair.h>
//Added by qt3to4:
#include <QList>
#include "xmlprotocol.h"
#include "xmpp.h"
#define NS_ETHERX "http://etherx.jabber.org/streams"
#define NS_CLIENT "jabber:client"
#define NS_SERVER "jabber:server"
#define NS_DIALBACK "jabber:server:dialback"
#define NS_STREAMS "urn:ietf:params:xml:ns:xmpp-streams"
#define NS_TLS "urn:ietf:params:xml:ns:xmpp-tls"
#define NS_SASL "urn:ietf:params:xml:ns:xmpp-sasl"
#define NS_SESSION "urn:ietf:params:xml:ns:xmpp-session"
#define NS_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas"
#define NS_BIND "urn:ietf:params:xml:ns:xmpp-bind"
#define NS_COMPRESS_FEATURE "http://jabber.org/features/compress"
#define NS_COMPRESS_PROTOCOL "http://jabber.org/protocol/compress"
namespace XMPP
{
class Version
{
public:
Version(int maj=0, int min=0);
int major, minor;
};
class StreamFeatures
{
public:
StreamFeatures();
bool tls_supported, sasl_supported, bind_supported, compress_supported;
bool tls_required;
QStringList sasl_mechs;
QStringList compression_mechs;
};
class BasicProtocol : public XmlProtocol
{
public:
// xmpp 1.0 error conditions
enum SASLCond {
Aborted,
IncorrectEncoding,
InvalidAuthzid,
InvalidMech,
MechTooWeak,
NotAuthorized,
TemporaryAuthFailure
};
enum StreamCond {
BadFormat,
BadNamespacePrefix,
Conflict,
ConnectionTimeout,
HostGone,
HostUnknown,
ImproperAddressing,
InternalServerError,
InvalidFrom,
InvalidId,
InvalidNamespace,
InvalidXml,
StreamNotAuthorized,
PolicyViolation,
RemoteConnectionFailed,
ResourceConstraint,
RestrictedXml,
SeeOtherHost,
SystemShutdown,
UndefinedCondition,
UnsupportedEncoding,
UnsupportedStanzaType,
UnsupportedVersion,
XmlNotWellFormed
};
enum BindCond {
BindBadRequest,
BindNotAllowed,
BindConflict
};
// extend the XmlProtocol enums
enum Need {
NSASLMechs = XmlProtocol::NCustom, // need SASL mechlist
NStartTLS, // need to switch on TLS layer
NCompress, // need to switch on compression layer
NSASLFirst, // need SASL first step
NSASLNext, // need SASL next step
NSASLLayer, // need to switch on SASL layer
NCustom = XmlProtocol::NCustom+10
};
enum Event {
EFeatures = XmlProtocol::ECustom, // breakpoint after features packet is received
ESASLSuccess, // breakpoint after successful sasl auth
EStanzaReady, // a stanza was received
EStanzaSent, // a stanza was sent
EReady, // stream is ready for stanza use
ECustom = XmlProtocol::ECustom+10
};
enum Error {
ErrProtocol = XmlProtocol::ErrCustom, // there was an error in the xmpp-core protocol exchange
ErrStream, // <stream:error>, see errCond, errText, and errAppSpec for details
ErrStartTLS, // server refused starttls
ErrCompress, // server refused compression
ErrAuth, // authorization error. errCond holds sasl condition (or numeric code for old-protocol)
ErrBind, // server refused resource bind
ErrCustom = XmlProtocol::ErrCustom+10
};
BasicProtocol();
~BasicProtocol();
void reset();
// for outgoing xml
QDomDocument doc;
// sasl-related
QString saslMech() const;
QByteArray saslStep() const;
void setSASLMechList(const QStringList &list);
void setSASLFirst(const QString &mech, const QByteArray &step);
void setSASLNext(const QByteArray &step);
void setSASLAuthed();
// send / recv
void sendStanza(const QDomElement &e);
void sendDirect(const QString &s);
void sendWhitespace();
QDomElement recvStanza();
// shutdown
void shutdown();
void shutdownWithError(int cond, const QString &otherHost="");
// <stream> information
QString to, from, id, lang;
Version version;
// error output
int errCond;
QString errText;
QDomElement errAppSpec;
QString otherHost;
QByteArray spare; // filled with unprocessed data on NStartTLS and NSASLLayer
bool isReady() const;
enum { TypeElement, TypeStanza, TypeDirect, TypePing };
protected:
static int stringToSASLCond(const QString &s);
static int stringToStreamCond(const QString &s);
static QString saslCondToString(int);
static QString streamCondToString(int);
void send(const QDomElement &e, bool clip=false);
void sendStreamError(int cond, const QString &text="", const QDomElement &appSpec=QDomElement());
void sendStreamError(const QString &text); // old-style
bool errorAndClose(int cond, const QString &text="", const QDomElement &appSpec=QDomElement());
bool error(int code);
void delayErrorAndClose(int cond, const QString &text="", const QDomElement &appSpec=QDomElement());
void delayError(int code);
// reimplemented
QDomElement docElement();
void handleDocOpen(const Parser::Event &pe);
bool handleError();
bool handleCloseFinished();
bool doStep(const QDomElement &e);
void itemWritten(int id, int size);
virtual QString defaultNamespace();
virtual QStringList extraNamespaces(); // stringlist: prefix,uri,prefix,uri, [...]
virtual void handleStreamOpen(const Parser::Event &pe);
virtual bool doStep2(const QDomElement &e)=0;
void setReady(bool b);
QString sasl_mech;
QStringList sasl_mechlist;
QByteArray sasl_step;
bool sasl_authed;
QDomElement stanzaToRecv;
private:
struct SASLCondEntry
{
const char *str;
int cond;
};
static SASLCondEntry saslCondTable[];
struct StreamCondEntry
{
const char *str;
int cond;
};
static StreamCondEntry streamCondTable[];
struct SendItem
{
QDomElement stanzaToSend;
QString stringToSend;
bool doWhitespace;
};
QList<SendItem> sendList;
bool doShutdown, delayedError, closeError, ready;
int stanzasPending, stanzasWritten;
void init();
void extractStreamError(const QDomElement &e);
};
class CoreProtocol : public BasicProtocol
{
public:
enum {
NPassword = NCustom, // need password for old-mode
EDBVerify = ECustom, // breakpoint after db:verify request
ErrPlain = ErrCustom // server only supports plain, but allowPlain is false locally
};
CoreProtocol();
~CoreProtocol();
void reset();
void startClientOut(const Jid &jid, bool oldOnly, bool tlsActive, bool doAuth, bool doCompression);
void startServerOut(const QString &to);
void startDialbackOut(const QString &to, const QString &from);
void startDialbackVerifyOut(const QString &to, const QString &from, const QString &id, const QString &key);
void startClientIn(const QString &id);
void startServerIn(const QString &id);
void setLang(const QString &s);
void setAllowTLS(bool b);
void setAllowBind(bool b);
void setAllowPlain(bool b); // old-mode
const Jid& jid() const;
void setPassword(const QString &s);
void setFrom(const QString &s);
void setDialbackKey(const QString &s);
// input
QString user, host;
// status
bool old;
StreamFeatures features;
//static QString xmlToString(const QDomElement &e, bool clip=false);
class DBItem
{
public:
enum { ResultRequest, ResultGrant, VerifyRequest, VerifyGrant, Validated };
int type;
Jid to, from;
QString key, id;
bool ok;
};
private:
enum Step {
Start,
Done,
SendFeatures,
GetRequest,
HandleTLS,
GetSASLResponse,
IncHandleSASLSuccess,
GetFeatures, // read features packet
HandleFeatures, // act on features, by initiating tls, sasl, or bind
GetTLSProceed, // read <proceed/> tls response
GetCompressProceed, // read <compressed/> compression response
GetSASLFirst, // perform sasl first step using provided data
GetSASLChallenge, // read server sasl challenge
GetSASLNext, // perform sasl next step using provided data
HandleSASLSuccess, // handle what must be done after reporting sasl success
GetBindResponse, // read bind response
HandleAuthGet, // send old-protocol auth-get
GetAuthGetResponse, // read auth-get response
HandleAuthSet, // send old-protocol auth-set
GetAuthSetResponse // read auth-set response
};
QList<DBItem> dbrequests, dbpending, dbvalidated;
bool server, dialback, dialback_verify;
int step;
bool digest;
bool tls_started, sasl_started, compress_started;
Jid jid_;
bool oldOnly;
bool allowPlain;
bool doTLS, doAuth, doBinding, doCompress;
QString password;
QString dialback_id, dialback_key;
QString self_from;
void init();
static int getOldErrorCode(const QDomElement &e);
bool loginComplete();
bool isValidStanza(const QDomElement &e) const;
bool grabPendingItem(const Jid &to, const Jid &from, int type, DBItem *item);
bool normalStep(const QDomElement &e);
bool dialbackStep(const QDomElement &e);
// reimplemented
bool stepAdvancesParser() const;
bool stepRequiresElement() const;
void stringSend(const QString &s);
void stringRecv(const QString &s);
QString defaultNamespace();
QStringList extraNamespaces();
void handleStreamOpen(const Parser::Event &pe);
bool doStep2(const QDomElement &e);
void elementSend(const QDomElement &e);
void elementRecv(const QDomElement &e);
};
}
#endif

View file

@ -0,0 +1,651 @@
/*
* securestream.cpp - combines a ByteStream with TLS and SASL
* Copyright (C) 2004 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*
Note: SecureStream depends on the underlying security layers to signal
plain-to-encrypted results immediately (as opposed to waiting for the
event loop) so that the user cannot add/remove security layers during
this conversion moment. QCA::TLS and QCA::SASL behave as expected,
but future layers might not.
*/
#include "securestream.h"
#include <qpointer.h>
#include <QList>
#include <qtimer.h>
//Added by qt3to4:
#include <Q3PtrList>
#ifdef USE_TLSHANDLER
#include "xmpp.h"
#endif
#include "compressionhandler.h"
//----------------------------------------------------------------------------
// LayerTracker
//----------------------------------------------------------------------------
class LayerTracker
{
public:
struct Item
{
int plain;
int encoded;
};
LayerTracker();
void reset();
void addPlain(int plain);
void specifyEncoded(int encoded, int plain);
int finished(int encoded);
int p;
QList<Item> list;
};
LayerTracker::LayerTracker()
{
p = 0;
}
void LayerTracker::reset()
{
p = 0;
list.clear();
}
void LayerTracker::addPlain(int plain)
{
p += plain;
}
void LayerTracker::specifyEncoded(int encoded, int plain)
{
// can't specify more bytes than we have
if(plain > p)
plain = p;
p -= plain;
Item i;
i.plain = plain;
i.encoded = encoded;
list += i;
}
int LayerTracker::finished(int encoded)
{
int plain = 0;
for(QList<Item>::Iterator it = list.begin(); it != list.end();) {
Item &i = *it;
// not enough?
if(encoded < i.encoded) {
i.encoded -= encoded;
break;
}
encoded -= i.encoded;
plain += i.plain;
it = list.remove(it);
}
return plain;
}
//----------------------------------------------------------------------------
// SecureStream
//----------------------------------------------------------------------------
class SecureLayer : public QObject
{
Q_OBJECT
public:
enum { TLS, SASL, TLSH, Compression };
int type;
union {
QCA::TLS *tls;
QCA::SASL *sasl;
#ifdef USE_TLSHANDLER
XMPP::TLSHandler *tlsHandler;
#endif
CompressionHandler *compressionHandler;
} p;
LayerTracker layer;
bool tls_done;
int prebytes;
SecureLayer(QCA::TLS *t)
{
type = TLS;
p.tls = t;
init();
connect(p.tls, SIGNAL(handshaken()), SLOT(tls_handshaken()));
connect(p.tls, SIGNAL(readyRead()), SLOT(tls_readyRead()));
connect(p.tls, SIGNAL(readyReadOutgoing(int)), SLOT(tls_readyReadOutgoing(int)));
connect(p.tls, SIGNAL(closed()), SLOT(tls_closed()));
connect(p.tls, SIGNAL(error(int)), SLOT(tls_error(int)));
}
SecureLayer(QCA::SASL *s)
{
type = SASL;
p.sasl = s;
init();
connect(p.sasl, SIGNAL(readyRead()), SLOT(sasl_readyRead()));
connect(p.sasl, SIGNAL(readyReadOutgoing()), SLOT(sasl_readyReadOutgoing()));
connect(p.sasl, SIGNAL(error()), SLOT(sasl_error()));
}
SecureLayer(CompressionHandler *t)
{
t->setParent(this); // automatically clean up CompressionHandler when SecureLayer is destroyed
type = Compression;
p.compressionHandler = t;
init();
connect(p.compressionHandler, SIGNAL(readyRead()), SLOT(compressionHandler_readyRead()));
connect(p.compressionHandler, SIGNAL(readyReadOutgoing()), SLOT(compressionHandler_readyReadOutgoing()));
connect(p.compressionHandler, SIGNAL(error()), SLOT(compressionHandler_error()));
}
#ifdef USE_TLSHANDLER
SecureLayer(XMPP::TLSHandler *t)
{
type = TLSH;
p.tlsHandler = t;
init();
connect(p.tlsHandler, SIGNAL(success()), SLOT(tlsHandler_success()));
connect(p.tlsHandler, SIGNAL(fail()), SLOT(tlsHandler_fail()));
connect(p.tlsHandler, SIGNAL(closed()), SLOT(tlsHandler_closed()));
connect(p.tlsHandler, SIGNAL(readyRead(const QByteArray &)), SLOT(tlsHandler_readyRead(const QByteArray &)));
connect(p.tlsHandler, SIGNAL(readyReadOutgoing(const QByteArray &, int)), SLOT(tlsHandler_readyReadOutgoing(const QByteArray &, int)));
}
#endif
void init()
{
tls_done = false;
prebytes = 0;
}
void write(const QByteArray &a)
{
layer.addPlain(a.size());
switch(type) {
case TLS: { p.tls->write(a); break; }
case SASL: { p.sasl->write(a); break; }
#ifdef USE_TLSHANDLER
case TLSH: { p.tlsHandler->write(a); break; }
#endif
case Compression: { p.compressionHandler->write(a); break; }
}
}
void writeIncoming(const QByteArray &a)
{
switch(type) {
case TLS: { p.tls->writeIncoming(a); break; }
case SASL: { p.sasl->writeIncoming(a); break; }
#ifdef USE_TLSHANDLER
case TLSH: { p.tlsHandler->writeIncoming(a); break; }
#endif
case Compression: { p.compressionHandler->writeIncoming(a); break; }
}
}
int finished(int plain)
{
int written = 0;
// deal with prebytes (bytes sent prior to this security layer)
if(prebytes > 0) {
if(prebytes >= plain) {
written += plain;
prebytes -= plain;
plain = 0;
}
else {
written += prebytes;
plain -= prebytes;
prebytes = 0;
}
}
// put remainder into the layer tracker
if(type == SASL || tls_done)
written += layer.finished(plain);
return written;
}
signals:
void tlsHandshaken();
void tlsClosed(const QByteArray &);
void readyRead(const QByteArray &);
void needWrite(const QByteArray &);
void error(int);
private slots:
void tls_handshaken()
{
tls_done = true;
tlsHandshaken();
}
void tls_readyRead()
{
QByteArray a = p.tls->read();
readyRead(a);
}
void tls_readyReadOutgoing(int plainBytes)
{
QByteArray a = p.tls->readOutgoing();
if(tls_done)
layer.specifyEncoded(a.size(), plainBytes);
needWrite(a);
}
void tls_closed()
{
QByteArray a = p.tls->readUnprocessed();
tlsClosed(a);
}
void tls_error(int x)
{
error(x);
}
void sasl_readyRead()
{
QByteArray a = p.sasl->read();
readyRead(a);
}
void sasl_readyReadOutgoing()
{
int plainBytes;
QByteArray a = p.sasl->readOutgoing(&plainBytes);
layer.specifyEncoded(a.size(), plainBytes);
needWrite(a);
}
void sasl_error()
{
error(p.sasl->errorCode());
}
void compressionHandler_readyRead()
{
QByteArray a = p.compressionHandler->read();
readyRead(a);
}
void compressionHandler_readyReadOutgoing()
{
int plainBytes;
QByteArray a = p.compressionHandler->readOutgoing(&plainBytes);
layer.specifyEncoded(a.size(), plainBytes);
needWrite(a);
}
void compressionHandler_error()
{
error(p.compressionHandler->errorCode());
}
#ifdef USE_TLSHANDLER
void tlsHandler_success()
{
tls_done = true;
tlsHandshaken();
}
void tlsHandler_fail()
{
error(0);
}
void tlsHandler_closed()
{
tlsClosed(QByteArray());
}
void tlsHandler_readyRead(const QByteArray &a)
{
readyRead(a);
}
void tlsHandler_readyReadOutgoing(const QByteArray &a, int plainBytes)
{
if(tls_done)
layer.specifyEncoded(a.size(), plainBytes);
needWrite(a);
}
#endif
};
#include "securestream.moc"
class SecureStream::Private
{
public:
ByteStream *bs;
Q3PtrList<SecureLayer> layers;
int pending;
int errorCode;
bool active;
bool topInProgress;
bool haveTLS() const
{
Q3PtrListIterator<SecureLayer> it(layers);
for(SecureLayer *s; (s = it.current()); ++it) {
if(s->type == SecureLayer::TLS
#ifdef USE_TLSHANDLER
|| s->type == SecureLayer::TLSH
#endif
) {
return true;
}
}
return false;
}
bool haveSASL() const
{
Q3PtrListIterator<SecureLayer> it(layers);
for(SecureLayer *s; (s = it.current()); ++it) {
if(s->type == SecureLayer::SASL)
return true;
}
return false;
}
bool haveCompress() const
{
Q3PtrListIterator<SecureLayer> it(layers);
for(SecureLayer *s; (s = it.current()); ++it) {
if(s->type == SecureLayer::Compression)
return true;
}
return false;
}
};
SecureStream::SecureStream(ByteStream *s)
:ByteStream(0)
{
d = new Private;
d->bs = s;
connect(d->bs, SIGNAL(readyRead()), SLOT(bs_readyRead()));
connect(d->bs, SIGNAL(bytesWritten(int)), SLOT(bs_bytesWritten(int)));
d->layers.setAutoDelete(true);
d->pending = 0;
d->active = true;
d->topInProgress = false;
}
SecureStream::~SecureStream()
{
delete d;
}
void SecureStream::linkLayer(QObject *s)
{
connect(s, SIGNAL(tlsHandshaken()), SLOT(layer_tlsHandshaken()));
connect(s, SIGNAL(tlsClosed(const QByteArray &)), SLOT(layer_tlsClosed(const QByteArray &)));
connect(s, SIGNAL(readyRead(const QByteArray &)), SLOT(layer_readyRead(const QByteArray &)));
connect(s, SIGNAL(needWrite(const QByteArray &)), SLOT(layer_needWrite(const QByteArray &)));
connect(s, SIGNAL(error(int)), SLOT(layer_error(int)));
}
int SecureStream::calcPrebytes() const
{
int x = 0;
Q3PtrListIterator<SecureLayer> it(d->layers);
for(SecureLayer *s; (s = it.current()); ++it)
x += s->prebytes;
return (d->pending - x);
}
void SecureStream::startTLSClient(QCA::TLS *t, const QByteArray &spare)
{
if(!d->active || d->topInProgress || d->haveTLS())
return;
SecureLayer *s = new SecureLayer(t);
s->prebytes = calcPrebytes();
linkLayer(s);
d->layers.append(s);
d->topInProgress = true;
insertData(spare);
}
void SecureStream::startTLSServer(QCA::TLS *t, const QByteArray &spare)
{
if(!d->active || d->topInProgress || d->haveTLS())
return;
SecureLayer *s = new SecureLayer(t);
s->prebytes = calcPrebytes();
linkLayer(s);
d->layers.append(s);
d->topInProgress = true;
insertData(spare);
}
void SecureStream::setLayerCompress(const QByteArray& spare)
{
if(!d->active || d->topInProgress || d->haveCompress())
return;
SecureLayer *s = new SecureLayer(new CompressionHandler());
s->prebytes = calcPrebytes();
linkLayer(s);
d->layers.append(s);
insertData(spare);
}
void SecureStream::setLayerSASL(QCA::SASL *sasl, const QByteArray &spare)
{
if(!d->active || d->topInProgress || d->haveSASL())
return;
SecureLayer *s = new SecureLayer(sasl);
s->prebytes = calcPrebytes();
linkLayer(s);
d->layers.append(s);
insertData(spare);
}
#ifdef USE_TLSHANDLER
void SecureStream::startTLSClient(XMPP::TLSHandler *t, const QString &server, const QByteArray &spare)
{
if(!d->active || d->topInProgress || d->haveTLS())
return;
SecureLayer *s = new SecureLayer(t);
s->prebytes = calcPrebytes();
linkLayer(s);
d->layers.append(s);
d->topInProgress = true;
// unlike QCA::TLS, XMPP::TLSHandler has no return value
s->p.tlsHandler->startClient(server);
insertData(spare);
}
#endif
void SecureStream::closeTLS()
{
SecureLayer *s = d->layers.getLast();
if(s) {
if(s->type == SecureLayer::TLS)
s->p.tls->close();
}
}
int SecureStream::errorCode() const
{
return d->errorCode;
}
bool SecureStream::isOpen() const
{
return d->active;
}
void SecureStream::write(const QByteArray &a)
{
if(!isOpen())
return;
d->pending += a.size();
// send to the last layer
SecureLayer *s = d->layers.getLast();
if(s)
s->write(a);
else
writeRawData(a);
}
int SecureStream::bytesToWrite() const
{
return d->pending;
}
void SecureStream::bs_readyRead()
{
QByteArray a = d->bs->read();
// send to the first layer
SecureLayer *s = d->layers.getFirst();
if(s) {
s->writeIncoming(a);
}
else {
incomingData(a);
}
}
void SecureStream::bs_bytesWritten(int bytes)
{
Q3PtrListIterator<SecureLayer> it(d->layers);
for(SecureLayer *s; (s = it.current()); ++it)
bytes = s->finished(bytes);
if(bytes > 0) {
d->pending -= bytes;
bytesWritten(bytes);
}
}
void SecureStream::layer_tlsHandshaken()
{
d->topInProgress = false;
tlsHandshaken();
}
void SecureStream::layer_tlsClosed(const QByteArray &)
{
d->active = false;
d->layers.clear();
tlsClosed();
}
void SecureStream::layer_readyRead(const QByteArray &a)
{
SecureLayer *s = (SecureLayer *)sender();
Q3PtrListIterator<SecureLayer> it(d->layers);
while(it.current() != s)
++it;
// pass upwards
++it;
s = it.current();
if(s)
s->writeIncoming(a);
else
incomingData(a);
}
void SecureStream::layer_needWrite(const QByteArray &a)
{
SecureLayer *s = (SecureLayer *)sender();
Q3PtrListIterator<SecureLayer> it(d->layers);
while(it.current() != s)
++it;
// pass downwards
--it;
s = it.current();
if(s)
s->write(a);
else
writeRawData(a);
}
void SecureStream::layer_error(int x)
{
SecureLayer *s = (SecureLayer *)sender();
int type = s->type;
d->errorCode = x;
d->active = false;
d->layers.clear();
if(type == SecureLayer::TLS)
error(ErrTLS);
else if(type == SecureLayer::SASL)
error(ErrSASL);
#ifdef USE_TLSHANDLER
else if(type == SecureLayer::TLSH)
error(ErrTLS);
#endif
}
void SecureStream::insertData(const QByteArray &a)
{
if(!a.isEmpty()) {
SecureLayer *s = d->layers.getLast();
if(s)
s->writeIncoming(a);
else
incomingData(a);
}
}
void SecureStream::writeRawData(const QByteArray &a)
{
d->bs->write(a);
}
void SecureStream::incomingData(const QByteArray &a)
{
appendRead(a);
if(bytesAvailable())
readyRead();
}

View file

@ -0,0 +1,87 @@
/*
* securestream.h - combines a ByteStream with TLS and SASL
* Copyright (C) 2004 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef SECURESTREAM_H
#define SECURESTREAM_H
#include <qca.h>
#include "bytestream.h"
#define USE_TLSHANDLER
#ifdef USE_TLSHANDLER
namespace XMPP
{
class TLSHandler;
}
#endif
class CompressionHandler;
class SecureStream : public ByteStream
{
Q_OBJECT
public:
enum Error { ErrTLS = ErrCustom, ErrSASL };
SecureStream(ByteStream *s);
~SecureStream();
void startTLSClient(QCA::TLS *t, const QByteArray &spare=QByteArray());
void startTLSServer(QCA::TLS *t, const QByteArray &spare=QByteArray());
void setLayerCompress(const QByteArray &spare=QByteArray());
void setLayerSASL(QCA::SASL *s, const QByteArray &spare=QByteArray());
#ifdef USE_TLSHANDLER
void startTLSClient(XMPP::TLSHandler *t, const QString &server, const QByteArray &spare=QByteArray());
#endif
void closeTLS();
int errorCode() const;
// reimplemented
bool isOpen() const;
void write(const QByteArray &);
int bytesToWrite() const;
signals:
void tlsHandshaken();
void tlsClosed();
private slots:
void bs_readyRead();
void bs_bytesWritten(int);
void layer_tlsHandshaken();
void layer_tlsClosed(const QByteArray &);
void layer_readyRead(const QByteArray &);
void layer_needWrite(const QByteArray &);
void layer_error(int);
private:
void linkLayer(QObject *);
int calcPrebytes() const;
void insertData(const QByteArray &a);
void writeRawData(const QByteArray &a);
void incomingData(const QByteArray &a);
class Private;
Private *d;
};
#endif

View file

@ -0,0 +1,676 @@
/*
* simplesasl.cpp - Simple SASL implementation
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "simplesasl.h"
#include <qhostaddress.h>
#include <qstringlist.h>
#include <q3ptrlist.h>
#include <QList>
#include <qca.h>
#include <Q3CString>
#include <stdlib.h>
#include <QtCrypto>
#include <QDebug>
#ifdef YAPSI
#include <QUrl>
#include <QVariantMap>
#include <QDateTime>
#include "yaonline.h"
#endif
#define SIMPLESASL_PLAIN
namespace XMPP {
struct Prop
{
Q3CString var, val;
};
class PropList : public QList<Prop>
{
public:
PropList() : QList<Prop>()
{
}
void set(const Q3CString &var, const Q3CString &val)
{
Prop p;
p.var = var;
p.val = val;
append(p);
}
Q3CString get(const Q3CString &var)
{
for(ConstIterator it = begin(); it != end(); ++it) {
if((*it).var == var)
return (*it).val;
}
return Q3CString();
}
Q3CString toString() const
{
Q3CString str;
bool first = true;
for(ConstIterator it = begin(); it != end(); ++it) {
if(!first)
str += ',';
if ((*it).var == "realm" || (*it).var == "nonce" || (*it).var == "username" || (*it).var == "cnonce" || (*it).var == "digest-uri" || (*it).var == "authzid")
str += (*it).var + "=\"" + (*it).val + '\"';
else
str += (*it).var + "=" + (*it).val;
first = false;
}
return str;
}
bool fromString(const QByteArray &str)
{
PropList list;
int at = 0;
while(1) {
while (at < str.length() && (str[at] == ',' || str[at] == ' ' || str[at] == '\t'))
++at;
int n = str.find('=', at);
if(n == -1)
break;
Q3CString var, val;
var = str.mid(at, n-at);
at = n + 1;
if(str[at] == '\"') {
++at;
n = str.find('\"', at);
if(n == -1)
break;
val = str.mid(at, n-at);
at = n + 1;
}
else {
n = at;
while (n < str.length() && str[n] != ',' && str[n] != ' ' && str[n] != '\t')
++n;
val = str.mid(at, n-at);
at = n;
}
Prop prop;
prop.var = var;
if (var == "qop" || var == "cipher") {
int a = 0;
while (a < val.length()) {
while (a < val.length() && (val[a] == ',' || val[a] == ' ' || val[a] == '\t'))
++a;
if (a == val.length())
break;
n = a+1;
while (n < val.length() && val[n] != ',' && val[n] != ' ' && val[n] != '\t')
++n;
prop.val = val.mid(a, n-a);
list.append(prop);
a = n+1;
}
}
else {
prop.val = val;
list.append(prop);
}
if(at >= str.size() - 1 || (str[at] != ',' && str[at] != ' ' && str[at] != '\t'))
break;
}
// integrity check
if(list.varCount("nonce") != 1)
return false;
if(list.varCount("algorithm") != 1)
return false;
*this = list;
return true;
}
int varCount(const Q3CString &var)
{
int n = 0;
for(ConstIterator it = begin(); it != end(); ++it) {
if((*it).var == var)
++n;
}
return n;
}
QStringList getValues(const Q3CString &var)
{
QStringList list;
for(ConstIterator it = begin(); it != end(); ++it) {
if((*it).var == var)
list += (*it).val;
}
return list;
}
};
class SimpleSASLContext : public QCA::SASLContext
{
Q_OBJECT
public:
class ParamsMutable
{
public:
/**
User is held
*/
bool user;
/**
Authorization ID is held
*/
bool authzid;
/**
Password is held
*/
bool pass;
/**
Realm is held
*/
bool realm;
};
// core props
QString service, host;
// state
int step;
bool capable;
bool allow_plain;
#ifdef YAPSI
bool allow_xFacebookPlatform;
#endif
QByteArray out_buf, in_buf;
QString mechanism_;
QString out_mech;
ParamsMutable need;
ParamsMutable have;
QString user, authz, realm;
QCA::SecureArray pass;
Result result_;
QCA::SASL::AuthCondition authCondition_;
QByteArray result_to_net_, result_to_app_;
int encoded_;
SimpleSASLContext(QCA::Provider* p) : QCA::SASLContext(p)
{
reset();
}
~SimpleSASLContext()
{
reset();
}
void reset()
{
resetState();
capable = true;
allow_plain = false;
#ifdef YAPSI
allow_xFacebookPlatform = false;
#endif
need.user = false;
need.authzid = false;
need.pass = false;
need.realm = false;
have.user = false;
have.authzid = false;
have.pass = false;
have.realm = false;
user = QString();
authz = QString();
pass = QCA::SecureArray();
realm = QString();
}
void resetState()
{
out_mech = QString();
out_buf.resize(0);
authCondition_ = QCA::SASL::AuthFail;
}
virtual void setConstraints(QCA::SASL::AuthFlags flags, int ssfMin, int) {
if(flags & (QCA::SASL::RequireForwardSecrecy | QCA::SASL::RequirePassCredentials | QCA::SASL::RequireMutualAuth) || ssfMin > 0)
capable = false;
else
capable = true;
allow_plain = flags & QCA::SASL::AllowPlain;
#ifdef YAPSI
allow_xFacebookPlatform = flags & QCA::SASL::AllowXFacebookPlatform;
#endif
}
virtual void setup(const QString& _service, const QString& _host, const QCA::SASLContext::HostPort*, const QCA::SASLContext::HostPort*, const QString&, int) {
service = _service;
host = _host;
}
virtual void startClient(const QStringList &mechlist, bool allowClientSendFirst) {
Q_UNUSED(allowClientSendFirst);
mechanism_ = QString();
foreach(QString mech, mechlist) {
#ifdef YAPSI
if (mech == "X-FACEBOOK-PLATFORM" && allow_xFacebookPlatform) {
mechanism_ = "X-FACEBOOK-PLATFORM";
break;
}
#endif
if (mech == "DIGEST-MD5") {
mechanism_ = "DIGEST-MD5";
break;
}
#ifdef SIMPLESASL_PLAIN
if (mech == "PLAIN" && allow_plain)
mechanism_ = "PLAIN";
#endif
}
if(!capable || mechanism_.isEmpty()) {
result_ = Error;
authCondition_ = QCA::SASL::NoMechanism;
if (!capable)
qWarning("simplesasl.cpp: Not enough capabilities");
if (mechanism_.isEmpty())
qWarning("simplesasl.cpp: No mechanism available");
QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
return;
}
resetState();
result_ = Continue;
step = 0;
tryAgain();
}
virtual void nextStep(const QByteArray &from_net) {
in_buf = from_net;
tryAgain();
}
virtual void tryAgain() {
// All exits of the method must emit the ready signal
// so all exits go through a goto ready;
if(step == 0) {
out_mech = mechanism_;
#ifdef SIMPLESASL_PLAIN
// PLAIN
if (out_mech == "PLAIN") {
// First, check if we have everything
if(need.user || need.pass) {
qWarning("simplesasl.cpp: Did not receive necessary auth parameters");
result_ = Error;
goto ready;
}
if(!have.user)
need.user = true;
if(!have.pass)
need.pass = true;
if(need.user || need.pass) {
result_ = Params;
goto ready;
}
// Continue with authentication
QByteArray plain;
if (!authz.isEmpty())
plain += authz.utf8();
plain += '\0' + user.toUtf8() + '\0' + pass.toByteArray();
out_buf.resize(plain.length());
memcpy(out_buf.data(), plain.data(), out_buf.size());
}
#endif
++step;
if (out_mech == "PLAIN")
result_ = Success;
else
result_ = Continue;
}
else if(step == 1) {
#ifdef YAPSI
if (out_mech == "X-FACEBOOK-PLATFORM") {
QString fakeUrl = "http://facebook.com/?" + QString(in_buf);
QUrl url = QUrl(fakeUrl, QUrl::TolerantMode);
QVariantMap map;
map["method"] = url.queryItemValue("method");
map["nonce"] = url.queryItemValue("nonce");
map["call_id"] = QDateTime::currentDateTime().toTime_t();
xFacebookPlatformLogin(map);
return;
}
#endif
// if we still need params, then the app has failed us!
if(need.user || need.authzid || need.pass || need.realm) {
qWarning("simplesasl.cpp: Did not receive necessary auth parameters");
result_ = Error;
goto ready;
}
// see if some params are needed
if(!have.user)
need.user = true;
//if(!have.authzid)
// need.authzid = true;
if(!have.pass)
need.pass = true;
if(need.user || need.authzid || need.pass) {
result_ = Params;
goto ready;
}
// get props
QByteArray cs(in_buf);
PropList in;
if(!in.fromString(cs)) {
authCondition_ = QCA::SASL::BadProtocol;
result_ = Error;
goto ready;
}
//qDebug() << (QString("simplesasl.cpp: IN: %1").arg(QString(in.toString())));
// make a cnonce
QByteArray a(32);
for(int n = 0; n < (int)a.size(); ++n)
a[n] = (char)(256.0*rand()/(RAND_MAX+1.0));
Q3CString cnonce = QCA::Base64().arrayToString(a).toLatin1();
// make other variables
if (realm.isEmpty())
realm = QString::fromUtf8(in.get("realm"));
Q3CString nonce = in.get("nonce");
Q3CString nc = "00000001";
Q3CString uri = service.utf8() + '/' + host.utf8();
Q3CString qop = "auth";
// build 'response'
Q3CString X = user.utf8() + ':' + realm.utf8() + ':' + Q3CString(pass.toByteArray());
QByteArray Y = QCA::Hash("md5").hash(X).toByteArray();
QByteArray tmp = ':' + nonce + ':' + cnonce;
if (!authz.isEmpty())
tmp += ':' + authz.utf8();
//qDebug() << (QString(tmp));
QByteArray A1(Y + tmp);
QByteArray A2 = QByteArray("AUTHENTICATE:") + uri;
Q3CString HA1 = QCA::Hash("md5").hashToString(A1).toLatin1();
Q3CString HA2 = QCA::Hash("md5").hashToString(A2).toLatin1();
Q3CString KD = HA1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + HA2;
Q3CString Z = QCA::Hash("md5").hashToString(KD).toLatin1();
//qDebug() << (QString("simplesasl.cpp: A1 = %1").arg(QString(A1)).toAscii());
//qDebug() << (QString("simplesasl.cpp: A2 = %1").arg(QString(A2)).toAscii());
//qDebug() << (QString("simplesasl.cpp: KD = %1").arg(QString(KD)).toAscii());
// build output
PropList out;
out.set("username", user.utf8());
if (!realm.isEmpty())
out.set("realm", realm.utf8());
out.set("nonce", nonce);
out.set("cnonce", cnonce);
out.set("nc", nc);
//out.set("serv-type", service.utf8());
//out.set("host", host.utf8());
out.set("digest-uri", uri);
out.set("qop", qop);
out.set("response", Z);
out.set("charset", "utf-8");
if (!authz.isEmpty())
out.set("authzid", authz.utf8());
QByteArray s(out.toString());
//qDebug() << (QString("OUT: %1").arg(QString(out.toString())));
// done
out_buf.resize(s.length());
memcpy(out_buf.data(), s.data(), out_buf.size());
++step;
result_ = Continue;
}
/*else if (step == 2) {
//Commenting this out is Justin's fix for updated QCA.
out_buf.resize(0);
result_ = Continue;
++step;
}*/
else {
out_buf.resize(0);
result_ = Success;
}
ready:
QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
}
virtual void update(const QByteArray &from_net, const QByteArray &from_app) {
result_to_app_ = from_net;
result_to_net_ = from_app;
encoded_ = from_app.size();
result_ = Success;
QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
}
virtual bool waitForResultsReady(int msecs) {
// TODO: for now, all operations block anyway
Q_UNUSED(msecs);
return true;
}
virtual Result result() const {
return result_;
}
virtual QStringList mechlist() const {
return QStringList();
}
virtual QString mech() const {
return out_mech;
}
virtual bool haveClientInit() const {
return out_mech == "PLAIN";
}
virtual QByteArray stepData() const {
return out_buf;
}
virtual QByteArray to_net() {
return result_to_net_;
}
virtual int encoded() const {
return encoded_;
}
virtual QByteArray to_app() {
return result_to_app_;
}
virtual int ssf() const {
return 0;
}
virtual QCA::SASL::AuthCondition authCondition() const {
return authCondition_;
}
virtual QCA::SASL::Params clientParams() const {
return QCA::SASL::Params(need.user, need.authzid, need.pass, need.realm);
}
virtual void setClientParams(const QString *_user, const QString *_authzid, const QCA::SecureArray *_pass, const QString *_realm) {
if(_user) {
user = *_user;
need.user = false;
have.user = true;
}
if(_authzid) {
authz = *_authzid;
need.authzid = false;
have.authzid = true;
}
if(_pass) {
pass = *_pass;
need.pass = false;
have.pass = true;
}
if(_realm) {
realm = *_realm;
need.realm = false;
have.realm = true;
}
}
virtual QStringList realmlist() const
{
// TODO
return QStringList();
}
virtual QString username() const {
return QString();
}
virtual QString authzid() const {
return QString();
}
virtual QCA::Provider::Context* clone() const {
SimpleSASLContext* s = new SimpleSASLContext(provider());
// TODO: Copy all the members
return s;
}
virtual void startServer(const QString &, bool) {
result_ = QCA::SASLContext::Error;
QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
}
virtual void serverFirstStep(const QString &, const QByteArray *) {
result_ = QCA::SASLContext::Error;
QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
}
public slots:
#ifdef YAPSI
void xFacebookPlatformLogin(QVariantMap map) {
#ifdef YAPSI_ACTIVEX_SERVER
YaOnlineHelper::instance()->xFacebookPlatformLogin(map, this, "xFacebookPlatformDataReady");
#else
QString apiKey = "API_KEY";
QString sessionKey = "SESSION_KEY";
QString secret = "SECRET";
map["api_key"] = apiKey;
map["session_key"] = sessionKey;
map["v"] = "1.0";
QStringList signingList;
QStringList resultList;
QMapIterator<QString, QVariant> it(map);
while (it.hasNext()) {
it.next();
signingList += QString("%1=%2")
.arg(it.key())
.arg(it.value().toString());
resultList += QString("%1=%2")
.arg(it.key())
.arg(QUrl::toPercentEncoding(it.value().toString()).constData());
}
QString signingData = signingList.join("") + secret;
QString resultData = resultList.join("&");
QString signature = QCA::Hash("md5").hashToString(signingData.toUtf8());
resultData += "&sig=" + signature;
// qWarning("signingData = '%s'", qPrintable(signingData));
// qWarning("resultData = '%s'", qPrintable(resultData));
xFacebookPlatformDataReady(resultData);
#endif
}
void xFacebookPlatformDataReady(const QString& data) {
Q_ASSERT(out_mech == "X-FACEBOOK-PLATFORM");
out_buf = data.toUtf8();
++step;
result_ = Success;
QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
}
#endif
};
class QCASimpleSASL : public QCA::Provider
{
public:
QCASimpleSASL() {}
~QCASimpleSASL() {}
void init()
{
}
QString name() const {
return "simplesasl";
}
QStringList features() const {
return QStringList("sasl");
}
QCA::Provider::Context* createContext(const QString& cap)
{
if(cap == "sasl")
return new SimpleSASLContext(this);
return 0;
}
int qcaVersion() const
{
return QCA_VERSION;
}
};
QCA::Provider *createProviderSimpleSASL()
{
return (new QCASimpleSASL);
}
}
#include "simplesasl.moc"

View file

@ -0,0 +1,33 @@
/*
* simplesasl.h - Simple SASL implementation
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef SIMPLESASL_H
#define SIMPLESASL_H
namespace QCA {
class Provider;
}
namespace XMPP
{
QCA::Provider* createProviderSimpleSASL();
}
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,20 @@
#ifndef TESTDEBUG_H
#define TESTDEBUG_H
#include <qdom.h>
class TD
{
public:
TD();
~TD();
static void msg(const QString &);
static void outgoingTag(const QString &);
static void incomingTag(const QString &);
static void outgoingXml(const QDomElement &);
static void incomingXml(const QDomElement &);
};
#endif

View file

@ -0,0 +1,171 @@
/*
* tlshandler.cpp - abstract wrapper for TLS
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "xmpp.h"
#include <qtimer.h>
#include "qca.h"
using namespace XMPP;
//----------------------------------------------------------------------------
// TLSHandler
//----------------------------------------------------------------------------
TLSHandler::TLSHandler(QObject *parent)
:QObject(parent)
{
}
TLSHandler::~TLSHandler()
{
}
//----------------------------------------------------------------------------
// QCATLSHandler
//----------------------------------------------------------------------------
class QCATLSHandler::Private
{
public:
QCA::TLS *tls;
int state, err;
QString host;
bool internalHostMatch;
};
QCATLSHandler::QCATLSHandler(QCA::TLS *parent)
:TLSHandler(parent)
{
d = new Private;
d->tls = parent;
connect(d->tls, SIGNAL(handshaken()), SLOT(tls_handshaken()));
connect(d->tls, SIGNAL(readyRead()), SLOT(tls_readyRead()));
connect(d->tls, SIGNAL(readyReadOutgoing()), SLOT(tls_readyReadOutgoing()));
connect(d->tls, SIGNAL(closed()), SLOT(tls_closed()));
connect(d->tls, SIGNAL(error()), SLOT(tls_error()));
d->state = 0;
d->err = -1;
d->internalHostMatch = false;
}
QCATLSHandler::~QCATLSHandler()
{
delete d;
}
void QCATLSHandler::setXMPPCertCheck(bool enable)
{
d->internalHostMatch = enable;
}
bool QCATLSHandler::XMPPCertCheck()
{
return d->internalHostMatch;
}
bool QCATLSHandler::certMatchesHostname()
{
if (!d->internalHostMatch) return false;
QCA::CertificateChain peerCert = d->tls->peerCertificateChain();
if (peerCert.primary().matchesHostName(d->host))
return true;
Jid host(d->host);
foreach( const QString &idOnXmppAddr, peerCert.primary().subjectInfo().values(QCA::XMPP) ) {
if (host.compare(Jid(idOnXmppAddr)))
return true;
}
return false;
}
QCA::TLS *QCATLSHandler::tls() const
{
return d->tls;
}
int QCATLSHandler::tlsError() const
{
return d->err;
}
void QCATLSHandler::reset()
{
d->tls->reset();
d->state = 0;
}
void QCATLSHandler::startClient(const QString &host)
{
d->state = 0;
d->err = -1;
if (d->internalHostMatch) d->host = host;
d->tls->startClient(d->internalHostMatch ? QString() : host);
}
void QCATLSHandler::write(const QByteArray &a)
{
d->tls->write(a);
}
void QCATLSHandler::writeIncoming(const QByteArray &a)
{
d->tls->writeIncoming(a);
}
void QCATLSHandler::continueAfterHandshake()
{
if(d->state == 2) {
d->tls->continueAfterStep();
success();
d->state = 3;
}
}
void QCATLSHandler::tls_handshaken()
{
d->state = 2;
tlsHandshaken();
}
void QCATLSHandler::tls_readyRead()
{
readyRead(d->tls->read());
}
void QCATLSHandler::tls_readyReadOutgoing()
{
int plainBytes;
QByteArray buf = d->tls->readOutgoing(&plainBytes);
readyReadOutgoing(buf, plainBytes);
}
void QCATLSHandler::tls_closed()
{
closed();
}
void QCATLSHandler::tls_error()
{
d->err = d->tls->errorCode();
d->state = 0;
fail();
}

View file

@ -0,0 +1,646 @@
/*
* xmlprotocol.cpp - state machine for 'jabber-like' protocols
* Copyright (C) 2004 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "xmlprotocol.h"
#include "bytestream.h"
//Added by qt3to4:
#include <QList>
#include <QTextStream>
#include <Q3CString>
#include <QApplication>
using namespace XMPP;
// stripExtraNS
//
// This function removes namespace information from various nodes for
// display purposes only (the element is pretty much useless for processing
// after this). We do this because QXml is a bit overzealous about outputting
// redundant namespaces.
static QDomElement stripExtraNS(const QDomElement &e)
{
// find closest parent with a namespace
QDomNode par = e.parentNode();
while(!par.isNull() && par.namespaceURI().isNull())
par = par.parentNode();
bool noShowNS = false;
if(!par.isNull() && par.namespaceURI() == e.namespaceURI())
noShowNS = true;
// build qName (prefix:localName)
QString qName;
if(!e.prefix().isEmpty())
qName = e.prefix() + ':' + e.localName();
else
qName = e.tagName();
QDomElement i;
int x;
if(noShowNS)
i = e.ownerDocument().createElement(qName);
else
i = e.ownerDocument().createElementNS(e.namespaceURI(), qName);
// copy attributes
QDomNamedNodeMap al = e.attributes();
for(x = 0; x < al.count(); ++x) {
QDomAttr a = al.item(x).cloneNode().toAttr();
// don't show xml namespace
if(a.namespaceURI() == NS_XML)
i.setAttribute(QString("xml:") + a.name(), a.value());
else
i.setAttributeNodeNS(a);
}
// copy children
QDomNodeList nl = e.childNodes();
for(x = 0; x < nl.count(); ++x) {
QDomNode n = nl.item(x);
if(n.isElement())
i.appendChild(stripExtraNS(n.toElement()));
else
i.appendChild(n.cloneNode());
}
return i;
}
// xmlToString
//
// This function converts a QDomElement into a QString, using stripExtraNS
// to make it pretty.
static QString xmlToString(const QDomElement &e, const QString &fakeNS, const QString &fakeQName, bool clip)
{
QDomElement i = e.cloneNode().toElement();
// It seems QDom can only have one namespace attribute at a time (see docElement 'HACK').
// Fortunately we only need one kind depending on the input, so it is specified here.
QDomElement fake = e.ownerDocument().createElementNS(fakeNS, fakeQName);
fake.appendChild(i);
fake = stripExtraNS(fake);
QString out;
{
QTextStream ts(&out, QIODevice::WriteOnly);
fake.firstChild().save(ts, 0);
}
// 'clip' means to remove any unwanted (and unneeded) characters, such as a trailing newline
if(clip) {
int n = out.findRev('>');
out.truncate(n+1);
}
return out;
}
// createRootXmlTags
//
// This function creates three QStrings, one being an <?xml .. ?> processing
// instruction, and the others being the opening and closing tags of an
// element, <foo> and </foo>. This basically allows us to get the raw XML
// text needed to open/close an XML stream, without resorting to generating
// the XML ourselves. This function uses QDom to do the generation, which
// ensures proper encoding and entity output.
static void createRootXmlTags(const QDomElement &root, QString *xmlHeader, QString *tagOpen, QString *tagClose)
{
QDomElement e = root.cloneNode(false).toElement();
// insert a dummy element to ensure open and closing tags are generated
QDomElement dummy = e.ownerDocument().createElement("dummy");
e.appendChild(dummy);
// convert to xml->text
QString str;
{
QTextStream ts(&str, QIODevice::WriteOnly);
e.save(ts, 0);
}
// parse the tags out
int n = str.find('<');
int n2 = str.find('>', n);
++n2;
*tagOpen = str.mid(n, n2-n);
n2 = str.findRev('>');
n = str.findRev('<');
++n2;
*tagClose = str.mid(n, n2-n);
// generate a nice xml processing header
*xmlHeader = "<?xml version=\"1.0\"?>";
}
// w3c xml spec:
// [2] Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
static inline bool validChar(const quint32 ch)
{
return ch == 0x9 || ch == 0xA || ch == 0xD
|| (ch >= 0x20 && ch <= 0xD7FF)
|| (ch >= 0xE000 && ch <= 0xFFFD)
|| (ch >= 0x10000 && ch <= 0x10FFFF);
}
static inline bool lowSurrogate(const quint32 ch)
{
return ch >= 0xDC00 && ch <= 0xDFFF;
}
static inline bool highSurrogate(const quint32 ch)
{
return ch >= 0xD800 && ch <= 0xDBFF;
}
// force encoding of '>'. this function is needed for XMPP-Core, which
// requires the '>' character to be encoded as "&gt;" even though this is
// not required by the XML spec.
// Also remove chars that are ouside the allowed range for XML (see validChar)
// and invalid surrogate pairs
static QString sanitizeForStream(const QString &in)
{
QString out;
bool intag = false;
bool inquote = false;
QChar quotechar;
int inlength = in.length();
for(int n = 0; n < inlength; ++n)
{
QChar c = in[n];
bool escape = false;
if(c == '<')
{
intag = true;
}
else if(c == '>')
{
if(inquote) {
escape = true;
} else if(!intag) {
escape = true;
} else {
intag = false;
}
}
else if(c == '\'' || c == '\"')
{
if(intag)
{
if(!inquote)
{
inquote = true;
quotechar = c;
}
else
{
if(quotechar == c) {
inquote = false;
}
}
}
}
if(escape) {
out += "&gt;";
} else {
// don't silently drop invalid chars in element or attribute names,
// because that's something that should not happen.
if (intag && (!inquote)) {
out += c;
} else if (validChar(c.unicode())) {
out += c;
} else if (highSurrogate(c.unicode()) && (n+1 < inlength) && lowSurrogate(in[n+1].unicode())) {
//uint unicode = (c.unicode() & 0x3FF) << 10 | in[n+1].unicode() & 0x3FF + 0x10000;
// we don't need to recheck this, because 0x10000 <= unicode <= 0x100000 is always true
out += c;
out += in[n+1];
++n;
} else {
qDebug("Dropping invalid XML char U+%04x",c.unicode());
}
}
}
return out;
}
//----------------------------------------------------------------------------
// Protocol
//----------------------------------------------------------------------------
XmlProtocol::TransferItem::TransferItem()
{
}
XmlProtocol::TransferItem::TransferItem(const QString &_str, bool sent, bool external)
{
isString = true;
isSent = sent;
isExternal = external;
str = _str;
}
XmlProtocol::TransferItem::TransferItem(const QDomElement &_elem, bool sent, bool external)
{
isString = false;
isSent = sent;
isExternal = external;
elem = _elem;
}
XmlProtocol::XmlProtocol()
// : QObject(qApp) // ONLINE-2555
: QObject()
{
init();
}
XmlProtocol::~XmlProtocol()
{
}
void XmlProtocol::init()
{
incoming = false;
peerClosed = false;
closeWritten = false;
}
void XmlProtocol::reset()
{
init();
elem = QDomElement();
elemDoc = QDomDocument();
tagOpen = QString();
tagClose = QString();
xml.reset();
outData.resize(0);
trackQueue.clear();
transferItemList.clear();
}
void XmlProtocol::addIncomingData(const QByteArray &a)
{
xml.appendData(a);
}
QByteArray XmlProtocol::takeOutgoingData()
{
QByteArray a = outData;
outData.resize(0);
return a;
}
void XmlProtocol::outgoingDataWritten(int bytes)
{
for(QList<TrackItem>::Iterator it = trackQueue.begin(); it != trackQueue.end();) {
TrackItem &i = *it;
// enough bytes?
if(bytes < i.size) {
i.size -= bytes;
break;
}
int type = i.type;
int id = i.id;
int size = i.size;
bytes -= i.size;
it = trackQueue.remove(it);
if(type == TrackItem::Raw) {
// do nothing
}
else if(type == TrackItem::Close) {
closeWritten = true;
}
else if(type == TrackItem::Custom) {
itemWritten(id, size);
}
}
}
bool XmlProtocol::processStep()
{
Parser::Event pe;
notify = 0;
transferItemList.clear();
if(state != Closing && (state == RecvOpen || stepAdvancesParser())) {
// if we get here, then it's because we're in some step that advances the parser
pe = xml.readNext();
if(!pe.isNull()) {
// note: error/close events should be handled for ALL steps, so do them here
switch(pe.type()) {
case Parser::Event::DocumentOpen: {
transferItemList += TransferItem(pe.actualString(), false);
//stringRecv(pe.actualString());
break;
}
case Parser::Event::DocumentClose: {
transferItemList += TransferItem(pe.actualString(), false);
//stringRecv(pe.actualString());
if(incoming) {
sendTagClose();
event = ESend;
peerClosed = true;
state = Closing;
}
else {
event = EPeerClosed;
}
return true;
}
case Parser::Event::Element: {
QDomElement e = elemDoc.importNode(pe.element(),true).toElement();
transferItemList += TransferItem(e, false);
//elementRecv(pe.element());
break;
}
case Parser::Event::Error: {
if(incoming) {
// If we get a parse error during the initial element exchange,
// flip immediately into 'open' mode so that we can report an error.
if(state == RecvOpen) {
sendTagOpen();
state = Open;
}
return handleError();
}
else {
event = EError;
errorCode = ErrParse;
return true;
}
}
}
}
else {
if(state == RecvOpen || stepRequiresElement()) {
need = NNotify;
notify |= NRecv;
return false;
}
}
}
return baseStep(pe);
}
QString XmlProtocol::xmlEncoding() const
{
return xml.encoding();
}
QString XmlProtocol::elementToString(const QDomElement &e, bool clip)
{
if(elem.isNull())
elem = elemDoc.importNode(docElement(), true).toElement();
// Determine the appropriate 'fakeNS' to use
QString ns;
// first, check root namespace
QString pre = e.prefix();
if(pre.isNull())
pre = "";
if(pre == elem.prefix()) {
ns = elem.namespaceURI();
}
else {
// scan the root attributes for 'xmlns' (oh joyous hacks)
QDomNamedNodeMap al = elem.attributes();
int n;
for(n = 0; n < al.count(); ++n) {
QDomAttr a = al.item(n).toAttr();
QString s = a.name();
int x = s.find(':');
if(x != -1)
s = s.mid(x+1);
else
s = "";
if(pre == s) {
ns = a.value();
break;
}
}
if(n >= al.count()) {
// if we get here, then no appropriate ns was found. use root then..
ns = elem.namespaceURI();
}
}
// build qName
QString qn;
if(!elem.prefix().isEmpty())
qn = elem.prefix() + ':';
qn += elem.localName();
// make the string
return sanitizeForStream(xmlToString(e, ns, qn, clip));
}
bool XmlProtocol::stepRequiresElement() const
{
// default returns false
return false;
}
void XmlProtocol::itemWritten(int, int)
{
// default does nothing
}
void XmlProtocol::stringSend(const QString &)
{
// default does nothing
}
void XmlProtocol::stringRecv(const QString &)
{
// default does nothing
}
void XmlProtocol::elementSend(const QDomElement &)
{
// default does nothing
}
void XmlProtocol::elementRecv(const QDomElement &)
{
// default does nothing
}
void XmlProtocol::startConnect()
{
incoming = false;
state = SendOpen;
}
void XmlProtocol::startAccept()
{
incoming = true;
state = RecvOpen;
}
bool XmlProtocol::close()
{
sendTagClose();
event = ESend;
state = Closing;
return true;
}
int XmlProtocol::writeString(const QString &s, int id, bool external)
{
transferItemList += TransferItem(s, true, external);
return internalWriteString(s, TrackItem::Custom, id);
}
int XmlProtocol::writeElement(const QDomElement &e, int id, bool external, bool clip)
{
if(e.isNull())
return 0;
transferItemList += TransferItem(e, true, external);
//elementSend(e);
QString out = sanitizeForStream(elementToString(e, clip));
return internalWriteString(out, TrackItem::Custom, id);
}
QByteArray XmlProtocol::resetStream()
{
// reset the state
if(incoming)
state = RecvOpen;
else
state = SendOpen;
// grab unprocessed data before resetting
QByteArray spare = xml.unprocessed();
xml.reset();
return spare;
}
int XmlProtocol::internalWriteData(const QByteArray &a, TrackItem::Type t, int id)
{
TrackItem i;
i.type = t;
i.id = id;
i.size = a.size();
trackQueue += i;
ByteStream::appendArray(&outData, a);
return a.size();
}
int XmlProtocol::internalWriteString(const QString &s, TrackItem::Type t, int id)
{
QString out=sanitizeForStream(s);
Q3CString cs = s.utf8();
QByteArray a(cs.length());
memcpy(a.data(), cs.data(), a.size());
return internalWriteData(a, t, id);
}
void XmlProtocol::sendTagOpen()
{
if(elem.isNull())
elem = elemDoc.importNode(docElement(), true).toElement();
QString xmlHeader;
createRootXmlTags(elem, &xmlHeader, &tagOpen, &tagClose);
QString s;
s += xmlHeader + '\n';
s += sanitizeForStream(tagOpen) + '\n';
transferItemList += TransferItem(xmlHeader, true);
transferItemList += TransferItem(tagOpen, true);
//stringSend(xmlHeader);
//stringSend(tagOpen);
internalWriteString(s, TrackItem::Raw);
}
void XmlProtocol::sendTagClose()
{
transferItemList += TransferItem(tagClose, true);
//stringSend(tagClose);
internalWriteString(tagClose, TrackItem::Close);
}
bool XmlProtocol::baseStep(const Parser::Event &pe)
{
// Basic
if(state == SendOpen) {
sendTagOpen();
event = ESend;
if(incoming)
state = Open;
else
state = RecvOpen;
return true;
}
else if(state == RecvOpen) {
if(incoming)
state = SendOpen;
else
state = Open;
// note: event will always be DocumentOpen here
handleDocOpen(pe);
event = ERecvOpen;
return true;
}
else if(state == Open) {
QDomElement e;
if(pe.type() == Parser::Event::Element)
e = pe.element();
return doStep(e);
}
// Closing
else {
if(closeWritten) {
if(peerClosed) {
event = EPeerClosed;
return true;
}
else
return handleCloseFinished();
}
need = NNotify;
notify = NSend;
return false;
}
}
void XmlProtocol::setIncomingAsExternal()
{
for(QList<TransferItem>::Iterator it = transferItemList.begin(); it != transferItemList.end(); ++it) {
TransferItem &i = *it;
// look for elements received
if(!i.isString && !i.isSent)
i.isExternal = true;
}
}

View file

@ -0,0 +1,146 @@
/*
* xmlprotocol.h - state machine for 'jabber-like' protocols
* Copyright (C) 2004 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef XMLPROTOCOL_H
#define XMLPROTOCOL_H
#include <qdom.h>
#include <QList>
#include <QObject>
#include "parser.h"
#define NS_XML "http://www.w3.org/XML/1998/namespace"
namespace XMPP
{
class XmlProtocol : public QObject
{
public:
enum Need {
NNotify, // need a data send and/or recv update
NCustom = 10
};
enum Event {
EError, // unrecoverable error, see errorCode for details
ESend, // data needs to be sent, use takeOutgoingData()
ERecvOpen, // breakpoint after root element open tag is received
EPeerClosed, // root element close tag received
EClosed, // finished closing
ECustom = 10
};
enum Error {
ErrParse, // there was an error parsing the xml
ErrCustom = 10
};
enum Notify {
NSend = 0x01, // need to know if data has been written
NRecv = 0x02 // need incoming data
};
XmlProtocol();
virtual ~XmlProtocol();
virtual void reset();
// byte I/O for the stream
void addIncomingData(const QByteArray &);
QByteArray takeOutgoingData();
void outgoingDataWritten(int);
// advance the state machine
bool processStep();
// set these before returning from a step
int need, event, errorCode, notify;
inline bool isIncoming() const { return incoming; }
QString xmlEncoding() const;
QString elementToString(const QDomElement &e, bool clip=false);
class TransferItem
{
public:
TransferItem();
TransferItem(const QString &str, bool sent, bool external=false);
TransferItem(const QDomElement &elem, bool sent, bool external=false);
bool isSent; // else, received
bool isString; // else, is element
bool isExternal; // not owned by protocol
QString str;
QDomElement elem;
};
QList<TransferItem> transferItemList;
void setIncomingAsExternal();
protected:
virtual QDomElement docElement()=0;
virtual void handleDocOpen(const Parser::Event &pe)=0;
virtual bool handleError()=0;
virtual bool handleCloseFinished()=0;
virtual bool stepAdvancesParser() const=0;
virtual bool stepRequiresElement() const;
virtual bool doStep(const QDomElement &e)=0;
virtual void itemWritten(int id, int size);
// 'debug'
virtual void stringSend(const QString &s);
virtual void stringRecv(const QString &s);
virtual void elementSend(const QDomElement &e);
virtual void elementRecv(const QDomElement &e);
void startConnect();
void startAccept();
bool close();
int writeString(const QString &s, int id, bool external);
int writeElement(const QDomElement &e, int id, bool external, bool clip=false);
QByteArray resetStream();
private:
enum { SendOpen, RecvOpen, Open, Closing };
class TrackItem
{
public:
enum Type { Raw, Close, Custom };
int type, id, size;
};
bool incoming;
QDomDocument elemDoc;
QDomElement elem;
QString tagOpen, tagClose;
int state;
bool peerClosed;
bool closeWritten;
Parser xml;
QByteArray outData;
QList<TrackItem> trackQueue;
void init();
int internalWriteData(const QByteArray &a, TrackItem::Type t, int id=-1);
int internalWriteString(const QString &s, TrackItem::Type t, int id=-1);
void sendTagOpen();
void sendTagClose();
bool baseStep(const Parser::Event &pe);
};
}
#endif

View file

@ -0,0 +1,660 @@
/*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "xmpp_stanza.h"
#include <QCoreApplication>
#include "xmpp_jid.h"
#include "xmpp_stream.h"
using namespace XMPP;
#define NS_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas"
#define NS_XML "http://www.w3.org/XML/1998/namespace"
//----------------------------------------------------------------------------
// Stanza::Error
//----------------------------------------------------------------------------
/**
\class Stanza::Error
\brief Represents stanza error
Stanza error consists of error type and condition.
In addition, it may contain a human readable description,
and application specific element.
One of the usages of this class is to easily generate error XML:
\code
QDomElement e = createIQ(client()->doc(), "error", jid, id);
Error error(Stanza::Error::Auth, Stanza::Error::NotAuthorized);
e.appendChild(error.toXml(*client()->doc(), client()->stream().baseNS()));
\endcode
This class implements JEP-0086, which means that it can read both
old and new style error elements. Also, generated XML will contain
both type/condition and code.
Error text in output XML is always presented in XMPP-style only.
All functions will always try to guess missing information based on mappings defined in the JEP.
*/
/**
\enum Stanza::Error::ErrorType
\brief Represents error type
*/
/**
\enum Stanza::Error::ErrorCond
\brief Represents error condition
*/
/**
\brief Constructs new error
*/
Stanza::Error::Error(int _type, int _condition, const QString &_text, const QDomElement &_appSpec)
{
type = _type;
condition = _condition;
text = _text;
appSpec = _appSpec;
}
class Stanza::Error::Private
{
public:
struct ErrorTypeEntry
{
const char *str;
int type;
};
static ErrorTypeEntry errorTypeTable[];
struct ErrorCondEntry
{
const char *str;
int cond;
};
static ErrorCondEntry errorCondTable[];
struct ErrorCodeEntry
{
int cond;
int type;
int code;
};
static ErrorCodeEntry errorCodeTable[];
struct ErrorDescEntry
{
int cond;
const char *name;
const char *str;
};
static ErrorDescEntry errorDescriptions[];
static int stringToErrorType(const QString &s)
{
for(int n = 0; errorTypeTable[n].str; ++n) {
if(s == errorTypeTable[n].str)
return errorTypeTable[n].type;
}
return -1;
}
static QString errorTypeToString(int x)
{
for(int n = 0; errorTypeTable[n].str; ++n) {
if(x == errorTypeTable[n].type)
return errorTypeTable[n].str;
}
return QString();
}
static int stringToErrorCond(const QString &s)
{
for(int n = 0; errorCondTable[n].str; ++n) {
if(s == errorCondTable[n].str)
return errorCondTable[n].cond;
}
return -1;
}
static QString errorCondToString(int x)
{
for(int n = 0; errorCondTable[n].str; ++n) {
if(x == errorCondTable[n].cond)
return errorCondTable[n].str;
}
return QString();
}
static int errorTypeCondToCode(int t, int c)
{
Q_UNUSED(t);
for(int n = 0; errorCodeTable[n].cond; ++n) {
if(c == errorCodeTable[n].cond)
return errorCodeTable[n].code;
}
return 0;
}
static QPair<int, int> errorCodeToTypeCond(int x)
{
for(int n = 0; errorCodeTable[n].cond; ++n) {
if(x == errorCodeTable[n].code)
return QPair<int, int>(errorCodeTable[n].type, errorCodeTable[n].cond);
}
return QPair<int, int>(-1, -1);
}
static QPair<QString,QString> errorCondToDesc(int x)
{
for(int n = 0; errorDescriptions[n].str; ++n) {
if(x == errorDescriptions[n].cond)
return QPair<QString, QString>(QCoreApplication::translate("Stanza::Error::Private", errorDescriptions[n].name),
QCoreApplication::translate("Stanza::Error::Private", errorDescriptions[n].str));
}
return QPair<QString,QString>();
}
};
Stanza::Error::Private::ErrorTypeEntry Stanza::Error::Private::errorTypeTable[] =
{
{ "cancel", Cancel },
{ "continue", Continue },
{ "modify", Modify },
{ "auth", Auth },
{ "wait", Wait },
{ 0, 0 },
};
Stanza::Error::Private::ErrorCondEntry Stanza::Error::Private::errorCondTable[] =
{
{ "bad-request", BadRequest },
{ "conflict", Conflict },
{ "feature-not-implemented", FeatureNotImplemented },
{ "forbidden", Forbidden },
{ "gone", Gone },
{ "internal-server-error", InternalServerError },
{ "item-not-found", ItemNotFound },
{ "jid-malformed", JidMalformed },
{ "not-acceptable", NotAcceptable },
{ "not-allowed", NotAllowed },
{ "not-authorized", NotAuthorized },
{ "payment-required", PaymentRequired },
{ "recipient-unavailable", RecipientUnavailable },
{ "redirect", Redirect },
{ "registration-required", RegistrationRequired },
{ "remote-server-not-found", RemoteServerNotFound },
{ "remote-server-timeout", RemoteServerTimeout },
{ "resource-constraint", ResourceConstraint },
{ "service-unavailable", ServiceUnavailable },
{ "subscription-required", SubscriptionRequired },
{ "undefined-condition", UndefinedCondition },
{ "unexpected-request", UnexpectedRequest },
{ 0, 0 },
};
Stanza::Error::Private::ErrorCodeEntry Stanza::Error::Private::errorCodeTable[] =
{
{ BadRequest, Modify, 400 },
{ Conflict, Cancel, 409 },
{ FeatureNotImplemented, Cancel, 501 },
{ Forbidden, Auth, 403 },
{ Gone, Modify, 302 }, // permanent
{ InternalServerError, Wait, 500 },
{ ItemNotFound, Cancel, 404 },
{ JidMalformed, Modify, 400 },
{ NotAcceptable, Modify, 406 },
{ NotAllowed, Cancel, 405 },
{ NotAuthorized, Auth, 401 },
{ PaymentRequired, Auth, 402 },
{ RecipientUnavailable, Wait, 404 },
{ Redirect, Modify, 302 }, // temporary
{ RegistrationRequired, Auth, 407 },
{ RemoteServerNotFound, Cancel, 404 },
{ RemoteServerTimeout, Wait, 504 },
{ ResourceConstraint, Wait, 500 },
{ ServiceUnavailable, Cancel, 503 },
{ SubscriptionRequired, Auth, 407 },
{ UndefinedCondition, Wait, 500 }, // Note: any type matches really
{ UnexpectedRequest, Wait, 400 },
{ 0, 0, 0 },
};
Stanza::Error::Private::ErrorDescEntry Stanza::Error::Private::errorDescriptions[] =
{
{ BadRequest, QT_TR_NOOP("Bad request"), QT_TR_NOOP("The sender has sent XML that is malformed or that cannot be processed.") },
{ Conflict, QT_TR_NOOP("Conflict"), QT_TR_NOOP("Access cannot be granted because an existing resource or session exists with the same name or address.") },
{ FeatureNotImplemented, QT_TR_NOOP("Feature not implemented"), QT_TR_NOOP("The feature requested is not implemented by the recipient or server and therefore cannot be processed.") },
{ Forbidden, QT_TR_NOOP("Forbidden"), QT_TR_NOOP("The requesting entity does not possess the required permissions to perform the action.") },
{ Gone, QT_TR_NOOP("Gone"), QT_TR_NOOP("The recipient or server can no longer be contacted at this address.") },
{ InternalServerError, QT_TR_NOOP("Internal server error"), QT_TR_NOOP("The server could not process the stanza because of a misconfiguration or an otherwise-undefined internal server error.") },
{ ItemNotFound, QT_TR_NOOP("Item not found"), QT_TR_NOOP("The addressed JID or item requested cannot be found.") },
{ JidMalformed, QT_TR_NOOP("JID malformed"), QT_TR_NOOP("The sending entity has provided or communicated an XMPP address (e.g., a value of the 'to' attribute) or aspect thereof (e.g., a resource identifier) that does not adhere to the syntax defined in Addressing Scheme.") },
{ NotAcceptable, QT_TR_NOOP("Not acceptable"), QT_TR_NOOP("The recipient or server understands the request but is refusing to process it because it does not meet criteria defined by the recipient or server (e.g., a local policy regarding acceptable words in messages).") },
{ NotAllowed, QT_TR_NOOP("Not allowed"), QT_TR_NOOP("The recipient or server does not allow any entity to perform the action.") },
{ NotAuthorized, QT_TR_NOOP("Not authorized"), QT_TR_NOOP("The sender must provide proper credentials before being allowed to perform the action, or has provided improper credentials.") },
{ PaymentRequired, QT_TR_NOOP("Payment required"), QT_TR_NOOP("The requesting entity is not authorized to access the requested service because payment is required.") },
{ RecipientUnavailable, QT_TR_NOOP("Recipient unavailable"), QT_TR_NOOP("The intended recipient is temporarily unavailable.") },
{ Redirect, QT_TR_NOOP("Redirect"), QT_TR_NOOP("The recipient or server is redirecting requests for this information to another entity, usually temporarily.") },
{ RegistrationRequired, QT_TR_NOOP("Registration required"), QT_TR_NOOP("The requesting entity is not authorized to access the requested service because registration is required.") },
{ RemoteServerNotFound, QT_TR_NOOP("Remote server not found"), QT_TR_NOOP("A remote server or service specified as part or all of the JID of the intended recipient does not exist.") },
{ RemoteServerTimeout, QT_TR_NOOP("Remote server timeout"), QT_TR_NOOP("A remote server or service specified as part or all of the JID of the intended recipient (or required to fulfill a request) could not be contacted within a reasonable amount of time.") },
{ ResourceConstraint, QT_TR_NOOP("Resource constraint"), QT_TR_NOOP("The server or recipient lacks the system resources necessary to service the request.") },
{ ServiceUnavailable, QT_TR_NOOP("Service unavailable"), QT_TR_NOOP("The server or recipient does not currently provide the requested service.") },
{ SubscriptionRequired, QT_TR_NOOP("Subscription required"), QT_TR_NOOP("The requesting entity is not authorized to access the requested service because a subscription is required.") },
{ UndefinedCondition, QT_TR_NOOP("Undefined condition"), QT_TR_NOOP("The error condition is not one of those defined by the other conditions in this list.") },
{ UnexpectedRequest, QT_TR_NOOP("Unexpected request"), QT_TR_NOOP("The recipient or server understood the request but was not expecting it at this time (e.g., the request was out of order).") },
};
/**
\brief Returns the error code
If the error object was constructed with a code, this code will be returned.
Otherwise, the code will be guessed.
0 means unknown code.
*/
int Stanza::Error::code() const
{
return originalCode ? originalCode : Private::errorTypeCondToCode(type, condition);
}
/**
\brief Creates a StanzaError from \a code.
The error's type and condition are guessed from the give \a code.
The application-specific error element is preserved.
*/
bool Stanza::Error::fromCode(int code)
{
QPair<int, int> guess = Private::errorCodeToTypeCond(code);
if(guess.first == -1 || guess.second == -1)
return false;
type = guess.first;
condition = guess.second;
originalCode = code;
return true;
}
/**
\brief Reads the error from XML
This function finds and reads the error element \a e.
You need to provide the base namespace of the stream which this stanza belongs to
(probably by using stream.baseNS() function).
*/
bool Stanza::Error::fromXml(const QDomElement &e, const QString &baseNS)
{
if(e.tagName() != "error" && e.namespaceURI() != baseNS)
return false;
// type
type = Private::stringToErrorType(e.attribute("type"));
// condition
QDomNodeList nl = e.childNodes();
QDomElement t;
condition = -1;
int n;
for(n = 0; n < nl.count(); ++n) {
QDomNode i = nl.item(n);
t = i.toElement();
if(!t.isNull()) {
// FIX-ME: this shouldn't be needed
if(t.namespaceURI() == NS_STANZAS || t.attribute("xmlns") == NS_STANZAS) {
condition = Private::stringToErrorCond(t.tagName());
if (condition != -1)
break;
}
}
}
// code
originalCode = e.attribute("code").toInt();
// try to guess type/condition
if(type == -1 || condition == -1) {
QPair<int, int> guess(-1, -1);
if (originalCode)
guess = Private::errorCodeToTypeCond(originalCode);
if (type == -1)
type = guess.first != -1 ? guess.first : Cancel;
if (condition == -1)
condition = guess.second != -1 ? guess.second : UndefinedCondition;
}
// text
t = e.elementsByTagNameNS(NS_STANZAS, "text").item(0).toElement();
if(!t.isNull())
text = t.text().trimmed();
else
text = e.text().trimmed();
// appspec: find first non-standard namespaced element
appSpec = QDomElement();
nl = e.childNodes();
for(n = 0; n < nl.count(); ++n) {
QDomNode i = nl.item(n);
if(i.isElement() && i.namespaceURI() != NS_STANZAS) {
appSpec = i.toElement();
break;
}
}
return true;
}
/**
\brief Writes the error to XML
This function creates an error element representing the error object.
You need to provide the base namespace of the stream to which this stanza belongs to
(probably by using stream.baseNS() function).
*/
QDomElement Stanza::Error::toXml(QDomDocument &doc, const QString &baseNS) const
{
QDomElement errElem = doc.createElementNS(baseNS, "error");
QDomElement t;
// XMPP error
QString stype = Private::errorTypeToString(type);
if(stype.isEmpty())
return errElem;
QString scond = Private::errorCondToString(condition);
if(scond.isEmpty())
return errElem;
errElem.setAttribute("type", stype);
errElem.appendChild(t = doc.createElementNS(NS_STANZAS, scond));
t.setAttribute("xmlns", NS_STANZAS); // FIX-ME: this shouldn't be needed
// old code
int scode = code();
if(scode)
errElem.setAttribute("code", scode);
// text
if(!text.isEmpty()) {
t = doc.createElementNS(NS_STANZAS, "text");
t.setAttribute("xmlns", NS_STANZAS); // FIX-ME: this shouldn't be needed
t.appendChild(doc.createTextNode(text));
errElem.appendChild(t);
}
// application specific
errElem.appendChild(appSpec);
return errElem;
}
/**
\brief Returns the error name and description
Returns the error name (e.g. "Not Allowed") and generic description.
*/
QPair<QString,QString> Stanza::Error::description() const
{
return Private::errorCondToDesc(condition);
}
//----------------------------------------------------------------------------
// Stanza
//----------------------------------------------------------------------------
class Stanza::Private
{
public:
static int stringToKind(const QString &s)
{
if(s == "message")
return Message;
else if(s == "presence")
return Presence;
else if(s == "iq")
return IQ;
else
return -1;
}
static QString kindToString(Kind k)
{
if(k == Message)
return "message";
else if(k == Presence)
return "presence";
else
return "iq";
}
Stream *s;
QDomElement e;
};
Stanza::Stanza()
{
d = 0;
}
Stanza::Stanza(Stream *s, Kind k, const Jid &to, const QString &type, const QString &id)
{
Q_ASSERT(s);
d = new Private;
Kind kind;
if(k == Message || k == Presence || k == IQ)
kind = k;
else
kind = Message;
d->s = s;
if(d->s)
d->e = d->s->doc().createElementNS(s->baseNS(), Private::kindToString(kind));
if(to.isValid())
setTo(to);
if(!type.isEmpty())
setType(type);
if(!id.isEmpty())
setId(id);
}
Stanza::Stanza(Stream *s, const QDomElement &e)
{
Q_ASSERT(s);
d = 0;
if(e.namespaceURI() != s->baseNS())
return;
int x = Private::stringToKind(e.tagName());
if(x == -1)
return;
d = new Private;
d->s = s;
d->e = e;
}
Stanza::Stanza(const Stanza &from)
{
d = 0;
*this = from;
}
Stanza & Stanza::operator=(const Stanza &from)
{
delete d;
d = 0;
if(from.d)
d = new Private(*from.d);
return *this;
}
Stanza::~Stanza()
{
delete d;
}
bool Stanza::isNull() const
{
return (d ? false: true);
}
QDomElement Stanza::element() const
{
return d->e;
}
QString Stanza::toString() const
{
return Stream::xmlToString(d->e);
}
QDomDocument & Stanza::doc() const
{
return d->s->doc();
}
QString Stanza::baseNS() const
{
return d->s->baseNS();
}
QDomElement Stanza::createElement(const QString &ns, const QString &tagName)
{
return d->s->doc().createElementNS(ns, tagName);
}
QDomElement Stanza::createTextElement(const QString &ns, const QString &tagName, const QString &text)
{
QDomElement e = d->s->doc().createElementNS(ns, tagName);
e.appendChild(d->s->doc().createTextNode(text));
return e;
}
void Stanza::appendChild(const QDomElement &e)
{
d->e.appendChild(e);
}
Stanza::Kind Stanza::kind() const
{
return (Kind)Private::stringToKind(d->e.tagName());
}
void Stanza::setKind(Kind k)
{
d->e.setTagName(Private::kindToString(k));
}
Jid Stanza::to() const
{
return Jid(d->e.attribute("to"));
}
Jid Stanza::from() const
{
return Jid(d->e.attribute("from"));
}
QString Stanza::id() const
{
return d->e.attribute("id");
}
QString Stanza::type() const
{
return d->e.attribute("type");
}
QString Stanza::lang() const
{
return d->e.attributeNS(NS_XML, "lang", QString());
}
void Stanza::setTo(const Jid &j)
{
d->e.setAttribute("to", j.full());
}
void Stanza::setFrom(const Jid &j)
{
d->e.setAttribute("from", j.full());
}
void Stanza::setId(const QString &id)
{
d->e.setAttribute("id", id);
}
void Stanza::setType(const QString &type)
{
d->e.setAttribute("type", type);
}
void Stanza::setLang(const QString &lang)
{
d->e.setAttribute("xml:lang", lang);
}
Stanza::Error Stanza::error() const
{
Error err;
QDomElement e = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement();
if(!e.isNull())
err.fromXml(e, d->s->baseNS());
return err;
}
void Stanza::setError(const Error &err)
{
QDomDocument doc = d->e.ownerDocument();
QDomElement errElem = err.toXml(doc, d->s->baseNS());
QDomElement oldElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement();
if(oldElem.isNull()) {
d->e.appendChild(errElem);
}
else {
d->e.replaceChild(errElem, oldElem);
}
}
void Stanza::clearError()
{
QDomElement errElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement();
if(!errElem.isNull())
d->e.removeChild(errElem);
}
#ifdef YAPSI
void Stanza::setAttribute(const QString& name, const QString& value)
{
d->e.setAttribute(name, value);
}
#endif

View file

@ -0,0 +1,183 @@
/*
* xmpp_yadatetime.cpp
* Copyright (C) 2009 Yandex LLC (Michail Pishchagin)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "xmpp_yadatetime.h"
#include <QHash>
uint qHash(const XMPP::YaDateTime& yaDateTime)
{
return qHash(yaDateTime.toYaTime_t());
}
XMPP::YaDateTime::YaDateTime()
: QDateTime()
, microsec_(0)
{
}
XMPP::YaDateTime::YaDateTime(const QDateTime& dateTime)
: QDateTime(dateTime)
, microsec_(0)
{
}
XMPP::YaDateTime::YaDateTime(const YaDateTime& dateTime)
: QDateTime(dateTime)
, microsec_(dateTime.microsec_)
{
}
bool XMPP::YaDateTime::isNull() const
{
return !QDateTime::isValid();
}
int XMPP::YaDateTime::microsec() const
{
return microsec_;
}
void XMPP::YaDateTime::setMiscosec(int microsec)
{
microsec_ = microsec;
}
XMPP::YaDateTime& XMPP::YaDateTime::operator=(const YaDateTime &other)
{
setDate(other.date());
setTime(other.time());
microsec_ = other.microsec_;
return *this;
}
bool XMPP::YaDateTime::operator==(const YaDateTime& other) const
{
return (date() == other.date()) &&
(time() == other.time()) &&
(microsec() == other.microsec());
}
bool XMPP::YaDateTime::operator!=(const YaDateTime& other) const
{
return !operator==(other);
}
QString XMPP::YaDateTime::toYaTime_t() const
{
QString msec;
msec.sprintf("%06d", microsec_);
QString ts = QString::number(toUTC().toTime_t()) + msec;
return ts;
}
XMPP::YaDateTime XMPP::YaDateTime::fromYaTime_t(const QString& str)
{
QString timeStamp = str;
YaDateTime result;
if (str.isEmpty())
return result;
result.microsec_ = getMicrosec(timeStamp.right(6));
timeStamp.chop(6);
QDateTime ts = QDateTime::fromTime_t(timeStamp.toInt());
// ts.setTimeSpec(Qt::UTC);
// ts = ts.toLocalTime();
result.setDate(ts.date());
result.setTime(ts.time());
return result;
}
QString XMPP::YaDateTime::toYaIsoTime() const
{
QString result = toUTC().toString("yyyy-MM-dd HH:mm:ss");
QString msec;
msec.sprintf("%06d", microsec_);
result += "." + msec;
return result;
}
XMPP::YaDateTime XMPP::YaDateTime::fromYaIsoTime(const QString& str)
{
YaDateTime result;
if (str.isEmpty())
return result;
QString timeStamp = str;
if (timeStamp.length() == 26) {
result.microsec_ = getMicrosec(timeStamp.right(6));
timeStamp.chop(7);
}
else {
result.microsec_ = 0;
}
QDateTime ts = QDateTime::fromString(timeStamp, "yyyy-MM-dd HH:mm:ss");
ts.setTimeSpec(Qt::UTC);
ts = ts.toLocalTime();
result.setDate(ts.date());
result.setTime(ts.time());
return result;
}
bool XMPP::YaDateTime::operator<(const YaDateTime& other) const
{
if (date() != other.date())
return date() < other.date();
if (time() != other.time())
return time() < other.time();
return microsec_ < other.microsec_;
}
bool XMPP::YaDateTime::operator<=(const YaDateTime& other) const
{
if (date() != other.date())
return date() <= other.date();
if (time() != other.time())
return time() <= other.time();
return microsec_ <= other.microsec_;
}
bool XMPP::YaDateTime::operator>(const YaDateTime& other) const
{
if (date() != other.date())
return date() > other.date();
if (time() != other.time())
return time() > other.time();
return microsec_ > other.microsec_;
}
bool XMPP::YaDateTime::operator>=(const YaDateTime& other) const
{
if (date() != other.date())
return date() >= other.date();
if (time() != other.time())
return time() >= other.time();
return microsec_ >= other.microsec_;
}
// WEBCHAT-2480, ONLINE-2275
int XMPP::YaDateTime::getMicrosec(const QString& str)
{
QString s = str;
s.chop(2);
s += "00";
return s.toInt();
}