~ubuntu-branches/ubuntu/breezy/psi/breezy

« back to all changes in this revision

Viewing changes to iris/xmpp-core/xmlprotocol.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Jan Niehusmann
  • Date: 2004-06-15 00:10:41 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20040615001041-enywb6pcpe4sjsw6
Tags: 0.9.2-1
* New upstream release
* Set KDEDIR for ./configure so kde specific files get installed
* Don't install libpsiwidgets.so. It got installed in /usr/share
  where it doesn't belong. May be included (at a better location)
  later.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * xmlprotocol.cpp - state machine for 'jabber-like' protocols
 
3
 * Copyright (C) 2004  Justin Karneges
 
4
 *
 
5
 * This library is free software; you can redistribute it and/or
 
6
 * modify it under the terms of the GNU Lesser General Public
 
7
 * License as published by the Free Software Foundation; either
 
8
 * version 2.1 of the License, or (at your option) any later version.
 
9
 *
 
10
 * This library is distributed in the hope that it will be useful,
 
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
13
 * Lesser General Public License for more details.
 
14
 *
 
15
 * You should have received a copy of the GNU Lesser General Public
 
16
 * License along with this library; if not, write to the Free Software
 
17
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
18
 *
 
19
 */
 
20
 
 
21
#include"xmlprotocol.h"
 
22
 
 
23
#include"bytestream.h"
 
24
 
 
25
using namespace XMPP;
 
26
 
 
27
// stripExtraNS
 
28
//
 
29
// This function removes namespace information from various nodes for
 
30
// display purposes only (the element is pretty much useless for processing
 
31
// after this).  We do this because QXml is a bit overzealous about outputting
 
32
// redundant namespaces.
 
33
static QDomElement stripExtraNS(const QDomElement &e)
 
34
{
 
35
        // find closest parent with a namespace
 
36
        QDomNode par = e.parentNode();
 
37
        while(!par.isNull() && par.namespaceURI().isNull())
 
38
                par = par.parentNode();
 
39
        bool noShowNS = false;
 
40
        if(!par.isNull() && par.namespaceURI() == e.namespaceURI())
 
41
                noShowNS = true;
 
42
 
 
43
        // build qName (prefix:localName)
 
44
        QString qName;
 
45
        if(!e.prefix().isEmpty())
 
46
                qName = e.prefix() + ':' + e.localName();
 
47
        else
 
48
                qName = e.tagName();
 
49
 
 
50
        QDomElement i;
 
51
        uint x;
 
52
        if(noShowNS)
 
53
                i = e.ownerDocument().createElement(qName);
 
54
        else
 
55
                i = e.ownerDocument().createElementNS(e.namespaceURI(), qName);
 
56
 
 
57
        // copy attributes
 
58
        QDomNamedNodeMap al = e.attributes();
 
59
        for(x = 0; x < al.count(); ++x) {
 
60
                QDomAttr a = al.item(x).cloneNode().toAttr();
 
61
 
 
62
                // don't show xml namespace
 
63
                if(a.namespaceURI() == NS_XML)
 
64
                        i.setAttribute(QString("xml:") + a.name(), a.value());
 
65
                else
 
66
                        i.setAttributeNodeNS(a);
 
67
        }
 
68
 
 
69
        // copy children
 
70
        QDomNodeList nl = e.childNodes();
 
71
        for(x = 0; x < nl.count(); ++x) {
 
72
                QDomNode n = nl.item(x);
 
73
                if(n.isElement())
 
74
                        i.appendChild(stripExtraNS(n.toElement()));
 
75
                else
 
76
                        i.appendChild(n.cloneNode());
 
77
        }
 
78
        return i;
 
79
}
 
80
 
 
81
// xmlToString
 
82
//
 
83
// This function converts a QDomElement into a QString, using stripExtraNS
 
84
// to make it pretty.
 
85
static QString xmlToString(const QDomElement &e, const QString &fakeNS, const QString &fakeQName, bool clip)
 
86
{
 
87
        QDomElement i = e.cloneNode().toElement();
 
88
 
 
89
        // It seems QDom can only have one namespace attribute at a time (see docElement 'HACK').
 
90
        // Fortunately we only need one kind depending on the input, so it is specified here.
 
91
        QDomElement fake = e.ownerDocument().createElementNS(fakeNS, fakeQName);
 
92
        fake.appendChild(i);
 
93
        fake = stripExtraNS(fake);
 
94
        QString out;
 
95
        {
 
96
                QTextStream ts(&out, IO_WriteOnly);
 
97
                fake.firstChild().save(ts, 0);
 
98
        }
 
99
        // 'clip' means to remove any unwanted (and unneeded) characters, such as a trailing newline
 
100
        if(clip) {
 
101
                int n = out.findRev('>');
 
102
                out.truncate(n+1);
 
103
        }
 
104
        return out;
 
105
}
 
106
 
 
107
// createRootXmlTags
 
108
//
 
109
// This function creates three QStrings, one being an <?xml .. ?> processing
 
110
// instruction, and the others being the opening and closing tags of an
 
111
// element, <foo> and </foo>.  This basically allows us to get the raw XML
 
112
// text needed to open/close an XML stream, without resorting to generating
 
113
// the XML ourselves.  This function uses QDom to do the generation, which
 
114
// ensures proper encoding and entity output.
 
115
static void createRootXmlTags(const QDomElement &root, QString *xmlHeader, QString *tagOpen, QString *tagClose)
 
116
{
 
117
        QDomElement e = root.cloneNode(false).toElement();
 
118
 
 
119
        // insert a dummy element to ensure open and closing tags are generated
 
120
        QDomElement dummy = e.ownerDocument().createElement("dummy");
 
121
        e.appendChild(dummy);
 
122
 
 
123
        // convert to xml->text
 
124
        QString str;
 
125
        {
 
126
                QTextStream ts(&str, IO_WriteOnly);
 
127
                e.save(ts, 0);
 
128
        }
 
129
 
 
130
        // parse the tags out
 
131
        int n = str.find('<');
 
132
        int n2 = str.find('>', n);
 
133
        ++n2;
 
134
        *tagOpen = str.mid(n, n2-n);
 
135
        n2 = str.findRev('>');
 
136
        n = str.findRev('<');
 
137
        ++n2;
 
138
        *tagClose = str.mid(n, n2-n);
 
139
 
 
140
        // generate a nice xml processing header
 
141
        *xmlHeader = "<?xml version=\"1.0\"?>";
 
142
}
 
143
 
 
144
//----------------------------------------------------------------------------
 
145
// Protocol
 
146
//----------------------------------------------------------------------------
 
147
XmlProtocol::TransferItem::TransferItem()
 
148
{
 
149
}
 
150
 
 
151
XmlProtocol::TransferItem::TransferItem(const QString &_str, bool sent, bool external)
 
152
{
 
153
        isString = true;
 
154
        isSent = sent;
 
155
        isExternal = external;
 
156
        str = _str;
 
157
}
 
158
 
 
159
XmlProtocol::TransferItem::TransferItem(const QDomElement &_elem, bool sent, bool external)
 
160
{
 
161
        isString = false;
 
162
        isSent = sent;
 
163
        isExternal = external;
 
164
        elem = _elem;
 
165
}
 
166
 
 
167
XmlProtocol::XmlProtocol()
 
168
{
 
169
        init();
 
170
}
 
171
 
 
172
XmlProtocol::~XmlProtocol()
 
173
{
 
174
}
 
175
 
 
176
void XmlProtocol::init()
 
177
{
 
178
        incoming = false;
 
179
        peerClosed = false;
 
180
        closeWritten = false;
 
181
}
 
182
 
 
183
void XmlProtocol::reset()
 
184
{
 
185
        init();
 
186
 
 
187
        elem = QDomElement();
 
188
        tagOpen = QString();
 
189
        tagClose = QString();
 
190
        xml.reset();
 
191
        outData.resize(0);
 
192
        trackQueue.clear();
 
193
        transferItemList.clear();
 
194
}
 
195
 
 
196
void XmlProtocol::addIncomingData(const QByteArray &a)
 
197
{
 
198
        xml.appendData(a);
 
199
}
 
200
 
 
201
QByteArray XmlProtocol::takeOutgoingData()
 
202
{
 
203
        QByteArray a = outData.copy();
 
204
        outData.resize(0);
 
205
        return a;
 
206
}
 
207
 
 
208
void XmlProtocol::outgoingDataWritten(int bytes)
 
209
{
 
210
        for(QValueList<TrackItem>::Iterator it = trackQueue.begin(); it != trackQueue.end();) {
 
211
                TrackItem &i = *it;
 
212
 
 
213
                // enough bytes?
 
214
                if(bytes < i.size) {
 
215
                        i.size -= bytes;
 
216
                        break;
 
217
                }
 
218
                int type = i.type;
 
219
                int id = i.id;
 
220
                int size = i.size;
 
221
                bytes -= i.size;
 
222
                it = trackQueue.remove(it);
 
223
 
 
224
                if(type == TrackItem::Raw) {
 
225
                        // do nothing
 
226
                }
 
227
                else if(type == TrackItem::Close) {
 
228
                        closeWritten = true;
 
229
                }
 
230
                else if(type == TrackItem::Custom) {
 
231
                        itemWritten(id, size);
 
232
                }
 
233
        }
 
234
}
 
235
 
 
236
bool XmlProtocol::processStep()
 
237
{
 
238
        Parser::Event pe;
 
239
        notify = 0;
 
240
        transferItemList.clear();
 
241
 
 
242
        if(state != Closing && (state == RecvOpen || stepAdvancesParser())) {
 
243
                // if we get here, then it's because we're in some step that advances the parser
 
244
                pe = xml.readNext();
 
245
                if(!pe.isNull()) {
 
246
                        // note: error/close events should be handled for ALL steps, so do them here
 
247
                        switch(pe.type()) {
 
248
                                case Parser::Event::DocumentOpen: {
 
249
                                        transferItemList += TransferItem(pe.actualString(), false);
 
250
 
 
251
                                        //stringRecv(pe.actualString());
 
252
                                        break;
 
253
                                }
 
254
                                case Parser::Event::DocumentClose: {
 
255
                                        transferItemList += TransferItem(pe.actualString(), false);
 
256
 
 
257
                                        //stringRecv(pe.actualString());
 
258
                                        if(incoming) {
 
259
                                                sendTagClose();
 
260
                                                event = ESend;
 
261
                                                peerClosed = true;
 
262
                                                state = Closing;
 
263
                                        }
 
264
                                        else {
 
265
                                                event = EPeerClosed;
 
266
                                        }
 
267
                                        return true;
 
268
                                }
 
269
                                case Parser::Event::Element: {
 
270
                                        transferItemList += TransferItem(pe.element(), false);
 
271
 
 
272
                                        //elementRecv(pe.element());
 
273
                                        break;
 
274
                                }
 
275
                                case Parser::Event::Error: {
 
276
                                        if(incoming) {
 
277
                                                // If we get a parse error during the initial element exchange,
 
278
                                                // flip immediately into 'open' mode so that we can report an error.
 
279
                                                if(state == RecvOpen) {
 
280
                                                        sendTagOpen();
 
281
                                                        state = Open;
 
282
                                                }
 
283
                                                return handleError();
 
284
                                        }
 
285
                                        else {
 
286
                                                event = EError;
 
287
                                                errorCode = ErrParse;
 
288
                                                return true;
 
289
                                        }
 
290
                                }
 
291
                        }
 
292
                }
 
293
                else {
 
294
                        if(state == RecvOpen || stepRequiresElement()) {
 
295
                                need = NNotify;
 
296
                                notify |= NRecv;
 
297
                                return false;
 
298
                        }
 
299
                }
 
300
        }
 
301
 
 
302
        return baseStep(pe);
 
303
}
 
304
 
 
305
QString XmlProtocol::xmlEncoding() const
 
306
{
 
307
        return xml.encoding();
 
308
}
 
309
 
 
310
QString XmlProtocol::elementToString(const QDomElement &e, bool clip)
 
311
{
 
312
        if(elem.isNull())
 
313
                elem = elemDoc.importNode(docElement(), true).toElement();
 
314
 
 
315
        // Determine the appropriate 'fakeNS' to use
 
316
        QString ns;
 
317
 
 
318
        // first, check root namespace
 
319
        QString pre = e.prefix();
 
320
        if(pre.isNull())
 
321
                pre = "";
 
322
        if(pre == elem.prefix()) {
 
323
                ns = elem.namespaceURI();
 
324
        }
 
325
        else {
 
326
                // scan the root attributes for 'xmlns' (oh joyous hacks)
 
327
                QDomNamedNodeMap al = elem.attributes();
 
328
                uint n;
 
329
                for(n = 0; n < al.count(); ++n) {
 
330
                        QDomAttr a = al.item(n).toAttr();
 
331
                        QString s = a.name();
 
332
                        int x = s.find(':');
 
333
                        if(x != -1)
 
334
                                s = s.mid(x+1);
 
335
                        else
 
336
                                s = "";
 
337
                        if(pre == s) {
 
338
                                ns = a.value();
 
339
                                break;
 
340
                        }
 
341
                }
 
342
                if(n >= al.count()) {
 
343
                        // if we get here, then no appropriate ns was found.  use root then..
 
344
                        ns = elem.namespaceURI();
 
345
                }
 
346
        }
 
347
 
 
348
        // build qName
 
349
        QString qn;
 
350
        if(!elem.prefix().isEmpty())
 
351
                qn = elem.prefix() + ':';
 
352
        qn += elem.localName();
 
353
 
 
354
        // make the string
 
355
        return xmlToString(e, ns, qn, clip);
 
356
}
 
357
 
 
358
bool XmlProtocol::stepRequiresElement() const
 
359
{
 
360
        // default returns false
 
361
        return false;
 
362
}
 
363
 
 
364
void XmlProtocol::itemWritten(int, int)
 
365
{
 
366
        // default does nothing
 
367
}
 
368
 
 
369
void XmlProtocol::stringSend(const QString &)
 
370
{
 
371
        // default does nothing
 
372
}
 
373
 
 
374
void XmlProtocol::stringRecv(const QString &)
 
375
{
 
376
        // default does nothing
 
377
}
 
378
 
 
379
void XmlProtocol::elementSend(const QDomElement &)
 
380
{
 
381
        // default does nothing
 
382
}
 
383
 
 
384
void XmlProtocol::elementRecv(const QDomElement &)
 
385
{
 
386
        // default does nothing
 
387
}
 
388
 
 
389
void XmlProtocol::startConnect()
 
390
{
 
391
        incoming = false;
 
392
        state = SendOpen;
 
393
}
 
394
 
 
395
void XmlProtocol::startAccept()
 
396
{
 
397
        incoming = true;
 
398
        state = RecvOpen;
 
399
}
 
400
 
 
401
bool XmlProtocol::close()
 
402
{
 
403
        sendTagClose();
 
404
        event = ESend;
 
405
        state = Closing;
 
406
        return true;
 
407
}
 
408
 
 
409
int XmlProtocol::writeString(const QString &s, int id, bool external)
 
410
{
 
411
        transferItemList += TransferItem(s, true, external);
 
412
        return internalWriteString(s, TrackItem::Custom, id);
 
413
}
 
414
 
 
415
int XmlProtocol::writeElement(const QDomElement &e, int id, bool external, bool clip)
 
416
{
 
417
        if(e.isNull())
 
418
                return 0;
 
419
        transferItemList += TransferItem(e, true, external);
 
420
 
 
421
        //elementSend(e);
 
422
        QString out = elementToString(e, clip);
 
423
        return internalWriteString(out, TrackItem::Custom, id);
 
424
}
 
425
 
 
426
QByteArray XmlProtocol::resetStream()
 
427
{
 
428
        // reset the state
 
429
        if(incoming)
 
430
                state = RecvOpen;
 
431
        else
 
432
                state = SendOpen;
 
433
 
 
434
        // grab unprocessed data before resetting
 
435
        QByteArray spare = xml.unprocessed();
 
436
        xml.reset();
 
437
        return spare;
 
438
}
 
439
 
 
440
int XmlProtocol::internalWriteData(const QByteArray &a, TrackItem::Type t, int id)
 
441
{
 
442
        TrackItem i;
 
443
        i.type = t;
 
444
        i.id = id;
 
445
        i.size = a.size();
 
446
        trackQueue += i;
 
447
 
 
448
        ByteStream::appendArray(&outData, a);
 
449
        return a.size();
 
450
}
 
451
 
 
452
int XmlProtocol::internalWriteString(const QString &s, TrackItem::Type t, int id)
 
453
{
 
454
        QCString cs = s.utf8();
 
455
        QByteArray a(cs.length());
 
456
        memcpy(a.data(), cs.data(), a.size());
 
457
        return internalWriteData(a, t, id);
 
458
}
 
459
 
 
460
void XmlProtocol::sendTagOpen()
 
461
{
 
462
        if(elem.isNull())
 
463
                elem = elemDoc.importNode(docElement(), true).toElement();
 
464
 
 
465
        QString xmlHeader;
 
466
        createRootXmlTags(elem, &xmlHeader, &tagOpen, &tagClose);
 
467
 
 
468
        QString s;
 
469
        s += xmlHeader + '\n';
 
470
        s += tagOpen + '\n';
 
471
 
 
472
        transferItemList += TransferItem(xmlHeader, true);
 
473
        transferItemList += TransferItem(tagOpen, true);
 
474
 
 
475
        //stringSend(xmlHeader);
 
476
        //stringSend(tagOpen);
 
477
        internalWriteString(s, TrackItem::Raw);
 
478
}
 
479
 
 
480
void XmlProtocol::sendTagClose()
 
481
{
 
482
        transferItemList += TransferItem(tagClose, true);
 
483
 
 
484
        //stringSend(tagClose);
 
485
        internalWriteString(tagClose, TrackItem::Close);
 
486
}
 
487
 
 
488
bool XmlProtocol::baseStep(const Parser::Event &pe)
 
489
{
 
490
        // Basic
 
491
        if(state == SendOpen) {
 
492
                sendTagOpen();
 
493
                event = ESend;
 
494
                if(incoming)
 
495
                        state = Open;
 
496
                else
 
497
                        state = RecvOpen;
 
498
                return true;
 
499
        }
 
500
        else if(state == RecvOpen) {
 
501
                if(incoming)
 
502
                        state = SendOpen;
 
503
                else
 
504
                        state = Open;
 
505
 
 
506
                // note: event will always be DocumentOpen here
 
507
                handleDocOpen(pe);
 
508
                event = ERecvOpen;
 
509
                return true;
 
510
        }
 
511
        else if(state == Open) {
 
512
                QDomElement e;
 
513
                if(pe.type() == Parser::Event::Element)
 
514
                        e = pe.element();
 
515
                return doStep(e);
 
516
        }
 
517
        // Closing
 
518
        else {
 
519
                if(closeWritten) {
 
520
                        if(peerClosed) {
 
521
                                event = EPeerClosed;
 
522
                                return true;
 
523
                        }
 
524
                        else
 
525
                                return handleCloseFinished();
 
526
                }
 
527
 
 
528
                need = NNotify;
 
529
                notify = NSend;
 
530
                return false;
 
531
        }
 
532
}
 
533
 
 
534
void XmlProtocol::setIncomingAsExternal()
 
535
{
 
536
        for(QValueList<TransferItem>::Iterator it = transferItemList.begin(); it != transferItemList.end(); ++it) {
 
537
                TransferItem &i = *it;
 
538
                // look for elements received
 
539
                if(!i.isString && !i.isSent)
 
540
                        i.isExternal = true;
 
541
        }
 
542
}
 
543