From bcb704366cb5e333a626c18c308c7e0448a8e69f Mon Sep 17 00:00:00 2001 From: toma Date: Wed, 25 Nov 2009 17:56:58 +0000 Subject: Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features. BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdenetwork@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- .../jabber/libiris/iris/xmpp-core/xmlprotocol.cpp | 543 +++++++++++++++++++++ 1 file changed, 543 insertions(+) create mode 100644 kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp (limited to 'kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp') diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp new file mode 100644 index 00000000..c70a04a9 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp @@ -0,0 +1,543 @@ +/* + * 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" + +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; + uint 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, 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 QStrings, 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 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, 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 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() +{ + init(); +} + +XmlProtocol::~XmlProtocol() +{ +} + +void XmlProtocol::init() +{ + incoming = false; + peerClosed = false; + closeWritten = false; +} + +void XmlProtocol::reset() +{ + init(); + + elem = QDomElement(); + 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.copy(); + outData.resize(0); + return a; +} + +void XmlProtocol::outgoingDataWritten(int bytes) +{ + for(QValueList::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); +} + +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(); + uint 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 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 = 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) +{ + QCString 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 += 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(QValueList::Iterator it = transferItemList.begin(); it != transferItemList.end(); ++it) { + TransferItem &i = *it; + // look for elements received + if(!i.isString && !i.isSent) + i.isExternal = true; + } +} + -- cgit v1.2.1