/*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include"xmlprotocol.h"
#include"bytestream.h"
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 TQXml is a bit overzealous about outputting
// redundant namespaces.
static TQDomElement stripExtraNS(const TQDomElement &e)
{
// find closest tqparent with a namespace
TQDomNode 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)
TQString qName;
if(!e.prefix().isEmpty())
qName = e.prefix() + ':' + e.localName();
else
qName = e.tagName();
TQDomElement i;
uint x;
if(noShowNS)
i = e.ownerDocument().createElement(qName);
else
i = e.ownerDocument().createElementNS(e.namespaceURI(), qName);
// copy attributes
TQDomNamedNodeMap al = e.attributes();
for(x = 0; x < al.count(); ++x) {
TQDomAttr a = al.item(x).cloneNode().toAttr();
// don't show xml namespace
if(a.namespaceURI() == NS_XML)
i.setAttribute(TQString("xml:") + a.name(), a.value());
else
i.setAttributeNodeNS(a);
}
// copy tqchildren
TQDomNodeList nl = e.childNodes();
for(x = 0; x < nl.count(); ++x) {
TQDomNode n = nl.item(x);
if(n.isElement())
i.appendChild(stripExtraNS(n.toElement()));
else
i.appendChild(n.cloneNode());
}
return i;
}
// xmlToString
//
// This function converts a TQDomElement into a TQString, using stripExtraNS
// to make it pretty.
static TQString xmlToString(const TQDomElement &e, const TQString &fakeNS, const TQString &fakeTQName, bool clip)
{
TQDomElement i = e.cloneNode().toElement();
// It seems TQDom 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.
TQDomElement fake = e.ownerDocument().createElementNS(fakeNS, fakeTQName);
fake.appendChild(i);
fake = stripExtraNS(fake);
TQString out;
{
TQTextStream ts(&out, IO_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 TQStrings, one being an processing
// instruction, and the others being the opening and closing tags of an
// element, and . 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 TQDom to do the generation, which
// ensures proper encoding and entity output.
static void createRootXmlTags(const TQDomElement &root, TQString *xmlHeader, TQString *tagOpen, TQString *tagClose)
{
TQDomElement e = root.cloneNode(false).toElement();
// insert a dummy element to ensure open and closing tags are generated
TQDomElement dummy = e.ownerDocument().createElement("dummy");
e.appendChild(dummy);
// convert to xml->text
TQString str;
{
TQTextStream ts(&str, IO_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 = "";
}
//----------------------------------------------------------------------------
// Protocol
//----------------------------------------------------------------------------
XmlProtocol::TransferItem::TransferItem()
{
}
XmlProtocol::TransferItem::TransferItem(const TQString &_str, bool sent, bool external)
{
isString = true;
isSent = sent;
isExternal = external;
str = _str;
}
XmlProtocol::TransferItem::TransferItem(const TQDomElement &_elem, bool sent, bool external)
{
isString = false;
isSent = sent;
isExternal = external;
elem = _elem;
}
XmlProtocol::XmlProtocol()
{
init();
}
XmlProtocol::~XmlProtocol()
{
}
void XmlProtocol::init()
{
incoming = false;
peerClosed = false;
closeWritten = false;
}
void XmlProtocol::reset()
{
init();
elem = TQDomElement();
tagOpen = TQString();
tagClose = TQString();
xml.reset();
outData.resize(0);
trackQueue.clear();
transferItemList.clear();
}
void XmlProtocol::addIncomingData(const TQByteArray &a)
{
xml.appendData(a);
}
TQByteArray XmlProtocol::takeOutgoingData()
{
TQByteArray a = outData.copy();
outData.resize(0);
return a;
}
void XmlProtocol::outgoingDataWritten(int bytes)
{
for(TQValueList::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: {
transferItemList += TransferItem(pe.element(), 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);
}
TQString XmlProtocol::xmlEncoding() const
{
return xml.encoding();
}
TQString XmlProtocol::elementToString(const TQDomElement &e, bool clip)
{
if(elem.isNull())
elem = elemDoc.importNode(docElement(), true).toElement();
// Determine the appropriate 'fakeNS' to use
TQString ns;
// first, check root namespace
TQString pre = e.prefix();
if(pre.isNull())
pre = "";
if(pre == elem.prefix()) {
ns = elem.namespaceURI();
}
else {
// scan the root attributes for 'xmlns' (oh joyous hacks)
TQDomNamedNodeMap al = elem.attributes();
uint n;
for(n = 0; n < al.count(); ++n) {
TQDomAttr a = al.item(n).toAttr();
TQString 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
TQString qn;
if(!elem.prefix().isEmpty())
qn = elem.prefix() + ':';
qn += elem.localName();
// make the string
return 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 TQString &)
{
// default does nothing
}
void XmlProtocol::stringRecv(const TQString &)
{
// default does nothing
}
void XmlProtocol::elementSend(const TQDomElement &)
{
// default does nothing
}
void XmlProtocol::elementRecv(const TQDomElement &)
{
// 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 TQString &s, int id, bool external)
{
transferItemList += TransferItem(s, true, external);
return internalWriteString(s, TrackItem::Custom, id);
}
int XmlProtocol::writeElement(const TQDomElement &e, int id, bool external, bool clip)
{
if(e.isNull())
return 0;
transferItemList += TransferItem(e, true, external);
//elementSend(e);
TQString out = elementToString(e, clip);
return internalWriteString(out, TrackItem::Custom, id);
}
TQByteArray XmlProtocol::resetStream()
{
// reset the state
if(incoming)
state = RecvOpen;
else
state = SendOpen;
// grab unprocessed data before resetting
TQByteArray spare = xml.unprocessed();
xml.reset();
return spare;
}
int XmlProtocol::internalWriteData(const TQByteArray &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 TQString &s, TrackItem::Type t, int id)
{
TQCString cs = s.utf8();
TQByteArray 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();
TQString xmlHeader;
createRootXmlTags(elem, &xmlHeader, &tagOpen, &tagClose);
TQString s;
s += xmlHeader + '\n';
s += 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) {
TQDomElement 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(TQValueList::Iterator it = transferItemList.begin(); it != transferItemList.end(); ++it) {
TransferItem &i = *it;
// look for elements received
if(!i.isString && !i.isSent)
i.isExternal = true;
}
}