reSIProcate
SIP stack, Dialog Usage Manager and related libraries
Loading...
Searching...
No Matches
XMLCursor.cxx
Go to the documentation of this file.
1#if defined(HAVE_CONFIG_H)
2#include "config.h"
3#endif
4
5#include "rutil/XMLCursor.hxx"
6#include "rutil/Logger.hxx"
8
9using namespace resip;
10using namespace std;
11
12#define RESIPROCATE_SUBSYSTEM Subsystem::CONTENTS
13
39static char BANG[] = "!";
40static char HYPHEN[] = "-";
41static char LA_QUOTE[] = "<";
42static char RA_QUOTE[] = ">";
43static char SLASH[] = "/";
44static char EQUALS[] = "=";
45static char DOUBLE_QUOTE[] = "\"";
46static char SINGLE_QUOTE[] = "\'";
47//http://www.w3.org/TR/1998/REC-xml-19980210
48static const Data COMMENT_START("<!--");
49static const Data COMMENT_END("-->");
50static const Data QUESTION_RA_QUOTE("?>");
51
52// An alternative to stripping comments out in preparse
53// is to deal with them in the parse; ignore when after non-leaf element
54// put a leaf after a comment after a leaf in the first leaf's children
55// getValue() needs to copy first leaf and all 'child' leaves to mValue
56//
57// has the advantage of allowing
58// 1. lazier parsing
59// 2. embedded weirdnesses like <! > and <? >
61 : mCursor(nullptr),
62 mAttributesSet(false)
63{
64 ParseBuffer lPb(pb);
65
66 skipProlog(lPb);
67 const char* start = lPb.position();
68
70 if (!lPb.eof())
71 {
72 StackLog(<< "removing comments");
73 lPb.reset(start);
74 mData.reserve((Data::size_type)(lPb.end() - lPb.start()));
75
76 const char* anchor = start;
77 {
78 DataStream str(mData);
79 Data temp;
80 while (true)
81 {
83 if (!lPb.eof())
84 {
85 lPb.data(temp, anchor);
86 str << temp;
87 anchor = Node::skipComments(lPb);
88 }
89 else
90 {
91 lPb.data(temp, anchor);
92 str << temp;
93 break;
94 }
95 }
96 }
97 mRoot.reset(new Node(ParseBuffer(mData.data(), mData.size())));
98 }
99 else
100 {
101 mRoot.reset(new Node(ParseBuffer(start, pb.end() - start)));
102 }
103 mCursor = mRoot.get();
104
105 if (mRoot->extractTag())
106 {
107 InfoLog(<< "XML: empty element no a legal root");
108 mRoot->mPb.fail(__FILE__, __LINE__);
109 }
110
111 mTag = mRoot->mTag;
112 decodeName(mRoot->mTag);
113
114 // check for # & and note -- make decode, decodeName do stuff if set
115
116 //<top></top> // no children
117 ParseBuffer pbtemp(mRoot->mPb);
118 pbtemp.skipToChar(RA_QUOTE[0]);
119 pbtemp.skipChar();
121 {
122 pbtemp.skipWhitespace();
123 }
124 if (*pbtemp.position() == LA_QUOTE[0] &&
125 *(pbtemp.position()+1) == SLASH[0])
126 {
127 pbtemp.skipChar();
128 pbtemp.skipChar();
129 if (strncmp(mRoot->mTag.data(), pbtemp.position(), mRoot->mTag.size()) == 0)
130 {
131 // no children ever
132 mRoot->mPb.reset(mRoot->mPb.end());
133 return;
134 }
135 }
136}
137
141
142void
143XMLCursor::skipProlog(ParseBuffer& pb)
144{
145 //'<?xml' VersionInfo '<xml?' EncodingDecl '?>'? '<?xml' SDDecl '?>'? S? '?>
146
147 // !dlb! much more complicated than this.. can contain comments
148 const char* start = pb.position();
150 if(pb.eof())
151 {
152 // No Prolog
153 pb.reset(start);
154 return;
155 }
156 pb.skipN(2);
157 pb.skipWhitespace();
158}
159
160void
161XMLCursor::decode(Data& text)
162{
163}
164
165void
166XMLCursor::decodeName(Data& name)
167{
168}
169
170void
171XMLCursor::parseNextRootChild()
172{
173 // no next child to parse?
174 if (mRoot->mPb.eof())
175 {
176 return;
177 }
178
179 // next child already parsed?
180 if (mRoot->mNext != mRoot->mChildren.end())
181 {
182 return;
183 }
184
185 // skip self tag
186 if (mRoot->mPb.position() == mRoot->mPb.start())
187 {
188 mRoot->mPb.skipToChar(RA_QUOTE[0]);
189 mRoot->mPb.skipChar();
190 }
191
193 {
194 mRoot->mPb.skipWhitespace();
195 }
196
197 // root end tag?
198 if (*mRoot->mPb.position() == LA_QUOTE[0])
199 {
200 ParseBuffer pb(mRoot->mPb.position(),
201 mRoot->mPb.end() - mRoot->mPb.position());
202 pb.skipChar();
203 if (!pb.eof() && *pb.position() == SLASH[0])
204 {
205 pb.skipChar();
206 // CodeWarrior isn't helpful enough to pick the "obvious" operator definition
207 // so we add volatile here so CW is completely unconfused what to do.
208 // second note - MSVC 7.0 won't compile the volatile - tried the following to fix
209 const char* end = pb.position();
210 if ( (const char*)pb.end() < end + mTag.size() )
211 {
212 InfoLog(<< "XML: unexpected end");
213 pb.fail(__FILE__, __LINE__);
214 }
215
216 if (strncmp(mTag.data(), pb.position(), mRoot->mTag.size()) == 0)
217 {
218 mRoot->mPb.skipToEnd();
219 return;
220 }
221 }
222 }
223
224 // leaf?
225 if (*mRoot->mPb.position() != LA_QUOTE[0])
226 {
227 const char* anchor = mRoot->mPb.position();
228 mRoot->mPb.skipToChar(LA_QUOTE[0]);
229 Node* leaf = new Node(ParseBuffer(anchor, mRoot->mPb.position() - anchor));
230 leaf->mIsLeaf = true;
231 mRoot->addChild(leaf);
232 }
233 else
234 {
235 // Owned here until it is attached to the root: the nodes parsed below
236 // it are already attached to it, so if the parse fails, freeing the
237 // child frees the whole partly parsed subtree.
238 std::unique_ptr<Node> child(new Node(mRoot->mPb));
239 child->skipToEndTag(1); // a child of the root is one level down
240
241 // leave the parse buffer after the child
242 mRoot->mPb.reset(child->mPb.end());
243
244 mRoot->addChild(child.release());
245 }
246
247 // mNext always points at cursored child
248 mRoot->mNext = mRoot->mChildren.end();
249 mRoot->mNext--;
250}
251
252bool
254{
255 if (atRoot())
256 {
257 StackLog(<< "XMLCursor::nextSibling" << *this->mCursor << " <<root>>");
258 return false;
259 }
260
261 StackLog(<< "XMLCursor::nextSibling" << *this->mCursor << " " << *this->mCursor->mParent);
262 if (mCursor->mParent == mRoot.get())
263 {
264 parseNextRootChild();
265 }
266
267 if (mCursor->mParent->mNext != mCursor->mParent->mChildren.end())
268 {
269 mCursor = *((mCursor->mParent->mNext)++);
270 mAttributesSet = false;
271 return true;
272 }
273 else
274 {
275 return false;
276 }
277}
278
279bool
281{
282 if (atRoot() &&
283 mRoot->mChildren.empty())
284 {
285 parseNextRootChild();
286 }
287
288 if (mCursor->mChildren.empty())
289 {
290 return false;
291 }
292 else
293 {
294 // mNext always points after cursored child
295 mCursor->mNext = mCursor->mChildren.begin();
296 mCursor->mNext++;
297 mCursor = mCursor->mChildren.front();
298 mAttributesSet = false;
299 return true;
300 }
301}
302
303bool
305{
306 if (atRoot())
307 {
308 return false;
309 }
310
311 mCursor = mCursor->mParent;
312 mAttributesSet = false;
313 return true;
314}
315
316void
318{
319 mCursor = mRoot.get();
320 mAttributesSet = false;
321}
322
323bool
325{
326 return mCursor == mRoot.get();
327}
328
329bool
331{
332 return mCursor->mIsLeaf;
333}
334
335const Data&
337{
338 return mCursor->mTag;
339}
340
341//<foo >
342//<foo>
343//<foo/>
344//<foo attr = 'value' attr="value">
345//<foo attr = 'value' attr="value" >
346//
347//<foo attr = 'value' attr="value" />
348static const Data RA_QUOTE_SLASH(">/");
351{
352 if (!atLeaf() &&
353 !mAttributesSet)
354 {
355 mAttributes.clear();
356
357 ParseBuffer pb(mCursor->mPb);
358 pb.reset(mCursor->mPb.start());
359
360 Data attribute;
361 Data value;
362
364 pb.skipWhitespace();
365
366 while (!pb.eof() &&
367 *pb.position() != RA_QUOTE[0] &&
368 *pb.position() != SLASH[0])
369 {
370 attribute.clear();
371 value.clear();
372
373 const char* anchor = pb.skipWhitespace();
375 pb.data(attribute, anchor);
376 XMLCursor::decodeName(attribute);
377
378 StackLog(<< "attribute: " << attribute);
379
380 pb.skipWhitespace();
381 pb.skipToChar(EQUALS[0]);
382 pb.skipChar();
383 pb.skipWhitespace();
384 if (!pb.eof())
385 {
386 const char quote = *pb.position();
387
388 StackLog(<< "quote is <" << quote << ">");
389
390 if(quote != DOUBLE_QUOTE[0] &&
391 quote != SINGLE_QUOTE[0])
392 {
393 InfoLog(<< "XML: badly quoted attribute value");
394 pb.fail(__FILE__, __LINE__);
395 }
396 anchor = pb.skipChar();
397 pb.skipToChar(quote);
398 pb.data(value, anchor);
399 XMLCursor::decode(value);
400 pb.skipChar();
401 if (mAttributes.size() >= MaxAttributes && mAttributes.count(attribute) == 0)
402 {
403 InfoLog(<< "XML: too many attributes on element " << getTag());
404 pb.fail(__FILE__, __LINE__, "Too many XML attributes");
405 }
406 mAttributes[attribute] = value;
407 }
408 pb.skipWhitespace();
409 }
410 // Only once complete, so a failed parse fails again rather than leaving a partial map
411 mAttributesSet = true;
412 }
413
414 return mAttributes;
415}
416
417const Data&
419{
420 if (atLeaf())
421 {
422 ParseBuffer pb(mCursor->mPb);
423 pb.skipToEnd();
424 pb.data(mValue, pb.start());
425 XMLCursor::decode(mValue);
426 }
427 else
428 {
429 mValue.clear();
430 }
431 return mValue;
432}
433
435XMLCursor::encode(EncodeStream& str, const AttributeMap& attrs)
436{
437 for(AttributeMap::const_iterator i = attrs.begin();
438 i != attrs.end(); ++i)
439 {
440 if (i != attrs.begin())
441 {
442 str << " ";
443 }
444 // !dlb! some sort of character encoding required here
445 str << i->first << "=\"" << i->second << "\"";
446 }
447
448 return str;
449}
450
452 : mPb(pb.position(), pb.end() - pb.position()),
453 mParent(0),
454 mChildren(),
455 mNext(mChildren.begin()),
456 mIsLeaf(false)
457{
459 StackLog(<< "XMLCursor::Node::Node" << *this);
460}
461
463{
464 for (vector<Node*>::iterator i = mChildren.begin();
465 i != mChildren.end(); ++i)
466 {
467 delete *i;
468 }
469}
470
471// start:
472//<foo >
473//^
474// end:
475//<foo >
476// ^
477static Data SLASH_RA_QUOTE("/>");
478bool
480{
481 ParseBuffer pb(mPb);
483 {
484 pb.skipWhitespace();
485 }
486 const char* anchor = pb.skipChar(LA_QUOTE[0]);
488 pb.assertNotEof();
489 pb.data(mTag, anchor);
490
491 return !pb.eof() && *pb.position() == SLASH[0];
492}
493
494void
496{
497 mChildren.push_back(child);
498 child->mParent = this;
499}
500
501//<foo> <bar> </bar> <baz> </baz> </foo>
502//^start
503// ^child
504// ^child
505// ^end
506//
507//<foo> sdfsf sadfsf <bar> asdfdf </bar> sadfsdf </foo>
508//^start
509// ^child
510// ^child sub
511// ^child
512void
514{
515 // Bound the recursion. Each nested element adds a stack frame here, and
516 // another in ~Node() when the tree is torn down, so without a limit a
517 // document with very deep nesting (many thousands of elements) would recurse
518 // until the stack is exhausted. Failing the parse throws ParseException,
519 // which callers already treat as a malformed body.
520 if (depth > MaxNestingDepth)
521 {
522 InfoLog(<< "XML: nesting too deep");
523 mPb.fail(__FILE__, __LINE__, "XML nesting too deep");
524 }
525
526 extractTag();
527 StackLog(<< "XMLCursor::Node::skipToEndTag(" << mTag << ")");
528 //StackLog(<< "XMLCursor::Node::skipToEndTag(" << Data(mPb.position(), mPb.end() - mPb.position()) << ")");
529
530 //<foo />
531 mPb.skipToChar(RA_QUOTE[0]);
532 if (*(mPb.position()-1) == SLASH[0])
533 {
534 mPb.skipChar();
535 mPb = ParseBuffer(mPb.start(), mPb.position() - mPb.start());
536 return;
537 }
538
539 //<foo> ...<child> ... </child> </foo>
540 // ^
541 mPb.skipChar();
542 //<foo> ...<child> ... </child> </foo>
543 // ^
544 while (true)
545 {
547 {
548 mPb.skipWhitespace();
549 }
550
551 // Some text contents ...<
552 // ^ ^
553 if (*mPb.position() != LA_QUOTE[0])
554 {
555 const char* anchor = mPb.position();
556 mPb.skipToChar(LA_QUOTE[0]);
557 Node* leaf = new Node(ParseBuffer(anchor, mPb.position() - anchor));
558 leaf->mIsLeaf = true;
559 addChild(leaf);
560 }
561
562 //<...
563 //^
564 mPb.skipChar();
565 //<...
566 // ^
567
568 // exit condition
569 //</foo>
570 if (*mPb.position() == SLASH[0])
571 {
572 mPb.skipChar();
573 // CodeWarrior isn't helpful enough to pick the "obvious" operator definition
574 // so we add volatile here so CW is completely unconfused what to do.
575 // second note - MSVC 7.0 won't compile the volatile - tried the following to fix
576 const char* end = mPb.position();
577 if ( (const char*)mPb.end() < end + mTag.size() )
578 {
579 InfoLog(<< "XML: unexpected end");
580 mPb.fail(__FILE__, __LINE__);
581 }
582
583 if (strncmp(mTag.data(), mPb.position(), mTag.size()) == 0)
584 {
585 mPb.skipToChar(RA_QUOTE[0]);
586 mPb.skipChar();
587 mPb = ParseBuffer(mPb.start(), mPb.position() - mPb.start());
588 return;
589 }
590 else
591 {
592 InfoLog(<< "Badly formed XML: unexpected endtag");
593 mPb.fail(__FILE__, __LINE__);
594 }
595 }
596
597 //<child>...
598 // ^
599 if (mPb.position() == mPb.start())
600 {
601 InfoLog(<< "XML: badly formed element");
602 mPb.fail(__FILE__, __LINE__);
603 }
604
605 mPb.reset(mPb.position()-1);
606 //<child>...
607 //^
608 Node* child = new Node(mPb);
609 addChild(child);
610 child->skipToEndTag(depth + 1);
611 mPb.reset(child->mPb.end());
612 XMLCursor::decodeName(child->mTag);
613 StackLog(<< mTag << "(" << child->mTag << ")");
614 }
615}
616
617//<!-- declarations for <head> & <body> -->
618const char*
620{
621 while (*pb.position() == LA_QUOTE[0] &&
622 *(pb.position()+1) == BANG[0] &&
623 *(pb.position()+2) == HYPHEN[0] &&
624 *(pb.position()+3) == HYPHEN[0])
625 {
628 pb.skipWhitespace();
629 if(pb.eof())
630 {
631 return pb.end();
632 }
633 }
634
635 return pb.position();
636}
637
639resip::operator<<(EncodeStream& str, const XMLCursor::Node& node)
640{
641 Data::size_type size = (Data::size_type)(node.mPb.end() - node.mPb.start());
642
643 static const Data::size_type showSize(35);
644
645 str << &node << "["
646 << Data(node.mPb.start(),
647 min(showSize, size))
648 << "]" << (size ? "" : "...");
649
650 return str;
651}
652
654resip::operator<<(EncodeStream& str, const XMLCursor& cursor)
655{
656 str << "XMLCursor " << *cursor.mCursor;
657 return str;
658}
659
660/* ====================================================================
661 * The Vovida Software License, Version 1.0
662 *
663 * Copyright (c) 2026 SIP Spectrum, Inc. https://www.sipspectrum.com
664 * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved.
665 *
666 * Redistribution and use in source and binary forms, with or without
667 * modification, are permitted provided that the following conditions
668 * are met:
669 *
670 * 1. Redistributions of source code must retain the above copyright
671 * notice, this list of conditions and the following disclaimer.
672 *
673 * 2. Redistributions in binary form must reproduce the above copyright
674 * notice, this list of conditions and the following disclaimer in
675 * the documentation and/or other materials provided with the
676 * distribution.
677 *
678 * 3. The names "VOCAL", "Vovida Open Communication Application Library",
679 * and "Vovida Open Communication Application Library (VOCAL)" must
680 * not be used to endorse or promote products derived from this
681 * software without prior written permission. For written
682 * permission, please contact vocal@vovida.org.
683 *
684 * 4. Products derived from this software may not be called "VOCAL", nor
685 * may "VOCAL" appear in their name, without prior written
686 * permission of Vovida Networks, Inc.
687 *
688 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
689 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
690 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
691 * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA
692 * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
693 * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
694 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
695 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
696 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
697 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
698 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
699 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
700 * DAMAGE.
701 *
702 * ====================================================================
703 *
704 * This software consists of voluntary contributions made by Vovida
705 * Networks, Inc. and many individuals on behalf of Vovida Networks,
706 * Inc. For more information on Vovida Networks, Inc., please see
707 * <http://www.vovida.org/>.
708 *
709 */
#define StackLog(args_)
Definition Logger.hxx:125
#define InfoLog(args_)
Definition Logger.hxx:131
static char RA_QUOTE[]
Definition XMLCursor.cxx:42
static char SINGLE_QUOTE[]
Definition XMLCursor.cxx:46
static char HYPHEN[]
Definition XMLCursor.cxx:40
static const Data COMMENT_START("<!--")
static const Data RA_QUOTE_SLASH(">/")
static const Data COMMENT_END("-->")
static const Data QUESTION_RA_QUOTE("?>")
static char BANG[]
Whitespace handling: Are the following XML fragments equivalent?
Definition XMLCursor.cxx:39
static char EQUALS[]
Definition XMLCursor.cxx:44
static Data SLASH_RA_QUOTE("/>")
static char SLASH[]
Definition XMLCursor.cxx:43
static char LA_QUOTE[]
Definition XMLCursor.cxx:41
static char DOUBLE_QUOTE[]
Definition XMLCursor.cxx:45
An iostream that operates on an existing Data.
An alternative to std::string, encapsulates an arbitrary buffer of bytes.
Definition Data.hxx:90
size_t size_type
Definition Data.hxx:94
size_type size() const noexcept
Returns the number of bytes in this Data.
Definition Data.hxx:599
Data & clear()
Clears the contents of this Data.
Definition Data.hxx:800
const char * data() const noexcept
Returns a pointer to the contents of this Data.
Definition Data.hxx:608
void reserve(size_type capacity)
Guarantees that the underlying buffer used by the Data is at least the number of bytes specified.
Definition Data.cxx:1049
size_type count(const Key &key) const
size_type size() const
Provides string-parsing functionality with protection from buffer overflow.
Pointer start() const
CurrentPosition skipToEnd()
Pointer end() const
CurrentPosition skipWhitespace()
CurrentPosition skipToChars(const char *cs)
CurrentPosition skipToChar(char c)
CurrentPosition position() const
void reset(const char *pos)
CurrentPosition skipChars(const char *cs)
void assertNotEof() const
CurrentPosition skipToOneOf(const char *cs)
CurrentPosition skipN(size_t count)
CurrentPosition skipChar()
void fail(const char *file, unsigned int line, const Data &errmsg=Data::Empty) const
void data(Data &data, const char *start) const
make the passed in data share memory with the buffer (uses Data::Share)
static const char * Whitespace
void skipToEndTag(unsigned int depth)
std::vector< Node * > mChildren
static const char * skipComments(ParseBuffer &pb)
std::vector< Node * >::const_iterator mNext
Node(const ParseBuffer &pb)
const Data & getValue() const
static constexpr size_t MaxAttributes
getAttributes() fails (ParseException) an element with more attributes than this
XMLCursor(const ParseBuffer &pb)
Definition XMLCursor.cxx:60
bool atLeaf() const
const Data & getTag() const
const AttributeMap & getAttributes() const
friend class Node
static EncodeStream & encode(EncodeStream &strm, const AttributeMap &attrs)
bool atRoot() const
static constexpr unsigned int MaxNestingDepth
Parsing fails (ParseException) a document with elements nested more than this many levels below the r...
dcm! – add UnusedChecking(_enum) below;
EncodeStream & operator<<(EncodeStream &strm, const BaseException &e)
#define EncodeStream