/* * 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 parent 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 children 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; } }