Coverage Report

Created: 2026-10-28 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/resiprocate/rutil/XMLCursor.cxx
Line
Count
Source
1
#if defined(HAVE_CONFIG_H)
2
#include "config.h"
3
#endif
4
5
#include "rutil/XMLCursor.hxx"
6
#include "rutil/Logger.hxx"
7
#include "rutil/WinLeakCheck.hxx"
8
9
using namespace resip;
10
using namespace std;
11
12
#define RESIPROCATE_SUBSYSTEM Subsystem::CONTENTS
13
14
/**
15
Whitespace handling:
16
Are the following XML fragments equivalent?
17
18
Strictly interpreted, the root of the first XML document has one 
19
child while the root of the second XML doucment has three children.
20
The line breaks and spaces after the <root> and before </root> are 
21
tagless children.
22
23
--->
24
  <root><child>child content</child></root>
25
<--
26
  vs.
27
--->
28
  <root>
29
     <child>child content</child>
30
  </root>
31
<--
32
33
Treating whitespace as children is consistent with the spec but not usually
34
convenient. <!ATTLIST poem   xml:space (default|preserve) 'preserve'> is used to
35
control whitespace handling. Supporting this switch is painful. For now, treat
36
whitespace as non-significant.
37
*/
38
39
static char BANG[] = "!";
40
static char HYPHEN[] = "-";
41
static char LA_QUOTE[] = "<";
42
static char RA_QUOTE[] = ">";
43
static char SLASH[] = "/";
44
static char EQUALS[] = "=";
45
static char DOUBLE_QUOTE[] = "\"";
46
static char SINGLE_QUOTE[] = "\'";
47
//http://www.w3.org/TR/1998/REC-xml-19980210
48
static const Data COMMENT_START("<!--");
49
static const Data COMMENT_END("-->");
50
static 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 <? >
60
XMLCursor::XMLCursor(const ParseBuffer& pb)
61
0
   : mCursor(nullptr),
62
0
     mAttributesSet(false)
63
0
{
64
0
   ParseBuffer lPb(pb);
65
66
0
   skipProlog(lPb);
67
0
   const char* start = lPb.position();
68
69
0
   lPb.skipToChars(COMMENT_START);
70
0
   if (!lPb.eof())
71
0
   {
72
0
      StackLog(<< "removing comments");
73
0
      lPb.reset(start);
74
0
      mData.reserve((Data::size_type)(lPb.end() - lPb.start()));
75
76
0
      const char* anchor = start;
77
0
      {
78
0
         DataStream str(mData);
79
0
         Data temp;
80
0
         while (true)
81
0
         {
82
0
            lPb.skipToChars(COMMENT_START);
83
0
            if (!lPb.eof())
84
0
            {
85
0
               lPb.data(temp, anchor);
86
0
               str << temp;
87
0
               anchor = Node::skipComments(lPb);
88
0
            }
89
0
            else
90
0
            {
91
0
               lPb.data(temp, anchor);
92
0
               str << temp;
93
0
               break;
94
0
            }
95
0
         }
96
0
      }
97
0
      mRoot.reset(new Node(ParseBuffer(mData.data(), mData.size())));
98
0
   }
99
0
   else
100
0
   {
101
0
      mRoot.reset(new Node(ParseBuffer(start, pb.end() - start)));
102
0
   }
103
0
   mCursor = mRoot.get();
104
105
0
   if (mRoot->extractTag())
106
0
   {
107
0
      InfoLog(<< "XML: empty element no a legal root");
108
0
      mRoot->mPb.fail(__FILE__, __LINE__);
109
0
   }
110
111
0
   mTag = mRoot->mTag;
112
0
   decodeName(mRoot->mTag);
113
114
   // check for # & and note -- make decode, decodeName do stuff if set
115
116
   //<top></top> // no children
117
0
   ParseBuffer pbtemp(mRoot->mPb);
118
0
   pbtemp.skipToChar(RA_QUOTE[0]);
119
0
   pbtemp.skipChar();
120
0
   if (!WhitespaceSignificant)
121
0
   {
122
0
      pbtemp.skipWhitespace();
123
0
   }
124
0
   if (*pbtemp.position() == LA_QUOTE[0] &&
125
0
       *(pbtemp.position()+1) == SLASH[0])
126
0
   {
127
0
      pbtemp.skipChar();
128
0
      pbtemp.skipChar();
129
0
      if (strncmp(mRoot->mTag.data(), pbtemp.position(), mRoot->mTag.size()) == 0)
130
0
      {
131
         // no children ever
132
0
         mRoot->mPb.reset(mRoot->mPb.end());
133
0
         return;
134
0
      }
135
0
   }
136
0
}
137
138
XMLCursor::~XMLCursor()
139
0
{
140
0
}
141
142
void
143
XMLCursor::skipProlog(ParseBuffer& pb)
144
0
{
145
   //'<?xml' VersionInfo '<xml?' EncodingDecl '?>'? '<?xml' SDDecl '?>'? S? '?>
146
147
   // !dlb! much more complicated than this.. can contain comments
148
0
   const char* start = pb.position();
149
0
   pb.skipToChars(QUESTION_RA_QUOTE);
150
0
   if(pb.eof()) 
151
0
   {
152
      // No Prolog
153
0
      pb.reset(start);
154
0
      return;
155
0
   }
156
0
   pb.skipN(2);
157
0
   pb.skipWhitespace();
158
0
}
159
160
void
161
XMLCursor::decode(Data& text)
162
0
{
163
0
}
164
165
void
166
XMLCursor::decodeName(Data& name)
167
0
{
168
0
}
169
170
void
171
XMLCursor::parseNextRootChild()
172
0
{
173
   // no next child to parse?
174
0
   if (mRoot->mPb.eof())
175
0
   {
176
0
      return;
177
0
   }
178
179
   // next child already parsed?
180
0
   if (mRoot->mNext != mRoot->mChildren.end())
181
0
   {
182
0
      return;
183
0
   }
184
185
   // skip self tag
186
0
   if (mRoot->mPb.position() == mRoot->mPb.start())
187
0
   {
188
0
      mRoot->mPb.skipToChar(RA_QUOTE[0]);
189
0
      mRoot->mPb.skipChar();
190
0
   }
191
192
0
   if (!WhitespaceSignificant)
193
0
   {
194
0
      mRoot->mPb.skipWhitespace();
195
0
   }
196
197
   // root end tag?
198
0
   if (*mRoot->mPb.position() == LA_QUOTE[0])
199
0
   {
200
0
      ParseBuffer pb(mRoot->mPb.position(), 
201
0
                     mRoot->mPb.end() - mRoot->mPb.position());
202
0
      pb.skipChar();
203
0
      if (!pb.eof() && *pb.position() == SLASH[0])
204
0
      {
205
0
         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
0
         const char* end = pb.position();
210
0
         if ( (const char*)pb.end() < end + mTag.size() )
211
0
         {
212
0
            InfoLog(<< "XML: unexpected end");
213
0
            pb.fail(__FILE__, __LINE__);
214
0
         }
215
         
216
0
         if (strncmp(mTag.data(), pb.position(), mRoot->mTag.size()) == 0)
217
0
         {
218
0
            mRoot->mPb.skipToEnd();
219
0
            return;
220
0
         }
221
0
      }
222
0
   }
223
224
   // leaf?
225
0
   if (*mRoot->mPb.position() != LA_QUOTE[0])
226
0
   {
227
0
      const char* anchor = mRoot->mPb.position();
228
0
      mRoot->mPb.skipToChar(LA_QUOTE[0]);
229
0
      Node* leaf = new Node(ParseBuffer(anchor, mRoot->mPb.position() - anchor));
230
0
      leaf->mIsLeaf = true;
231
0
      mRoot->addChild(leaf);
232
0
   }
233
0
   else
234
0
   {
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
0
      std::unique_ptr<Node> child(new Node(mRoot->mPb));
239
0
      child->skipToEndTag(1);   // a child of the root is one level down
240
241
      // leave the parse buffer after the child
242
0
      mRoot->mPb.reset(child->mPb.end());
243
244
0
      mRoot->addChild(child.release());
245
0
   }
246
247
   // mNext always points at cursored child
248
0
   mRoot->mNext = mRoot->mChildren.end();
249
0
   mRoot->mNext--;
250
0
}
251
252
bool
253
XMLCursor::nextSibling()
254
0
{
255
0
   if (atRoot())
256
0
   {
257
0
      StackLog(<< "XMLCursor::nextSibling" << *this->mCursor << " <<root>>");
258
0
      return false;
259
0
   }
260
261
0
   StackLog(<< "XMLCursor::nextSibling" << *this->mCursor << " " << *this->mCursor->mParent);
262
0
   if (mCursor->mParent == mRoot.get())
263
0
   {
264
0
      parseNextRootChild();
265
0
   }
266
267
0
   if (mCursor->mParent->mNext != mCursor->mParent->mChildren.end())
268
0
   {
269
0
      mCursor = *((mCursor->mParent->mNext)++);
270
0
      mAttributesSet = false;
271
0
      return true;
272
0
   }
273
0
   else
274
0
   {
275
0
      return false;
276
0
   }
277
0
}
278
279
bool
280
XMLCursor::firstChild()
281
0
{
282
0
   if (atRoot() &&
283
0
       mRoot->mChildren.empty())
284
0
   {
285
0
      parseNextRootChild();
286
0
   }
287
288
0
   if (mCursor->mChildren.empty())
289
0
   {
290
0
      return false;
291
0
   }
292
0
   else
293
0
   {
294
      // mNext always points after cursored child
295
0
      mCursor->mNext = mCursor->mChildren.begin();
296
0
      mCursor->mNext++;
297
0
      mCursor = mCursor->mChildren.front();
298
0
      mAttributesSet = false;
299
0
      return true;
300
0
   }
301
0
}
302
303
bool
304
XMLCursor::parent()
305
0
{
306
0
   if (atRoot())
307
0
   {
308
0
      return false;
309
0
   }
310
311
0
   mCursor = mCursor->mParent;
312
0
   mAttributesSet = false;
313
0
   return true;
314
0
}
315
316
void
317
XMLCursor::reset()
318
0
{
319
0
   mCursor = mRoot.get();
320
0
   mAttributesSet = false;
321
0
}
322
323
bool
324
XMLCursor::atRoot() const
325
0
{
326
0
   return mCursor == mRoot.get();
327
0
}
328
329
bool
330
XMLCursor::atLeaf() const
331
0
{
332
0
   return mCursor->mIsLeaf;
333
0
}
334
335
const Data&
336
XMLCursor::getTag() const
337
0
{
338
0
   return mCursor->mTag;
339
0
}
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" />
348
static const Data RA_QUOTE_SLASH(">/");
349
const XMLCursor::AttributeMap&
350
XMLCursor::getAttributes() const
351
0
{
352
0
   if (!atLeaf() &&
353
0
       !mAttributesSet)
354
0
   {
355
0
      mAttributes.clear();
356
357
0
      ParseBuffer pb(mCursor->mPb);
358
0
      pb.reset(mCursor->mPb.start());
359
360
0
      Data attribute;
361
0
      Data value;
362
363
0
      pb.skipToOneOf(ParseBuffer::Whitespace, RA_QUOTE_SLASH);
364
0
      pb.skipWhitespace();
365
366
0
      while (!pb.eof() && 
367
0
             *pb.position() != RA_QUOTE[0] &&
368
0
             *pb.position() != SLASH[0])
369
0
      {
370
0
         attribute.clear();
371
0
         value.clear();
372
373
0
         const char* anchor = pb.skipWhitespace();
374
0
         pb.skipToOneOf(ParseBuffer::Whitespace, EQUALS);
375
0
         pb.data(attribute, anchor);
376
0
         XMLCursor::decodeName(attribute);
377
378
0
         StackLog(<< "attribute: " << attribute);
379
380
0
         pb.skipWhitespace();
381
0
         pb.skipToChar(EQUALS[0]);
382
0
         pb.skipChar();
383
0
         pb.skipWhitespace();
384
0
         if (!pb.eof())
385
0
         {
386
0
            const char quote = *pb.position();
387
388
0
            StackLog(<< "quote is <" << quote << ">");
389
390
0
            if(quote != DOUBLE_QUOTE[0] &&
391
0
               quote != SINGLE_QUOTE[0])
392
0
            {
393
0
               InfoLog(<< "XML: badly quoted attribute value");
394
0
               pb.fail(__FILE__, __LINE__);
395
0
            }
396
0
            anchor = pb.skipChar();
397
0
            pb.skipToChar(quote);
398
0
            pb.data(value, anchor);
399
0
            XMLCursor::decode(value);
400
0
            pb.skipChar();
401
0
            if (mAttributes.size() >= MaxAttributes && mAttributes.count(attribute) == 0)
402
0
            {
403
0
               InfoLog(<< "XML: too many attributes on element " << getTag());
404
0
               pb.fail(__FILE__, __LINE__, "Too many XML attributes");
405
0
            }
406
0
            mAttributes[attribute] = value;
407
0
         }
408
0
         pb.skipWhitespace();
409
0
      }
410
      // Only once complete, so a failed parse fails again rather than leaving a partial map
411
0
      mAttributesSet = true;
412
0
   }
413
414
0
   return mAttributes;
415
0
}
416
417
const Data&
418
XMLCursor::getValue() const
419
0
{
420
0
   if (atLeaf())
421
0
   {
422
0
      ParseBuffer pb(mCursor->mPb);
423
0
      pb.skipToEnd();
424
0
      pb.data(mValue, pb.start());
425
0
      XMLCursor::decode(mValue);
426
0
   }
427
0
   else
428
0
   {
429
0
      mValue.clear();
430
0
   }
431
0
   return mValue;
432
0
}
433
434
EncodeStream&
435
XMLCursor::encode(EncodeStream& str, const AttributeMap& attrs)
436
0
{
437
0
   for(AttributeMap::const_iterator i = attrs.begin();
438
0
       i != attrs.end(); ++i)
439
0
   {
440
0
      if (i != attrs.begin())
441
0
      {
442
0
         str << " ";
443
0
      }
444
      // !dlb! some sort of character encoding required here
445
0
      str << i->first << "=\"" << i->second << "\"";
446
0
   }
447
448
0
   return str;
449
0
}
450
451
XMLCursor::Node::Node(const ParseBuffer& pb)
452
0
   : mPb(pb.position(), pb.end() - pb.position()),
453
0
     mParent(0),
454
0
     mChildren(),
455
0
     mNext(mChildren.begin()),
456
0
     mIsLeaf(false)
457
0
{
458
0
   mPb.assertNotEof();
459
0
   StackLog(<< "XMLCursor::Node::Node" << *this);
460
0
}
461
462
XMLCursor::Node::~Node()
463
0
{
464
0
   for (vector<Node*>::iterator i = mChildren.begin();
465
0
        i != mChildren.end(); ++i)
466
0
   {
467
0
      delete *i;
468
0
   }
469
0
}
470
471
// start:
472
//<foo >
473
//^
474
// end:
475
//<foo >
476
//      ^
477
static Data SLASH_RA_QUOTE("/>");
478
bool
479
XMLCursor::Node::extractTag()
480
0
{
481
0
   ParseBuffer pb(mPb);
482
0
   if (!WhitespaceSignificant)
483
0
   {
484
0
       pb.skipWhitespace();
485
0
   }
486
0
   const char* anchor = pb.skipChar(LA_QUOTE[0]);
487
0
   pb.skipToOneOf(ParseBuffer::Whitespace, SLASH_RA_QUOTE);
488
0
   pb.assertNotEof();
489
0
   pb.data(mTag, anchor);
490
491
0
   return !pb.eof() && *pb.position() == SLASH[0];
492
0
}
493
494
void
495
XMLCursor::Node::addChild(Node* child)
496
0
{
497
0
   mChildren.push_back(child);
498
0
   child->mParent = this;
499
0
}
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
512
void
513
XMLCursor::Node::skipToEndTag(unsigned int depth)
514
0
{
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
0
   if (depth > MaxNestingDepth)
521
0
   {
522
0
      InfoLog(<< "XML: nesting too deep");
523
0
      mPb.fail(__FILE__, __LINE__, "XML nesting too deep");
524
0
   }
525
526
0
   extractTag();
527
0
   StackLog(<< "XMLCursor::Node::skipToEndTag(" <<  mTag << ")");
528
   //StackLog(<< "XMLCursor::Node::skipToEndTag(" << Data(mPb.position(), mPb.end() - mPb.position()) << ")");
529
530
   //<foo />
531
0
   mPb.skipToChar(RA_QUOTE[0]);
532
0
   if (*(mPb.position()-1) == SLASH[0])
533
0
   {
534
0
      mPb.skipChar();
535
0
      mPb = ParseBuffer(mPb.start(), mPb.position() - mPb.start());
536
0
      return;
537
0
   }
538
539
   //<foo> ...<child> ... </child> </foo>
540
   //    ^
541
0
   mPb.skipChar();
542
   //<foo> ...<child> ... </child> </foo>
543
   //     ^
544
0
   while (true)
545
0
   {
546
0
      if (!WhitespaceSignificant)
547
0
      {
548
0
         mPb.skipWhitespace();
549
0
      }
550
551
      // Some text contents ...<
552
      // ^                     ^
553
0
      if (*mPb.position() != LA_QUOTE[0])
554
0
      {
555
0
         const char* anchor = mPb.position();
556
0
         mPb.skipToChar(LA_QUOTE[0]);
557
0
         Node* leaf = new Node(ParseBuffer(anchor, mPb.position() - anchor));
558
0
         leaf->mIsLeaf = true;
559
0
         addChild(leaf);
560
0
      }
561
562
      //<...
563
      //^
564
0
      mPb.skipChar();
565
      //<...
566
      // ^
567
568
      // exit condition
569
      //</foo>
570
0
      if (*mPb.position() == SLASH[0])
571
0
      {
572
0
         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
0
         const char* end = mPb.position();
577
0
         if ( (const char*)mPb.end() < end + mTag.size() )
578
0
         {
579
0
            InfoLog(<< "XML: unexpected end");
580
0
            mPb.fail(__FILE__, __LINE__);
581
0
         }
582
583
0
         if (strncmp(mTag.data(), mPb.position(), mTag.size()) == 0)
584
0
         {
585
0
            mPb.skipToChar(RA_QUOTE[0]);
586
0
            mPb.skipChar();
587
0
            mPb = ParseBuffer(mPb.start(), mPb.position() - mPb.start());
588
0
            return;
589
0
         }
590
0
         else
591
0
         {
592
0
            InfoLog(<< "Badly formed XML: unexpected endtag");
593
0
            mPb.fail(__FILE__, __LINE__);
594
0
         }
595
0
      }
596
597
      //<child>...
598
      // ^
599
0
      if (mPb.position() == mPb.start())
600
0
      {
601
0
         InfoLog(<< "XML: badly formed element");
602
0
         mPb.fail(__FILE__, __LINE__);
603
0
      }
604
605
0
      mPb.reset(mPb.position()-1);
606
      //<child>...
607
      //^
608
0
      Node* child = new Node(mPb);
609
0
      addChild(child);
610
0
      child->skipToEndTag(depth + 1);
611
0
      mPb.reset(child->mPb.end());
612
0
      XMLCursor::decodeName(child->mTag);
613
0
      StackLog(<< mTag << "(" << child->mTag << ")");
614
0
    }
615
0
}
616
617
//<!-- declarations for <head> & <body> -->
618
const char*
619
XMLCursor::Node::skipComments(ParseBuffer& pb)
620
0
{
621
0
   while (*pb.position() == LA_QUOTE[0] &&
622
0
          *(pb.position()+1) == BANG[0] &&
623
0
          *(pb.position()+2) == HYPHEN[0] &&
624
0
          *(pb.position()+3) == HYPHEN[0])
625
0
   {
626
0
      pb.skipToChars(COMMENT_END);
627
0
      pb.skipChars(COMMENT_END);
628
0
      pb.skipWhitespace();
629
0
      if(pb.eof())
630
0
      {
631
0
         return pb.end();
632
0
      }
633
0
   }
634
635
0
   return pb.position();
636
0
}
637
638
EncodeStream&
639
resip::operator<<(EncodeStream& str, const XMLCursor::Node& node)
640
0
{
641
0
   Data::size_type size = (Data::size_type)(node.mPb.end() - node.mPb.start());
642
643
0
   static const Data::size_type showSize(35);
644
645
0
   str << &node << "[" 
646
0
       << Data(node.mPb.start(), 
647
0
               min(showSize, size))
648
0
        << "]" << (size ? "" : "...");
649
650
0
   return str;
651
0
}
652
653
EncodeStream&
654
resip::operator<<(EncodeStream& str, const XMLCursor& cursor)
655
0
{
656
0
   str << "XMLCursor " << *cursor.mCursor;
657
0
   return str;
658
0
}
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
 */