0031008: Application Framework - memcpy-param-overlap reported by Clang address sanit...
[occt.git] / src / LDOM / LDOM_XmlReader.cxx
1 // Created on: 2001-07-20
2 // Created by: Alexander GRIGORIEV
3 // Copyright (c) 2001-2014 OPEN CASCADE SAS
4 //
5 // This file is part of Open CASCADE Technology software library.
6 //
7 // This library is free software; you can redistribute it and/or modify it under
8 // the terms of the GNU Lesser General Public License version 2.1 as published
9 // by the Free Software Foundation, with special exception defined in the file
10 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
11 // distribution for complete text of the license and disclaimer of any warranty.
12 //
13 // Alternatively, this file may be used under the terms of Open CASCADE
14 // commercial license or contractual agreement.
15
16 //AGV 060302: Input from std::istream
17 //            AGV 130302: bug corr: was error if strlen(root_elem_name) < 7
18
19 #include <LDOM_XmlReader.hxx>
20 #include <Standard_Stream.hxx>
21 #include <LDOM_MemManager.hxx>
22 #include <LDOM_BasicAttribute.hxx>
23 #include <LDOM_CharReference.hxx>
24 #include <LDOM_OSStream.hxx>
25
26 #include <string.h>
27 #include <errno.h>
28 #ifdef _MSC_VER
29 #include <io.h>
30 #else
31 #include <unistd.h>
32 #endif
33
34 //#include <ctype.h>
35
36 const int XML_MIN_BUFFER = 10;
37
38 typedef enum {
39   STATE_WAITING = 0,
40   STATE_HEADER,
41   STATE_DOCTYPE,
42   STATE_DOCTYPE_MARKUP,
43   STATE_ELEMENT,
44   STATE_ELEMENT_END,
45   STATE_ATTRIBUTE_NAME,
46   STATE_ATTRIBUTE_EQUAL,
47   STATE_ATTRIBUTE_VALUE,
48   STATE_COMMENT,
49   STATE_CDATA,
50   STATE_TEXT
51 } ParserState;
52
53 #define TEXT_COMPARE(aPtr,aPattern) \
54   (memcmp ((aPtr), (aPattern), sizeof(aPattern) - 1) == 0)
55
56 static Standard_Boolean isName          (const char             * aString,
57                                          const char             * aStringEnd,
58                                          const char             *& aNameEnd);
59
60 //=======================================================================
61 //function : LDOM_XmlReader()
62 //purpose  : Constructor (file descriptor)
63 //=======================================================================
64
65 LDOM_XmlReader::LDOM_XmlReader (
66                                 const Handle(LDOM_MemManager)&  theDocument,
67                                 TCollection_AsciiString&        theErrorString,
68                                 const Standard_Boolean theTagPerStep)
69 : myEOF      (Standard_False),
70   myError    (theErrorString),
71   myDocument (theDocument),
72   myElement  (NULL),
73   myLastChild(NULL), 
74   myPtr      (&myBuffer[0]),
75   myEndPtr   (&myBuffer[0]),
76   myTagPerStep (theTagPerStep)
77 {
78 }
79
80 //=======================================================================
81 //function : ReadRecord
82 //purpose  : Read a record from XML file
83 //=======================================================================
84
85 LDOM_XmlReader::RecordType LDOM_XmlReader::ReadRecord (Standard_IStream& theIStream,
86                                         LDOM_OSStream& theData)
87 {
88   theData.Clear();
89   myError.Clear();
90   ParserState aState = STATE_WAITING;
91   const char * aStartData = NULL, * aNameEnd = NULL, * aPtr;
92   LDOMBasicString anAttrName, anAttrValue;
93   char anAttDelimiter = '\0';
94   Standard_Boolean aHasRead = Standard_False;
95
96   for(;;) {
97     //  Check if the current file buffer is exhausted
98     // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
99     //  There should always be some bytes available in the buffer for analysis
100     Standard_Integer aBytesRest = (Standard_Integer)(myEndPtr - myPtr);
101     if (aBytesRest < XML_MIN_BUFFER)
102     {
103       if (myEOF == Standard_True)
104       {
105         if (aBytesRest <= 0)
106           break;                        // END of processing
107       }
108       else if (myTagPerStep && aHasRead)
109       {
110         // in myTagPerStep mode, we should parse the buffer to the end before
111         // getting more characters from the stream.
112       }
113       else
114       {
115         // If we are reading some data, save the beginning and preserve the state
116         if (aStartData /* && aState != STATE_WAITING */) {
117           if (myPtr > aStartData)
118             theData.rdbuf()->sputn(aStartData, myPtr - aStartData);
119           aStartData = &myBuffer[0];
120         }
121         // Copy the rest of file data to the beginning of buffer
122         if (aBytesRest > 0)
123         {
124           // do not use memcpy here because aBytesRest may be greater than myPtr-myBuffer, so, overlap
125           memmove (&myBuffer[0], myPtr, aBytesRest);
126         }
127
128         // Read the full buffer and reset start and end buffer pointers
129         myPtr    = &myBuffer[0];
130         Standard_Size aNBytes;
131
132         if (myTagPerStep)
133         {
134           theIStream.getline (&myBuffer[aBytesRest], XML_BUFFER_SIZE - aBytesRest, '>');
135           aHasRead = Standard_True;
136         }
137         else
138         {
139           theIStream.read (&myBuffer[aBytesRest], XML_BUFFER_SIZE - aBytesRest);
140         }
141         aNBytes = (Standard_Size)theIStream.gcount();
142         
143         if (aNBytes == 0)
144         {
145           myEOF = Standard_True;                  // END-OF-FILE
146         }
147         else if (myTagPerStep)
148         {
149           // replace \0 (being inserted by getline method) with > 
150           myBuffer[aBytesRest + aNBytes - 1] = '>';
151         }
152         myEndPtr = &myBuffer[aBytesRest + aNBytes];
153         myBuffer[aBytesRest + aNBytes] = '\0';
154       }
155     }
156
157     //  Check the character data
158     switch (aState) {
159
160       // Checking the characters in STATE_WAITING (blank, TEXT or markup)
161       // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
162     case STATE_WAITING:
163       switch (myPtr[0]) {
164       case ' ':
165       case '\t':
166       case '\n':
167       case '\r':
168         ++ myPtr;
169         continue;
170       case '<':
171         // XML markup found, then make detect the record type
172         switch (myPtr[1]) {
173         case '?':
174           aState = STATE_HEADER;
175           myPtr += 2;
176           aStartData = myPtr;
177           continue;
178         case '/':
179           aState = STATE_ELEMENT_END;
180           myPtr += 2;
181           aStartData = myPtr;
182           continue;
183         case '!':
184           if (myPtr[2] == '-' && myPtr[3] == '-') {
185             aState = STATE_COMMENT;
186             myPtr += 4;
187           } else if (TEXT_COMPARE (&myPtr[2], "DOCTYPE")) {
188             char ch = myPtr[9];
189             if (ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r')
190               break;
191             aState = STATE_DOCTYPE;
192             myPtr += 10;
193           } else if (TEXT_COMPARE (&myPtr[2], "[CDATA[")) {
194             aState = STATE_CDATA;
195             myPtr += 9;
196           } else break;                   // ERROR
197           aStartData = myPtr;
198           continue;
199         default:
200           if (::isName (&myPtr[1], myEndPtr, aNameEnd)) {
201             aStartData = myPtr + 1;
202             myPtr = aNameEnd;
203             if (myPtr < myEndPtr) {
204               myElement = & LDOM_BasicElement::Create (aStartData,
205                                                        (Standard_Integer)(myPtr - aStartData),
206                                                        myDocument);
207               myLastChild = NULL;
208               aState = STATE_ATTRIBUTE_NAME;
209               aStartData = NULL;
210             }else
211               aState = STATE_ELEMENT;
212             continue;
213           }       // otherwise ERROR
214         }     // end of switch
215         myError = "Unknown XML object: ";
216         myError += TCollection_AsciiString (myPtr, XML_MIN_BUFFER);
217         return XML_UNKNOWN;
218       case '\0':
219         if (myEOF == Standard_True) continue;
220         Standard_FALLTHROUGH
221       default:
222         //      Limitation: we do not treat '&' as special character
223         aPtr = (const char *) memchr (myPtr, '<', myEndPtr - myPtr);
224         if (aPtr) {
225           // The end of text field reached
226           theData.rdbuf()->sputn(myPtr, aPtr - myPtr);
227           myPtr = aPtr;
228           return XML_TEXT;
229         }
230         aState = STATE_TEXT;
231         aStartData = myPtr;
232         myPtr = myEndPtr;
233         aHasRead = Standard_False;
234       }   // end of checking in STATE_WAITING
235       continue;
236
237       // Checking the characters in STATE_HEADER, seek for "?>" sequence
238       // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
239     case STATE_HEADER:
240       aPtr = (const char *) memchr (aStartData, '?', (myEndPtr-1) - aStartData);
241       if (aPtr) {
242         // The end of XML declaration found
243         if (aPtr[1] != '>') {           // ERROR
244           myError = "Character \'>\' is expected in the end of XML declaration";
245           return XML_UNKNOWN;
246         }
247         // The XML declaration is retrieved
248         theData.rdbuf()->sputn(aStartData, aPtr - aStartData);
249         myPtr = aPtr + 2;
250         return XML_HEADER;
251       }
252       myPtr = myEndPtr - 1;
253       aHasRead = Standard_False;
254       continue;
255
256       // Checking the characters in STATE_DOCTYPE, seek for "]>" sequence
257       // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
258     case STATE_DOCTYPE:
259       for (aPtr = aStartData; aPtr < myEndPtr-1; aPtr++) {
260         const int aChar = aPtr[0];
261         if (aChar == '[') {
262           aState = STATE_DOCTYPE_MARKUP;
263           aStartData = &aPtr[1];
264           goto state_doctype_markup;
265         }
266         if (aChar == '>') {
267           // The DOCTYPE declaration is retrieved
268           theData.rdbuf()->sputn(aStartData, aPtr - aStartData - 1);
269           myPtr = aPtr + 1;
270           return XML_DOCTYPE;
271         }
272       }
273       myPtr = myEndPtr - 1;
274       aHasRead = Standard_False;
275       continue;
276
277     state_doctype_markup:
278     case STATE_DOCTYPE_MARKUP:
279       aPtr = (const char *) memchr (aStartData, ']', (myEndPtr-1) - aStartData);
280       if (aPtr) {
281         // The end of DOCTYPE declaration found
282         if (aPtr[1] != '>') {           // ERROR
283           myError =
284             "Character \'>\' is expected in the end of DOCTYPE declaration";
285           return XML_UNKNOWN;
286         }
287         // The DOCTYPE declaration is retrieved
288         theData.rdbuf()->sputn(aStartData, aPtr - aStartData);
289         myPtr = aPtr + 2;
290         return XML_DOCTYPE;
291       }
292       myPtr = myEndPtr - 1;
293       aHasRead = Standard_False;
294       continue;
295
296         // Checking the characters in STATE_COMMENT, seek for "-->" sequence
297         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
298     case STATE_COMMENT:
299       aPtr = aStartData;
300       for(;;) {
301         aPtr = (const char *) memchr (aPtr, '-', (myEndPtr - 2) - aPtr);
302         if (aPtr == NULL) break;
303         if (aPtr[1] != '-') ++ aPtr;
304         else {
305           if (aPtr[2] != '>') {       // ERROR
306             myError = "Character \'>\' is expected in the end of comment";
307             return XML_UNKNOWN;
308           }
309           theData.rdbuf()->sputn(aStartData, aPtr - aStartData);
310           myPtr = aPtr + 3;
311           return XML_COMMENT;
312         }
313       }
314       myPtr = myEndPtr - 2;
315       aHasRead = Standard_False;
316       continue;
317
318         // Checking the characters in STATE_TEXT, seek for "<"
319         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
320     case STATE_TEXT:
321       aPtr = (const char *) memchr (aStartData, '<', myEndPtr - aStartData);
322       if (aPtr) {
323         // The end of text field reached
324         theData.rdbuf()->sputn(aStartData, aPtr - aStartData);
325         myPtr = aPtr;
326         return XML_TEXT;
327       }
328       myPtr = myEndPtr;
329       aHasRead = Standard_False;
330       continue;
331
332         // Checking the characters in STATE_CDATA, seek for "]]"
333         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
334     case STATE_CDATA:
335       aPtr = aStartData;
336       for(;;) {
337         aPtr = (const char *) memchr (aPtr, ']', (myEndPtr - 1) - aStartData);
338         if (aPtr == NULL) break;
339         if (aPtr[1] != ']') {           // ERROR
340           myError = "Characters \']]\' are expected in the end of CDATA";
341           return XML_UNKNOWN;
342         }
343         theData.rdbuf()->sputn(aStartData, aPtr - aStartData);
344         myPtr = aPtr + 2;
345         return XML_CDATA;
346       }
347       myPtr = myEndPtr - 1;
348       aHasRead = Standard_False;
349       continue;
350
351         // Checking the characters in STATE_ELEMENT, seek the end of TagName
352         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
353     case STATE_ELEMENT:
354       if (::isName (myPtr, myEndPtr, aNameEnd) == Standard_False)
355         if (theData.Length() == 0 || aNameEnd != myPtr) {
356           myError = "Invalid tag name";
357           return XML_UNKNOWN;
358         }
359       {
360         theData.rdbuf()->sputn(aStartData, aNameEnd - aStartData);
361         char* aDataString = (char *)theData.str();
362         myElement = & LDOM_BasicElement::Create (aDataString, theData.Length(),
363                                                  myDocument);
364         theData.Clear();
365         myLastChild = NULL;
366         delete [] aDataString;
367         aState = STATE_ATTRIBUTE_NAME;
368         aStartData = NULL;
369         myPtr = aNameEnd;
370         continue;
371       }
372         // Parsing a single attribute (STATE_ATTRIBUTE)
373         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
374     case STATE_ATTRIBUTE_NAME:          // attribute name
375       switch (myPtr[0]) {
376       case ' ' :
377       case '\t':
378       case '\n':
379       case '\r':
380         if (aStartData) goto attr_name;
381         ++ myPtr;
382         continue;
383       case '/' :
384         if (aStartData)
385           myError = "Inexpected end of attribute";
386         else if (myPtr[1] != '>')
387           myError = "Improper element tag termination";
388         else {
389           myPtr += 2;
390 #ifdef OCCT_DEBUG
391           theData.Clear();
392           theData << myElement->GetTagName();
393 #endif
394           return XML_FULL_ELEMENT;
395         }
396         return XML_UNKNOWN;
397       case '>' :
398         if (aStartData) {
399           myError = "Inexpected end of attribute";
400           return XML_UNKNOWN;
401         }
402         ++ myPtr;
403 #ifdef OCCT_DEBUG
404         theData.Clear();
405         theData << myElement->GetTagName();
406 #endif
407         return XML_START_ELEMENT;
408       default  :
409         if (::isName (myPtr, myEndPtr, aNameEnd) == Standard_False)
410           if (theData.Length() == 0 || aNameEnd != myPtr) {
411             myError = "Invalid attribute name";
412             return XML_UNKNOWN;
413           }
414         if (aNameEnd >= myEndPtr)
415           aStartData = myPtr;
416         else {
417           if (theData.Length() == 0)
418             anAttrName = LDOMBasicString(myPtr, (Standard_Integer)(aNameEnd - myPtr), myDocument);
419           else {
420             theData.rdbuf()->sputn(myPtr, aNameEnd - myPtr);
421 attr_name:
422             char* aDataString = (char *)theData.str();
423             theData.Clear();
424             anAttrName = LDOMBasicString (aDataString, myDocument);
425             delete [] aDataString;
426           }
427           aStartData = NULL;
428           aState = STATE_ATTRIBUTE_EQUAL;
429         }
430         myPtr = aNameEnd;
431         continue;
432       }
433     case STATE_ATTRIBUTE_EQUAL:          // attribute 'equal' sign
434       switch (myPtr[0]) {
435       case '=' :
436         aState = STATE_ATTRIBUTE_VALUE;
437         Standard_FALLTHROUGH
438       case ' ' :
439       case '\t':
440       case '\n':
441       case '\r':
442         ++ myPtr;
443         continue;
444       default:
445         myError = "Equal sign expected in attribute definition";
446         return XML_UNKNOWN;
447       }
448
449     case STATE_ATTRIBUTE_VALUE:          // attribute value
450       switch (myPtr[0]) {
451       case ' ' :
452       case '\t':
453       case '\n':
454       case '\r':
455         if (aStartData == NULL) {
456           ++ myPtr;
457           continue;
458       default:
459           if (anAttDelimiter == '\0') {
460             myError = "Expected an attribute value";
461             return XML_UNKNOWN;
462       case '\"':
463       case '\'':
464             if (aStartData == NULL) {
465               aStartData     = &myPtr[1];
466               anAttDelimiter = myPtr[0];
467             }
468           }
469         }
470         //      Limitation: we do not take into account that '<' and '&'
471         //      are not allowed in attribute values
472         aPtr = (const char *) memchr (aStartData, anAttDelimiter,
473                                       myEndPtr - aStartData);
474         if (aPtr) {
475           (char&) aPtr[0] = '\0';
476           anAttDelimiter  = '\0';
477           char          * aDataString   = (char *) aStartData;
478           const char    * ePtr          = aPtr;
479
480           //    Append the end of the string to previously taken data
481           if (theData.Length() > 0) {
482             theData.rdbuf()->sputn(aStartData, aPtr-aStartData);
483             aDataString = (char *)theData.str();
484             ePtr = strchr (aDataString, '\0');
485           }
486
487           Standard_Integer aDataLen;
488           aDataString = LDOM_CharReference::Decode (aDataString, aDataLen);
489           if (IsDigit(aDataString[0])) {
490             if (getInteger (anAttrValue, aDataString, ePtr))
491               anAttrValue = LDOMBasicString (aDataString,aDataLen,myDocument);
492           } else
493             anAttrValue = LDOMBasicString (aDataString, aDataLen, myDocument);
494
495           if (theData.Length() > 0) {
496             theData.Clear();
497             delete [] aDataString;
498           }
499           //    Create an attribute
500           myLastChild = myElement -> AddAttribute (anAttrName, anAttrValue,
501                                                    myDocument, myLastChild);
502           myPtr = aPtr + 1;
503           aStartData = NULL;
504           aState = STATE_ATTRIBUTE_NAME;
505         }
506         else {
507           myPtr = myEndPtr;
508           aHasRead = Standard_False;
509         }
510         continue;
511       }
512         // Checking the characters in STATE_ELEMENT_END, seek for ">"
513         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
514     case STATE_ELEMENT_END:
515       aPtr = (const char *) memchr (aStartData, '>', myEndPtr - aStartData);
516       if (aPtr) {
517         // The end of the end-element markup
518         theData.rdbuf()->sputn(aStartData, aPtr - aStartData);
519         myPtr = aPtr + 1;
520         return XML_END_ELEMENT;
521       }
522       myPtr = myEndPtr;
523       aHasRead = Standard_False;
524       continue;
525     }
526   }
527   if (aState != STATE_WAITING) {
528     myError = "Unexpected end of file";
529     return XML_UNKNOWN;
530   }
531   return XML_EOF;
532 }
533
534 //=======================================================================
535 //function : isName
536 //type     : static
537 //purpose  : Check if aString is a valid XML Name
538 //=======================================================================
539
540 static Standard_Boolean isName (const char  * aString,
541                                 const char  * aStringEnd,
542                                 const char  *& aNameEnd)
543 {
544   Standard_Boolean aResult;
545   char aCh = aString[0];
546   if (IsAlphabetic(aCh) || aCh == '_' || aCh == ':') {
547     const char * aPtr = &aString[1];
548     while (aPtr < aStringEnd) {
549       aCh = * aPtr;
550       switch (aCh) {
551       case ' ' :
552       case '\n':
553       case '\r':
554       case '\t':
555       case '=' :
556       case '\0':
557       case '/' :
558       case '>' :
559         aNameEnd = aPtr;
560         return Standard_True;
561       default:
562         if (IsAlphanumeric(aCh) == 0) {
563           aNameEnd = aPtr;
564           return Standard_False;
565         }
566         Standard_FALLTHROUGH
567       case '.' :
568       case '-' :
569       case '_' :
570       case ':' :
571         ++ aPtr;
572       }
573     }
574     aNameEnd = aPtr;
575     aResult = Standard_True;
576   } else {
577     aNameEnd = aString;
578     aResult = Standard_False;
579   }
580   return aResult;
581 }
582
583 //=======================================================================
584 //function : CreateElement
585 //purpose  : 
586 //=======================================================================
587 void LDOM_XmlReader::CreateElement( const char *theName, const Standard_Integer theLen )
588 {
589   myElement = &LDOM_BasicElement::Create (theName, theLen, myDocument);
590 }
591
592 //=======================================================================
593 //function : getInteger
594 //purpose  : Try to initialize theValue as Integer; return False on success
595 //=======================================================================
596
597 Standard_Boolean LDOM_XmlReader::getInteger (LDOMBasicString&    theValue,
598                                              const char          * theStart,
599                                              const char          * theEnd)
600 {
601   char * ptr;
602   errno = 0;
603   if (theEnd - theStart == 1 || theStart[0] != '0')
604   {
605       long aResult = strtol (theStart, &ptr, 10);
606       if (ptr == theEnd && errno == 0) 
607       {
608         theValue = Standard_Integer(aResult);
609         return Standard_False;
610       }
611   }
612   return Standard_True;
613 }