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