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 ">" 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 += ">"; 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