File indexing completed on 2024-06-02 04:07:05
0001 /* 0002 * Copyright (C) 2003 Justin Karneges 0003 * 0004 * This library is free software; you can redistribute it and/or 0005 * modify it under the terms of the GNU Lesser General Public 0006 * License as published by the Free Software Foundation; either 0007 * either version 2 0008 of the License, or (at your option) any later version.1 of the License, or (at your option) any later version. 0009 * 0010 * This library is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 * Lesser General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU Lesser General Public 0016 * License along with this library; if not, write to the Free Software 0017 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 0018 * 0019 */ 0020 0021 #include "xmpp/jid/jid.h" 0022 #include "xmpp_stanza.h" 0023 #include "xmpp_stream.h" 0024 0025 using namespace XMPP; 0026 0027 #define NS_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas" 0028 #define NS_XML "http://www.w3.org/XML/1998/namespace" 0029 0030 //---------------------------------------------------------------------------- 0031 // Stanza::Error 0032 //---------------------------------------------------------------------------- 0033 0034 /** 0035 \class Stanza::Error 0036 \brief Represents stanza error 0037 0038 Stanza error consists of error type and condition. 0039 In addition, it may contain a human readable description, 0040 and application specific element. 0041 0042 One of the usages of this class is to easily generate error XML: 0043 0044 \code 0045 QDomElement e = createIQ(client()->doc(), "error", jid, id); 0046 Error error(Stanza::Error::Auth, Stanza::Error::NotAuthorized); 0047 e.appendChild(error.toXml(*client()->doc(), client()->stream().baseNS())); 0048 \endcode 0049 0050 This class implements JEP-0086, which means that it can read both 0051 old and new style error elements. Also, generated XML will contain 0052 both type/condition and code. 0053 Error text in output XML is always presented in XMPP-style only. 0054 0055 All functions will always try to guess missing information based on mappings defined in the JEP. 0056 */ 0057 0058 /** 0059 0060 \enum Stanza::Error::ErrorType 0061 \brief Represents error type 0062 */ 0063 0064 /** 0065 \enum Stanza::Error::ErrorCond 0066 \brief Represents error condition 0067 */ 0068 0069 /** 0070 \brief Constructs new error 0071 */ 0072 Stanza::Error::Error(int _type, int _condition, const QString &_text, const QDomElement &_appSpec) 0073 { 0074 type = _type; 0075 condition = _condition; 0076 text = _text; 0077 appSpec = _appSpec; 0078 originalCode = 0; 0079 } 0080 0081 0082 class Stanza::Error::Private 0083 { 0084 public: 0085 struct ErrorTypeEntry 0086 { 0087 const char *str; 0088 int type; 0089 }; 0090 static ErrorTypeEntry errorTypeTable[]; 0091 0092 struct ErrorCondEntry 0093 { 0094 const char *str; 0095 int cond; 0096 }; 0097 static ErrorCondEntry errorCondTable[]; 0098 0099 struct ErrorCodeEntry 0100 { 0101 int cond; 0102 int type; 0103 int code; 0104 }; 0105 static ErrorCodeEntry errorCodeTable[]; 0106 0107 struct ErrorDescEntry 0108 { 0109 int cond; 0110 const char *name; 0111 const char *str; 0112 }; 0113 static ErrorDescEntry errorDescriptions[]; 0114 0115 0116 static int stringToErrorType(const QString &s) 0117 { 0118 for(int n = 0; errorTypeTable[n].str; ++n) { 0119 if(s == errorTypeTable[n].str) 0120 return errorTypeTable[n].type; 0121 } 0122 return -1; 0123 } 0124 0125 static QString errorTypeToString(int x) 0126 { 0127 for(int n = 0; errorTypeTable[n].str; ++n) { 0128 if(x == errorTypeTable[n].type) 0129 return errorTypeTable[n].str; 0130 } 0131 return QString(); 0132 } 0133 0134 static int stringToErrorCond(const QString &s) 0135 { 0136 for(int n = 0; errorCondTable[n].str; ++n) { 0137 if(s == errorCondTable[n].str) 0138 return errorCondTable[n].cond; 0139 } 0140 return -1; 0141 } 0142 0143 static QString errorCondToString(int x) 0144 { 0145 for(int n = 0; errorCondTable[n].str; ++n) { 0146 if(x == errorCondTable[n].cond) 0147 return errorCondTable[n].str; 0148 } 0149 return QString(); 0150 } 0151 0152 static int errorTypeCondToCode(int t, int c) 0153 { 0154 Q_UNUSED(t); 0155 for(int n = 0; errorCodeTable[n].cond; ++n) { 0156 if(c == errorCodeTable[n].cond) 0157 return errorCodeTable[n].code; 0158 } 0159 return 0; 0160 } 0161 0162 static QPair<int, int> errorCodeToTypeCond(int x) 0163 { 0164 for(int n = 0; errorCodeTable[n].cond; ++n) { 0165 if(x == errorCodeTable[n].code) 0166 return QPair<int, int>(errorCodeTable[n].type, errorCodeTable[n].cond); 0167 } 0168 return QPair<int, int>(-1, -1); 0169 } 0170 0171 static QPair<QString,QString> errorCondToDesc(int x) 0172 { 0173 for(int n = 0; errorDescriptions[n].str; ++n) { 0174 if(x == errorDescriptions[n].cond) 0175 return QPair<QString,QString>(QObject::tr(errorDescriptions[n].name), QObject::tr(errorDescriptions[n].str)); 0176 } 0177 return QPair<QString,QString>(); 0178 } 0179 }; 0180 0181 Stanza::Error::Private::ErrorTypeEntry Stanza::Error::Private::errorTypeTable[] = 0182 { 0183 { "cancel", Cancel }, 0184 { "continue", Continue }, 0185 { "modify", Modify }, 0186 { "auth", Auth }, 0187 { "wait", Wait }, 0188 { 0, 0 }, 0189 }; 0190 0191 Stanza::Error::Private::ErrorCondEntry Stanza::Error::Private::errorCondTable[] = 0192 { 0193 { "bad-request", BadRequest }, 0194 { "conflict", Conflict }, 0195 { "feature-not-implemented", FeatureNotImplemented }, 0196 { "forbidden", Forbidden }, 0197 { "gone", Gone }, 0198 { "internal-server-error", InternalServerError }, 0199 { "item-not-found", ItemNotFound }, 0200 { "jid-malformed", JidMalformed }, 0201 { "not-acceptable", NotAcceptable }, 0202 { "not-allowed", NotAllowed }, 0203 { "not-authorized", NotAuthorized }, 0204 { "payment-required", PaymentRequired }, 0205 { "recipient-unavailable", RecipientUnavailable }, 0206 { "redirect", Redirect }, 0207 { "registration-required", RegistrationRequired }, 0208 { "remote-server-not-found", RemoteServerNotFound }, 0209 { "remote-server-timeout", RemoteServerTimeout }, 0210 { "resource-constraint", ResourceConstraint }, 0211 { "service-unavailable", ServiceUnavailable }, 0212 { "subscription-required", SubscriptionRequired }, 0213 { "undefined-condition", UndefinedCondition }, 0214 { "unexpected-request", UnexpectedRequest }, 0215 { 0, 0 }, 0216 }; 0217 0218 Stanza::Error::Private::ErrorCodeEntry Stanza::Error::Private::errorCodeTable[] = 0219 { 0220 { BadRequest, Modify, 400 }, 0221 { Conflict, Cancel, 409 }, 0222 { FeatureNotImplemented, Cancel, 501 }, 0223 { Forbidden, Auth, 403 }, 0224 { Gone, Modify, 302 }, // permanent 0225 { InternalServerError, Wait, 500 }, 0226 { ItemNotFound, Cancel, 404 }, 0227 { JidMalformed, Modify, 400 }, 0228 { NotAcceptable, Modify, 406 }, 0229 { NotAllowed, Cancel, 405 }, 0230 { NotAuthorized, Auth, 401 }, 0231 { PaymentRequired, Auth, 402 }, 0232 { RecipientUnavailable, Wait, 404 }, 0233 { Redirect, Modify, 302 }, // temporary 0234 { RegistrationRequired, Auth, 407 }, 0235 { RemoteServerNotFound, Cancel, 404 }, 0236 { RemoteServerTimeout, Wait, 504 }, 0237 { ResourceConstraint, Wait, 500 }, 0238 { ServiceUnavailable, Cancel, 503 }, 0239 { SubscriptionRequired, Auth, 407 }, 0240 { UndefinedCondition, Wait, 500 }, // Note: any type matches really 0241 { UnexpectedRequest, Wait, 400 }, 0242 { 0, 0, 0 }, 0243 }; 0244 0245 Stanza::Error::Private::ErrorDescEntry Stanza::Error::Private::errorDescriptions[] = 0246 { 0247 { BadRequest, QT_TR_NOOP("Bad request"), QT_TR_NOOP("The sender has sent XML that is malformed or that cannot be processed.") }, 0248 { Conflict, QT_TR_NOOP("Conflict"), QT_TR_NOOP("Access cannot be granted because an existing resource or session exists with the same name or address.") }, 0249 { FeatureNotImplemented, QT_TR_NOOP("Feature not implemented"), QT_TR_NOOP("The feature requested is not implemented by the recipient or server and therefore cannot be processed.") }, 0250 { Forbidden, QT_TR_NOOP("Forbidden"), QT_TR_NOOP("The requesting entity does not possess the required permissions to perform the action.") }, 0251 { Gone, QT_TR_NOOP("Gone"), QT_TR_NOOP("The recipient or server can no longer be contacted at this address.") }, 0252 { InternalServerError, QT_TR_NOOP("Internal server error"), QT_TR_NOOP("The server could not process the stanza because of a misconfiguration or an otherwise-undefined internal server error.") }, 0253 { ItemNotFound, QT_TR_NOOP("Item not found"), QT_TR_NOOP("The addressed JID or item requested cannot be found.") }, 0254 { JidMalformed, QT_TR_NOOP("JID malformed"), QT_TR_NOOP("The sending entity has provided or communicated an XMPP address (e.g., a value of the 'to' attribute) or aspect thereof (e.g., a resource identifier) that does not adhere to the syntax defined in Addressing Scheme.") }, 0255 { NotAcceptable, QT_TR_NOOP("Not acceptable"), QT_TR_NOOP("The recipient or server understands the request but is refusing to process it because it does not meet criteria defined by the recipient or server (e.g., a local policy regarding acceptable words in messages).") }, 0256 { NotAllowed, QT_TR_NOOP("Not allowed"), QT_TR_NOOP("The recipient or server does not allow any entity to perform the action.") }, 0257 { NotAuthorized, QT_TR_NOOP("Not authorized"), QT_TR_NOOP("The sender must provide proper credentials before being allowed to perform the action, or has provided improper credentials.") }, 0258 { PaymentRequired, QT_TR_NOOP("Payment required"), QT_TR_NOOP("The requesting entity is not authorized to access the requested service because payment is required.") }, 0259 { RecipientUnavailable, QT_TR_NOOP("Recipient unavailable"), QT_TR_NOOP("The intended recipient is temporarily unavailable.") }, 0260 { Redirect, QT_TR_NOOP("Redirect"), QT_TR_NOOP("The recipient or server is redirecting requests for this information to another entity, usually temporarily.") }, 0261 { RegistrationRequired, QT_TR_NOOP("Registration required"), QT_TR_NOOP("The requesting entity is not authorized to access the requested service because registration is required.") }, 0262 { RemoteServerNotFound, QT_TR_NOOP("Remote server not found"), QT_TR_NOOP("A remote server or service specified as part or all of the JID of the intended recipient does not exist.") }, 0263 { RemoteServerTimeout, QT_TR_NOOP("Remote server timeout"), QT_TR_NOOP("A remote server or service specified as part or all of the JID of the intended recipient (or required to fulfill a request) could not be contacted within a reasonable amount of time.") }, 0264 { ResourceConstraint, QT_TR_NOOP("Resource constraint"), QT_TR_NOOP("The server or recipient lacks the system resources necessary to service the request.") }, 0265 { ServiceUnavailable, QT_TR_NOOP("Service unavailable"), QT_TR_NOOP("The server or recipient does not currently provide the requested service.") }, 0266 { SubscriptionRequired, QT_TR_NOOP("Subscription required"), QT_TR_NOOP("The requesting entity is not authorized to access the requested service because a subscription is required.") }, 0267 { UndefinedCondition, QT_TR_NOOP("Undefined condition"), QT_TR_NOOP("The error condition is not one of those defined by the other conditions in this list.") }, 0268 { UnexpectedRequest, QT_TR_NOOP("Unexpected request"), QT_TR_NOOP("The recipient or server understood the request but was not expecting it at this time (e.g., the request was out of order).") }, 0269 }; 0270 0271 /** 0272 \brief Returns the error code 0273 0274 If the error object was constructed with a code, this code will be returned. 0275 Otherwise, the code will be guessed. 0276 0277 0 means unknown code. 0278 */ 0279 int Stanza::Error::code() const 0280 { 0281 return originalCode ? originalCode : Private::errorTypeCondToCode(type, condition); 0282 } 0283 0284 /** 0285 \brief Creates a StanzaError from \a code. 0286 0287 The error's type and condition are guessed from the give \a code. 0288 The application-specific error element is preserved. 0289 */ 0290 bool Stanza::Error::fromCode(int code) 0291 { 0292 QPair<int, int> guess = Private::errorCodeToTypeCond(code); 0293 if(guess.first == -1 || guess.second == -1) 0294 return false; 0295 0296 type = guess.first; 0297 condition = guess.second; 0298 originalCode = code; 0299 0300 return true; 0301 } 0302 0303 /** 0304 \brief Reads the error from XML 0305 0306 This function finds and reads the error element \a e. 0307 0308 You need to provide the base namespace of the stream which this stanza belongs to 0309 (probably by using stream.baseNS() function). 0310 */ 0311 bool Stanza::Error::fromXml(const QDomElement &e, const QString &baseNS) 0312 { 0313 if(e.tagName() != "error" && e.namespaceURI() != baseNS) 0314 return false; 0315 0316 // type 0317 type = Private::stringToErrorType(e.attribute("type")); 0318 0319 // condition 0320 QDomNodeList nl = e.childNodes(); 0321 QDomElement t; 0322 condition = -1; 0323 int n; 0324 for(n = 0; n < nl.count(); ++n) { 0325 QDomNode i = nl.item(n); 0326 t = i.toElement(); 0327 if(!t.isNull()) { 0328 // FIX-ME: this shouldn't be needed 0329 if(t.namespaceURI() == NS_STANZAS || t.attribute("xmlns") == NS_STANZAS) { 0330 condition = Private::stringToErrorCond(t.tagName()); 0331 if (condition != -1) 0332 break; 0333 } 0334 } 0335 } 0336 0337 // code 0338 originalCode = e.attribute("code").toInt(); 0339 0340 // try to guess type/condition 0341 if(type == -1 || condition == -1) { 0342 QPair<int, int> guess(-1, -1); 0343 if (originalCode) 0344 guess = Private::errorCodeToTypeCond(originalCode); 0345 0346 if (type == -1) 0347 type = guess.first != -1 ? guess.first : Cancel; 0348 if (condition == -1) 0349 condition = guess.second != -1 ? guess.second : UndefinedCondition; 0350 } 0351 0352 // text 0353 t = e.elementsByTagNameNS(NS_STANZAS, "text").item(0).toElement(); 0354 if(!t.isNull()) 0355 text = t.text().trimmed(); 0356 else 0357 text = e.text().trimmed(); 0358 0359 // appspec: find first non-standard namespaced element 0360 appSpec = QDomElement(); 0361 nl = e.childNodes(); 0362 for(n = 0; n < nl.count(); ++n) { 0363 QDomNode i = nl.item(n); 0364 if(i.isElement() && i.namespaceURI() != NS_STANZAS) { 0365 appSpec = i.toElement(); 0366 break; 0367 } 0368 } 0369 0370 return true; 0371 } 0372 0373 /** 0374 \brief Writes the error to XML 0375 0376 This function creates an error element representing the error object. 0377 0378 You need to provide the base namespace of the stream to which this stanza belongs to 0379 (probably by using stream.baseNS() function). 0380 */ 0381 QDomElement Stanza::Error::toXml(QDomDocument &doc, const QString &baseNS) const 0382 { 0383 QDomElement errElem = doc.createElementNS(baseNS, "error"); 0384 QDomElement t; 0385 0386 // XMPP error 0387 QString stype = Private::errorTypeToString(type); 0388 if(stype.isEmpty()) 0389 return errElem; 0390 QString scond = Private::errorCondToString(condition); 0391 if(scond.isEmpty()) 0392 return errElem; 0393 0394 errElem.setAttribute("type", stype); 0395 errElem.appendChild(t = doc.createElementNS(NS_STANZAS, scond)); 0396 t.setAttribute("xmlns", NS_STANZAS); // FIX-ME: this shouldn't be needed 0397 0398 // old code 0399 int scode = code(); 0400 if(scode) 0401 errElem.setAttribute("code", scode); 0402 0403 // text 0404 if(!text.isEmpty()) { 0405 t = doc.createElementNS(NS_STANZAS, "text"); 0406 t.setAttribute("xmlns", NS_STANZAS); // FIX-ME: this shouldn't be needed 0407 t.appendChild(doc.createTextNode(text)); 0408 errElem.appendChild(t); 0409 } 0410 0411 // application specific 0412 errElem.appendChild(appSpec); 0413 0414 return errElem; 0415 } 0416 0417 /** 0418 \brief Returns the error name and description 0419 0420 Returns the error name (e.g. "Not Allowed") and generic description. 0421 */ 0422 QPair<QString,QString> Stanza::Error::description() const 0423 { 0424 return Private::errorCondToDesc(condition); 0425 } 0426 0427 //---------------------------------------------------------------------------- 0428 // Stanza 0429 //---------------------------------------------------------------------------- 0430 class Stanza::Private 0431 { 0432 public: 0433 static int stringToKind(const QString &s) 0434 { 0435 if(s == "message") 0436 return Message; 0437 else if(s == "presence") 0438 return Presence; 0439 else if(s == "iq") 0440 return IQ; 0441 else 0442 return -1; 0443 } 0444 0445 static QString kindToString(Kind k) 0446 { 0447 if(k == Message) 0448 return "message"; 0449 else if(k == Presence) 0450 return "presence"; 0451 else 0452 return "iq"; 0453 } 0454 0455 Stream *s; 0456 QDomElement e; 0457 }; 0458 0459 Stanza::Stanza() 0460 { 0461 d = 0; 0462 } 0463 0464 Stanza::Stanza(Stream *s, Kind k, const Jid &to, const QString &type, const QString &id) 0465 { 0466 Q_ASSERT(s); 0467 d = new Private; 0468 0469 Kind kind; 0470 if(k == Message || k == Presence || k == IQ) 0471 kind = k; 0472 else 0473 kind = Message; 0474 0475 d->s = s; 0476 if(d->s) 0477 d->e = d->s->doc().createElementNS(s->baseNS(), Private::kindToString(kind)); 0478 if(to.isValid()) 0479 setTo(to); 0480 if(!type.isEmpty()) 0481 setType(type); 0482 if(!id.isEmpty()) 0483 setId(id); 0484 } 0485 0486 Stanza::Stanza(Stream *s, const QDomElement &e) 0487 { 0488 Q_ASSERT(s); 0489 d = 0; 0490 if(e.namespaceURI() != s->baseNS()) 0491 return; 0492 int x = Private::stringToKind(e.tagName()); 0493 if(x == -1) 0494 return; 0495 d = new Private; 0496 d->s = s; 0497 d->e = e; 0498 } 0499 0500 Stanza::Stanza(const Stanza &from) 0501 { 0502 d = 0; 0503 *this = from; 0504 } 0505 0506 Stanza & Stanza::operator=(const Stanza &from) 0507 { 0508 delete d; 0509 d = 0; 0510 if(from.d) 0511 d = new Private(*from.d); 0512 return *this; 0513 } 0514 0515 Stanza::~Stanza() 0516 { 0517 delete d; 0518 } 0519 0520 bool Stanza::isNull() const 0521 { 0522 return (d ? false: true); 0523 } 0524 0525 QDomElement Stanza::element() const 0526 { 0527 return d->e; 0528 } 0529 0530 QString Stanza::toString() const 0531 { 0532 return Stream::xmlToString(d->e); 0533 } 0534 0535 QDomDocument & Stanza::doc() const 0536 { 0537 return d->s->doc(); 0538 } 0539 0540 QString Stanza::baseNS() const 0541 { 0542 return d->s->baseNS(); 0543 } 0544 0545 QDomElement Stanza::createElement(const QString &ns, const QString &tagName) 0546 { 0547 return d->s->doc().createElementNS(ns, tagName); 0548 } 0549 0550 QDomElement Stanza::createTextElement(const QString &ns, const QString &tagName, const QString &text) 0551 { 0552 QDomElement e = d->s->doc().createElementNS(ns, tagName); 0553 e.appendChild(d->s->doc().createTextNode(text)); 0554 return e; 0555 } 0556 0557 void Stanza::appendChild(const QDomElement &e) 0558 { 0559 d->e.appendChild(e); 0560 } 0561 0562 Stanza::Kind Stanza::kind() const 0563 { 0564 return (Kind)Private::stringToKind(d->e.tagName()); 0565 } 0566 0567 void Stanza::setKind(Kind k) 0568 { 0569 d->e.setTagName(Private::kindToString(k)); 0570 } 0571 0572 Jid Stanza::to() const 0573 { 0574 return Jid(d->e.attribute("to")); 0575 } 0576 0577 Jid Stanza::from() const 0578 { 0579 return Jid(d->e.attribute("from")); 0580 } 0581 0582 QString Stanza::id() const 0583 { 0584 return d->e.attribute("id"); 0585 } 0586 0587 QString Stanza::type() const 0588 { 0589 return d->e.attribute("type"); 0590 } 0591 0592 QString Stanza::lang() const 0593 { 0594 return d->e.attributeNS(NS_XML, "lang", QString()); 0595 } 0596 0597 void Stanza::setTo(const Jid &j) 0598 { 0599 d->e.setAttribute("to", j.full()); 0600 } 0601 0602 void Stanza::setFrom(const Jid &j) 0603 { 0604 d->e.setAttribute("from", j.full()); 0605 } 0606 0607 void Stanza::setId(const QString &id) 0608 { 0609 d->e.setAttribute("id", id); 0610 } 0611 0612 void Stanza::setType(const QString &type) 0613 { 0614 d->e.setAttribute("type", type); 0615 } 0616 0617 void Stanza::setLang(const QString &lang) 0618 { 0619 d->e.setAttribute("xml:lang", lang); 0620 } 0621 0622 Stanza::Error Stanza::error() const 0623 { 0624 Error err; 0625 QDomElement e = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); 0626 if(!e.isNull()) 0627 err.fromXml(e, d->s->baseNS()); 0628 0629 return err; 0630 } 0631 0632 void Stanza::setError(const Error &err) 0633 { 0634 QDomDocument doc = d->e.ownerDocument(); 0635 QDomElement errElem = err.toXml(doc, d->s->baseNS()); 0636 0637 QDomElement oldElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); 0638 if(oldElem.isNull()) { 0639 d->e.appendChild(errElem); 0640 } 0641 else { 0642 d->e.replaceChild(errElem, oldElem); 0643 } 0644 } 0645 0646 void Stanza::clearError() 0647 { 0648 QDomElement errElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); 0649 if(!errElem.isNull()) 0650 d->e.removeChild(errElem); 0651 } 0652