File indexing completed on 2024-06-23 04:03:36

0001 /*
0002  * xmlprotocol.cpp - state machine for 'jabber-like' protocols
0003  * Copyright (C) 2004  Justin Karneges
0004  *
0005  * This library is free software; you can redistribute it and/or
0006  * modify it under the terms of the GNU Lesser General Public
0007  * License as published by the Free Software Foundation; either
0008  * either version 2
0009    of the License, or (at your option) any later version.1 of the License, or (at your option) any later version.
0010  *
0011  * This library is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0014  * Lesser General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU Lesser General Public
0017  * License along with this library; if not, write to the Free Software
0018  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
0019  *
0020  */
0021 
0022 #include "xmlprotocol.h"
0023 
0024 #include "bytestream.h"
0025 //Added by qt3to4:
0026 #include <QTextStream>
0027 #include <QByteArray>
0028 #include <QApplication>
0029 
0030 using namespace XMPP;
0031 
0032 // stripExtraNS
0033 //
0034 // This function removes namespace information from various nodes for
0035 // display purposes only (the element is pretty much useless for processing
0036 // after this).  We do this because QXml is a bit overzealous about outputting
0037 // redundant namespaces.
0038 static QDomElement stripExtraNS(const QDomElement &e)
0039 {
0040     // find closest parent with a namespace
0041     QDomNode par = e.parentNode();
0042     while(!par.isNull() && par.namespaceURI().isNull())
0043         par = par.parentNode();
0044     bool noShowNS = false;
0045     if(!par.isNull() && par.namespaceURI() == e.namespaceURI())
0046         noShowNS = true;
0047 
0048     // build qName (prefix:localName)
0049     QString qName;
0050     if(!e.prefix().isEmpty())
0051         qName = e.prefix() + ':' + e.localName();
0052     else
0053         qName = e.tagName();
0054 
0055     QDomElement i;
0056     int x;
0057     if(noShowNS)
0058         i = e.ownerDocument().createElement(qName);
0059     else
0060         i = e.ownerDocument().createElementNS(e.namespaceURI(), qName);
0061 
0062     // copy attributes
0063     QDomNamedNodeMap al = e.attributes();
0064     for(x = 0; x < al.count(); ++x) {
0065         QDomAttr a = al.item(x).cloneNode().toAttr();
0066 
0067         // don't show xml namespace
0068         if(a.namespaceURI() == NS_XML)
0069             i.setAttribute(QString("xml:") + a.name(), a.value());
0070         else
0071             i.setAttributeNodeNS(a);
0072     }
0073 
0074     // copy children
0075     QDomNodeList nl = e.childNodes();
0076     for(x = 0; x < nl.count(); ++x) {
0077         QDomNode n = nl.item(x);
0078         if(n.isElement())
0079             i.appendChild(stripExtraNS(n.toElement()));
0080         else
0081             i.appendChild(n.cloneNode());
0082     }
0083     return i;
0084 }
0085 
0086 // xmlToString
0087 //
0088 // This function converts a QDomElement into a QString, using stripExtraNS
0089 // to make it pretty.
0090 static QString xmlToString(const QDomElement &e, const QString &fakeNS, const QString &fakeQName, bool clip)
0091 {
0092     QDomElement i = e.cloneNode().toElement();
0093 
0094     // It seems QDom can only have one namespace attribute at a time (see docElement 'HACK').
0095     // Fortunately we only need one kind depending on the input, so it is specified here.
0096     QDomElement fake = e.ownerDocument().createElementNS(fakeNS, fakeQName);
0097     fake.appendChild(i);
0098     fake = stripExtraNS(fake);
0099     QString out;
0100     {
0101         QTextStream ts(&out, QIODevice::WriteOnly);
0102         fake.firstChild().save(ts, 0);
0103     }
0104     // 'clip' means to remove any unwanted (and unneeded) characters, such as a trailing newline
0105     if(clip) {
0106         int n = out.lastIndexOf('>');
0107         out.truncate(n+1);
0108     }
0109     return out;
0110 }
0111 
0112 // createRootXmlTags
0113 //
0114 // This function creates three QStrings, one being an <?xml .. ?> processing
0115 // instruction, and the others being the opening and closing tags of an
0116 // element, <foo> and </foo>.  This basically allows us to get the raw XML
0117 // text needed to open/close an XML stream, without resorting to generating
0118 // the XML ourselves.  This function uses QDom to do the generation, which
0119 // ensures proper encoding and entity output.
0120 static void createRootXmlTags(const QDomElement &root, QString *xmlHeader, QString *tagOpen, QString *tagClose)
0121 {
0122     QDomElement e = root.cloneNode(false).toElement();
0123 
0124     // insert a dummy element to ensure open and closing tags are generated
0125     QDomElement dummy = e.ownerDocument().createElement("dummy");
0126     e.appendChild(dummy);
0127 
0128     // convert to xml->text
0129     QString str;
0130     {
0131         QTextStream ts(&str, QIODevice::WriteOnly);
0132         e.save(ts, 0);
0133     }
0134 
0135     // parse the tags out
0136     int n = str.indexOf('<');
0137     int n2 = str.indexOf('>', n);
0138     ++n2;
0139     *tagOpen = str.mid(n, n2-n);
0140     n2 = str.lastIndexOf('>');
0141     n = str.lastIndexOf('<');
0142     ++n2;
0143     *tagClose = str.mid(n, n2-n);
0144 
0145     // generate a nice xml processing header
0146     *xmlHeader = "<?xml version=\"1.0\"?>";
0147 }
0148 
0149 // w3c xml spec:
0150 // [2] Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
0151 static inline bool validChar(const quint32 ch) 
0152 {
0153     return  ch == 0x9 || ch == 0xA || ch == 0xD
0154             || ch >= 0x20 && ch <= 0xD7FF
0155             || ch >= 0xE000 && ch <= 0xFFFD
0156             || ch >= 0x10000 && ch <= 0x10FFFF;
0157 }
0158 
0159 static inline bool lowSurrogate(const quint32 ch) 
0160 {
0161     return  ch >= 0xDC00 && ch <= 0xDFFF;
0162 }
0163 
0164 static inline bool highSurrogate(const quint32 ch) 
0165 {
0166     return  ch >= 0xD800 && ch <= 0xDBFF;
0167 }
0168 
0169 
0170 // force encoding of '>'.  this function is needed for XMPP-Core, which
0171 //  requires the '>' character to be encoded as "&gt;" even though this is
0172 //  not required by the XML spec.
0173 // Also remove chars that are ouside the allowed range for XML (see validChar)
0174 //  and invalid surrogate pairs
0175 static QString sanitizeForStream(const QString &in)
0176 {
0177     QString out;
0178     bool intag = false;
0179     bool inquote = false;
0180     QChar quotechar;
0181     int inlength = in.length();
0182     for(int n = 0; n < inlength; ++n)
0183     {
0184         QChar c = in[n];
0185         bool escape = false;
0186         if(c == '<')
0187         {
0188             intag = true;
0189         }
0190         else if(c == '>')
0191         {
0192             if(inquote) {
0193                 escape = true;
0194             } else if(!intag) {
0195                 escape = true;
0196             } else {
0197                 intag = false;
0198             }
0199         }
0200         else if(c == '\'' || c == '\"')
0201         {
0202             if(intag)
0203             {
0204                 if(!inquote)
0205                 {
0206                     inquote = true;
0207                     quotechar = c;
0208                 }
0209                 else
0210                 {
0211                     if(quotechar == c) {
0212                         inquote = false;
0213                     }
0214                 }
0215             }
0216         }
0217 
0218         if(escape) {
0219             out += "&gt;";
0220          } else {
0221             // don't silently drop invalid chars in element or attribute names,
0222             // because that's something that should not happen.
0223             if (intag && (!inquote)) {
0224                 out += c;
0225             } else if (validChar(c.unicode()))  {
0226                 out += c;
0227             } else if (highSurrogate(c.unicode()) && (n+1 < inlength) && lowSurrogate(in[n+1].unicode())) {
0228                 //uint unicode = (c.unicode() & 0x3FF) << 10 | in[n+1].unicode() & 0x3FF + 0x10000;
0229                 // we don't need to recheck this, because 0x10000 <= unicode <= 0x100000 is always true
0230                 out += c;
0231                 out += in[n+1];
0232                 ++n;
0233             } else {
0234                 qDebug("Dropping invalid XML char U+%04x",c.unicode());
0235             }
0236         }
0237     }
0238     return out;
0239 }
0240 
0241 
0242 //----------------------------------------------------------------------------
0243 // Protocol
0244 //----------------------------------------------------------------------------
0245 XmlProtocol::TransferItem::TransferItem()
0246 {
0247 }
0248 
0249 XmlProtocol::TransferItem::TransferItem(const QString &_str, bool sent, bool external)
0250 {
0251     isString = true;
0252     isSent = sent;
0253     isExternal = external;
0254     str = _str;
0255 }
0256 
0257 XmlProtocol::TransferItem::TransferItem(const QDomElement &_elem, bool sent, bool external)
0258 {
0259     isString = false;
0260     isSent = sent;
0261     isExternal = external;
0262     elem = _elem;
0263 }
0264 
0265 XmlProtocol::XmlProtocol()
0266     : QObject(qApp)
0267 {
0268     init();
0269 }
0270 
0271 XmlProtocol::~XmlProtocol()
0272 {
0273 }
0274 
0275 void XmlProtocol::init()
0276 {
0277     incoming = false;
0278     peerClosed = false;
0279     closeWritten = false;
0280 }
0281 
0282 void XmlProtocol::reset()
0283 {
0284     init();
0285 
0286     elem = QDomElement();
0287     elemDoc = QDomDocument();
0288     tagOpen = QString();
0289     tagClose = QString();
0290     xml.reset();
0291     outData.resize(0);
0292     trackQueue.clear();
0293     transferItemList.clear();
0294 }
0295 
0296 void XmlProtocol::addIncomingData(const QByteArray &a)
0297 {
0298     xml.appendData(a);
0299 }
0300 
0301 QByteArray XmlProtocol::takeOutgoingData()
0302 {
0303     QByteArray a = outData;
0304     outData.resize(0);
0305     return a;
0306 }
0307 
0308 void XmlProtocol::outgoingDataWritten(int bytes)
0309 {
0310     for(QList<TrackItem>::Iterator it = trackQueue.begin(); it != trackQueue.end();) {
0311         TrackItem &i = *it;
0312 
0313         // enough bytes?
0314         if(bytes < i.size) {
0315             i.size -= bytes;
0316             break;
0317         }
0318         int type = i.type;
0319         int id = i.id;
0320         int size = i.size;
0321         bytes -= i.size;
0322         it = trackQueue.erase(it);
0323 
0324         if(type == TrackItem::Raw) {
0325             // do nothing
0326         }
0327         else if(type == TrackItem::Close) {
0328             closeWritten = true;
0329         }
0330         else if(type == TrackItem::Custom) {
0331             itemWritten(id, size);
0332         }
0333     }
0334 }
0335 
0336 bool XmlProtocol::processStep()
0337 {
0338     Parser::Event pe;
0339     notify = 0;
0340     transferItemList.clear();
0341 
0342     if(state != Closing && (state == RecvOpen || stepAdvancesParser())) {
0343         // if we get here, then it's because we're in some step that advances the parser
0344         pe = xml.readNext();
0345         if(!pe.isNull()) {
0346             // note: error/close events should be handled for ALL steps, so do them here
0347             switch(pe.type()) {
0348                 case Parser::Event::DocumentOpen: {
0349                     transferItemList += TransferItem(pe.actualString(), false);
0350 
0351                     //stringRecv(pe.actualString());
0352                     break;
0353                 }
0354                 case Parser::Event::DocumentClose: {
0355                     transferItemList += TransferItem(pe.actualString(), false);
0356 
0357                     //stringRecv(pe.actualString());
0358                     if(incoming) {
0359                         sendTagClose();
0360                         event = ESend;
0361                         peerClosed = true;
0362                         state = Closing;
0363                     }
0364                     else {
0365                         event = EPeerClosed;
0366                     }
0367                     return true;
0368                 }
0369                 case Parser::Event::Element: {
0370                     QDomElement e = elemDoc.importNode(pe.element(),true).toElement();
0371                     transferItemList += TransferItem(e, false);
0372 
0373                     //elementRecv(pe.element());
0374                     break;
0375                 }
0376                 case Parser::Event::Error: {
0377                     if(incoming) {
0378                         // If we get a parse error during the initial element exchange,
0379                         // flip immediately into 'open' mode so that we can report an error.
0380                         if(state == RecvOpen) {
0381                             sendTagOpen();
0382                             state = Open;
0383                         }
0384                         return handleError();
0385                     }
0386                     else {
0387                         event = EError;
0388                         errorCode = ErrParse;
0389                         return true;
0390                     }
0391                 }
0392             }
0393         }
0394         else {
0395             if(state == RecvOpen || stepRequiresElement()) {
0396                 need = NNotify;
0397                 notify |= NRecv;
0398                 return false;
0399             }
0400         }
0401     }
0402 
0403     return baseStep(pe);
0404 }
0405 
0406 QString XmlProtocol::xmlEncoding() const
0407 {
0408     return xml.encoding();
0409 }
0410 
0411 QString XmlProtocol::elementToString(const QDomElement &e, bool clip)
0412 {
0413     if(elem.isNull())
0414         elem = elemDoc.importNode(docElement(), true).toElement();
0415 
0416     // Determine the appropriate 'fakeNS' to use
0417     QString ns;
0418 
0419     // first, check root namespace
0420     QString pre = e.prefix();
0421     if(pre.isNull())
0422         pre = "";
0423     if(pre == elem.prefix()) {
0424         ns = elem.namespaceURI();
0425     }
0426     else {
0427         // scan the root attributes for 'xmlns' (oh joyous hacks)
0428         QDomNamedNodeMap al = elem.attributes();
0429         int n;
0430         for(n = 0; n < al.count(); ++n) {
0431             QDomAttr a = al.item(n).toAttr();
0432             QString s = a.name();
0433             int x = s.indexOf(':');
0434             if(x != -1)
0435                 s = s.mid(x+1);
0436             else
0437                 s = "";
0438             if(pre == s) {
0439                 ns = a.value();
0440                 break;
0441             }
0442         }
0443         if(n >= al.count()) {
0444             // if we get here, then no appropriate ns was found.  use root then..
0445             ns = elem.namespaceURI();
0446         }
0447     }
0448 
0449     // build qName
0450     QString qn;
0451     if(!elem.prefix().isEmpty())
0452         qn = elem.prefix() + ':';
0453     qn += elem.localName();
0454 
0455     // make the string
0456     return sanitizeForStream(xmlToString(e, ns, qn, clip));
0457 }
0458 
0459 bool XmlProtocol::stepRequiresElement() const
0460 {
0461     // default returns false
0462     return false;
0463 }
0464 
0465 void XmlProtocol::itemWritten(int, int)
0466 {
0467     // default does nothing
0468 }
0469 
0470 void XmlProtocol::stringSend(const QString &)
0471 {
0472     // default does nothing
0473 }
0474 
0475 void XmlProtocol::stringRecv(const QString &)
0476 {
0477     // default does nothing
0478 }
0479 
0480 void XmlProtocol::elementSend(const QDomElement &)
0481 {
0482     // default does nothing
0483 }
0484 
0485 void XmlProtocol::elementRecv(const QDomElement &)
0486 {
0487     // default does nothing
0488 }
0489 
0490 void XmlProtocol::startConnect()
0491 {
0492     incoming = false;
0493     state = SendOpen;
0494 }
0495 
0496 void XmlProtocol::startAccept()
0497 {
0498     incoming = true;
0499     state = RecvOpen;
0500 }
0501 
0502 bool XmlProtocol::close()
0503 {
0504     sendTagClose();
0505     event = ESend;
0506     state = Closing;
0507     return true;
0508 }
0509 
0510 int XmlProtocol::writeString(const QString &s, int id, bool external)
0511 {
0512     transferItemList += TransferItem(s, true, external);
0513     return internalWriteString(s, TrackItem::Custom, id);
0514 }
0515 
0516 int XmlProtocol::writeElement(const QDomElement &e, int id, bool external, bool clip)
0517 {
0518     if(e.isNull())
0519         return 0;
0520     transferItemList += TransferItem(e, true, external);
0521 
0522     //elementSend(e);
0523     QString out = sanitizeForStream(elementToString(e, clip));
0524     return internalWriteString(out, TrackItem::Custom, id);
0525 }
0526 
0527 QByteArray XmlProtocol::resetStream()
0528 {
0529     // reset the state
0530     if(incoming)
0531         state = RecvOpen;
0532     else
0533         state = SendOpen;
0534 
0535     // grab unprocessed data before resetting
0536     QByteArray spare = xml.unprocessed();
0537     xml.reset();
0538     return spare;
0539 }
0540 
0541 int XmlProtocol::internalWriteData(const QByteArray &a, TrackItem::Type t, int id)
0542 {
0543     TrackItem i;
0544     i.type = t;
0545     i.id = id;
0546     i.size = a.size();
0547     trackQueue += i;
0548 
0549     ByteStream::appendArray(&outData, a);
0550     return a.size();
0551 }
0552 
0553 int XmlProtocol::internalWriteString(const QString &s, TrackItem::Type t, int id)
0554 {
0555     QString out=sanitizeForStream(s);
0556     return internalWriteData(s.toUtf8(), t, id);
0557 }
0558 
0559 void XmlProtocol::sendTagOpen()
0560 {
0561     if(elem.isNull())
0562         elem = elemDoc.importNode(docElement(), true).toElement();
0563 
0564     QString xmlHeader;
0565     createRootXmlTags(elem, &xmlHeader, &tagOpen, &tagClose);
0566 
0567     QString s;
0568     s += xmlHeader + '\n';
0569     s += sanitizeForStream(tagOpen) + '\n';
0570 
0571     transferItemList += TransferItem(xmlHeader, true);
0572     transferItemList += TransferItem(tagOpen, true);
0573 
0574     //stringSend(xmlHeader);
0575     //stringSend(tagOpen);
0576     internalWriteString(s, TrackItem::Raw);
0577 }
0578 
0579 void XmlProtocol::sendTagClose()
0580 {
0581     transferItemList += TransferItem(tagClose, true);
0582 
0583     //stringSend(tagClose);
0584     internalWriteString(tagClose, TrackItem::Close);
0585 }
0586 
0587 bool XmlProtocol::baseStep(const Parser::Event &pe)
0588 {
0589     // Basic
0590     if(state == SendOpen) {
0591         sendTagOpen();
0592         event = ESend;
0593         if(incoming)
0594             state = Open;
0595         else
0596             state = RecvOpen;
0597         return true;
0598     }
0599     else if(state == RecvOpen) {
0600         if(incoming)
0601             state = SendOpen;
0602         else
0603             state = Open;
0604 
0605         // note: event will always be DocumentOpen here
0606         handleDocOpen(pe);
0607         event = ERecvOpen;
0608         return true;
0609     }
0610     else if(state == Open) {
0611         QDomElement e;
0612         if(pe.type() == Parser::Event::Element)
0613             e = pe.element();
0614         return doStep(e);
0615     }
0616     // Closing
0617     else {
0618         if(closeWritten) {
0619             if(peerClosed) {
0620                 event = EPeerClosed;
0621                 return true;
0622             }
0623             else
0624                 return handleCloseFinished();
0625         }
0626 
0627         need = NNotify;
0628         notify = NSend;
0629         return false;
0630     }
0631 }
0632 
0633 void XmlProtocol::setIncomingAsExternal()
0634 {
0635     for(QList<TransferItem>::Iterator it = transferItemList.begin(); it != transferItemList.end(); ++it) {
0636         TransferItem &i = *it;
0637         // look for elements received
0638         if(!i.isString && !i.isSent)
0639             i.isExternal = true;
0640     }
0641 }
0642