svg_loader: fixing crash for to big buffer
[platform/core/graphics/tizenvg.git] / src / loaders / svg / tvgXmlParser.cpp
1 /*
2  * Copyright (c) 2020 - 2022 Samsung Electronics Co., Ltd. All rights reserved.
3
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  */
22
23 #include <cstring>
24 #include <ctype.h>
25 #include <string>
26
27 #ifdef _WIN32
28     #include <malloc.h>
29 #elif defined(__linux__)
30     #include <alloca.h>
31 #else
32     #include <stdlib.h>
33 #endif
34
35 #include "tvgXmlParser.h"
36
37 /************************************************************************/
38 /* Internal Class Implementation                                        */
39 /************************************************************************/
40
41 bool _isIgnoreUnsupportedLogAttributes(TVG_UNUSED const char* tagAttribute, TVG_UNUSED const char* tagValue)
42 {
43 #ifdef THORVG_LOG_ENABLED
44     const auto attributesNum = 6;
45     const struct
46     {
47         const char* tag;
48         bool tagWildcard; //If true, it is assumed that a wildcard is used after the tag. (ex: tagName*)
49         const char* value;
50     } attributes[] = {
51         {"id", false, nullptr},
52         {"data-name", false, nullptr},
53         {"overflow", false, "visible"},
54         {"version", false, nullptr},
55         {"xmlns", true, nullptr},
56         {"xml:space", false, nullptr},
57     };
58
59     for (unsigned int i = 0; i < attributesNum; ++i) {
60         if (!strncmp(tagAttribute, attributes[i].tag, attributes[i].tagWildcard ? strlen(attributes[i].tag) : strlen(tagAttribute))) {
61             if (attributes[i].value && tagValue) {
62                 if (!strncmp(tagValue, attributes[i].value, strlen(tagValue))) {
63                     return true;
64                 } else continue;
65             }
66             return true;
67         }
68     }
69     return false;
70 #endif
71     return true;
72 }
73
74
75 static const char* _simpleXmlFindWhiteSpace(const char* itr, const char* itrEnd)
76 {
77     for (; itr < itrEnd; itr++) {
78         if (isspace((unsigned char)*itr)) break;
79     }
80     return itr;
81 }
82
83
84 static const char* _simpleXmlSkipWhiteSpace(const char* itr, const char* itrEnd)
85 {
86     for (; itr < itrEnd; itr++) {
87         if (!isspace((unsigned char)*itr)) break;
88     }
89     return itr;
90 }
91
92
93 static const char* _simpleXmlUnskipWhiteSpace(const char* itr, const char* itrStart)
94 {
95     for (itr--; itr > itrStart; itr--) {
96         if (!isspace((unsigned char)*itr)) break;
97     }
98     return itr + 1;
99 }
100
101
102 static const char* _simpleXmlSkipXmlEntities(const char* itr, const char* itrEnd)
103 {
104     auto p = itr;
105     while (itr < itrEnd && *itr == '&') {
106         for (int i = 0; i < NUMBER_OF_XML_ENTITIES; ++i) {
107             if (strncmp(itr, xmlEntity[i], xmlEntityLength[i]) == 0) {
108                 itr += xmlEntityLength[i];
109                 break;
110             }
111         }
112         if (itr == p) break;
113         p = itr;
114     }
115     return itr;
116 }
117
118
119 static const char* _simpleXmlUnskipXmlEntities(const char* itr, const char* itrStart)
120 {
121     auto p = itr;
122     while (itr > itrStart && *(itr - 1) == ';') {
123         for (int i = 0; i < NUMBER_OF_XML_ENTITIES; ++i) {
124             if (itr - xmlEntityLength[i] > itrStart &&
125                 strncmp(itr - xmlEntityLength[i], xmlEntity[i], xmlEntityLength[i]) == 0) {
126                 itr -= xmlEntityLength[i];
127                 break;
128             }
129         }
130         if (itr == p) break;
131         p = itr;
132     }
133     return itr;
134 }
135
136
137 static const char* _skipWhiteSpacesAndXmlEntities(const char* itr, const char* itrEnd)
138 {
139     itr = _simpleXmlSkipWhiteSpace(itr, itrEnd);
140     auto p = itr;
141     while (true) {
142         if (p != (itr = _simpleXmlSkipXmlEntities(itr, itrEnd))) p = itr;
143         else break;
144         if (p != (itr = _simpleXmlSkipWhiteSpace(itr, itrEnd))) p = itr;
145         else break;
146     }
147     return itr;
148 }
149
150
151 static const char* _unskipWhiteSpacesAndXmlEntities(const char* itr, const char* itrStart)
152 {
153     itr = _simpleXmlUnskipWhiteSpace(itr, itrStart);
154     auto p = itr;
155     while (true) {
156         if (p != (itr = _simpleXmlUnskipXmlEntities(itr, itrStart))) p = itr;
157         else break;
158         if (p != (itr = _simpleXmlUnskipWhiteSpace(itr, itrStart))) p = itr;
159         else break;
160     }
161     return itr;
162 }
163
164
165 static const char* _simpleXmlFindStartTag(const char* itr, const char* itrEnd)
166 {
167     return (const char*)memchr(itr, '<', itrEnd - itr);
168 }
169
170
171 static const char* _simpleXmlFindEndTag(const char* itr, const char* itrEnd)
172 {
173     bool insideQuote = false;
174     for (; itr < itrEnd; itr++) {
175         if (*itr == '"') insideQuote = !insideQuote;
176         if (!insideQuote) {
177             if ((*itr == '>') || (*itr == '<'))
178                 return itr;
179         }
180     }
181     return nullptr;
182 }
183
184
185 static const char* _simpleXmlFindEndCommentTag(const char* itr, const char* itrEnd)
186 {
187     for (; itr < itrEnd; itr++) {
188         if ((*itr == '-') && ((itr + 1 < itrEnd) && (*(itr + 1) == '-')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2;
189     }
190     return nullptr;
191 }
192
193
194 static const char* _simpleXmlFindEndCdataTag(const char* itr, const char* itrEnd)
195 {
196     for (; itr < itrEnd; itr++) {
197         if ((*itr == ']') && ((itr + 1 < itrEnd) && (*(itr + 1) == ']')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2;
198     }
199     return nullptr;
200 }
201
202
203 static const char* _simpleXmlFindDoctypeChildEndTag(const char* itr, const char* itrEnd)
204 {
205     for (; itr < itrEnd; itr++) {
206         if (*itr == '>') return itr;
207     }
208     return nullptr;
209 }
210
211
212 static SimpleXMLType _getXMLType(const char* itr, const char* itrEnd, size_t &toff)
213 {
214     toff = 0;
215     if (itr[1] == '/') {
216         toff = 1;
217         return SimpleXMLType::Close;
218     } else if (itr[1] == '?') {
219         toff = 1;
220         return SimpleXMLType::Processing;
221     } else if (itr[1] == '!') {
222         if ((itr + sizeof("<!DOCTYPE>") - 1 < itrEnd) && (!memcmp(itr + 2, "DOCTYPE", sizeof("DOCTYPE") - 1)) && ((itr[2 + sizeof("DOCTYPE") - 1] == '>') || (isspace((unsigned char)itr[2 + sizeof("DOCTYPE") - 1])))) {
223             toff = sizeof("!DOCTYPE") - 1;
224             return SimpleXMLType::Doctype;
225         } else if ((itr + sizeof("<![CDATA[]]>") - 1 < itrEnd) && (!memcmp(itr + 2, "[CDATA[", sizeof("[CDATA[") - 1))) {
226             toff = sizeof("![CDATA[") - 1;
227             return SimpleXMLType::CData;
228         } else if ((itr + sizeof("<!---->") - 1 < itrEnd) && (!memcmp(itr + 2, "--", sizeof("--") - 1))) {
229             toff = sizeof("!--") - 1;
230             return SimpleXMLType::Comment;
231         } else if (itr + sizeof("<!>") - 1 < itrEnd) {
232             toff = sizeof("!") - 1;
233             return SimpleXMLType::DoctypeChild;
234         }
235         return SimpleXMLType::Open;
236     }
237     return SimpleXMLType::Open;
238 }
239
240
241 static char* _strndup(const char* src, unsigned len)
242 {
243     auto ret = (char*)malloc(len + 1);
244     if (!ret) return nullptr;
245     ret[len] = '\0';
246     return (char*)memcpy(ret, src, len);
247 }
248
249 /************************************************************************/
250 /* External Class Implementation                                        */
251 /************************************************************************/
252
253 const char* simpleXmlNodeTypeToString(TVG_UNUSED SvgNodeType type)
254 {
255 #ifdef THORVG_LOG_ENABLED
256     static const char* TYPE_NAMES[] = {
257         "Svg",
258         "G",
259         "Defs",
260         "Animation",
261         "Arc",
262         "Circle",
263         "Ellipse",
264         "Image",
265         "Line",
266         "Path",
267         "Polygon",
268         "Polyline",
269         "Rect",
270         "Text",
271         "TextArea",
272         "Tspan",
273         "Use",
274         "Video",
275         "ClipPath",
276         "Mask",
277         "Symbol",
278         "Unknown",
279     };
280     return TYPE_NAMES[(int) type];
281 #endif
282     return nullptr;
283 }
284
285
286 bool isIgnoreUnsupportedLogElements(TVG_UNUSED const char* tagName)
287 {
288 #ifdef THORVG_LOG_ENABLED
289     const auto elementsNum = 1;
290     const char* const elements[] = { "title" };
291
292     for (unsigned int i = 0; i < elementsNum; ++i) {
293         if (!strncmp(tagName, elements[i], strlen(tagName))) {
294             return true;
295         }
296     }
297     return false;
298 #else
299     return true;
300 #endif
301 }
302
303
304 bool simpleXmlParseAttributes(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data)
305 {
306     const char *itr = buf, *itrEnd = buf + bufLength;
307     char* tmpBuf = (char*)malloc(bufLength + 1);
308
309     if (!buf || !func || !tmpBuf) goto error;
310
311     while (itr < itrEnd) {
312         const char* p = _skipWhiteSpacesAndXmlEntities(itr, itrEnd);
313         const char *key, *keyEnd, *value, *valueEnd;
314         char* tval;
315
316         if (p == itrEnd) goto success;
317
318         key = p;
319         for (keyEnd = key; keyEnd < itrEnd; keyEnd++) {
320             if ((*keyEnd == '=') || (isspace((unsigned char)*keyEnd))) break;
321         }
322         if (keyEnd == itrEnd) goto error;
323         if (keyEnd == key) continue;
324
325         if (*keyEnd == '=') value = keyEnd + 1;
326         else {
327             value = (const char*)memchr(keyEnd, '=', itrEnd - keyEnd);
328             if (!value) goto error;
329             value++;
330         }
331         keyEnd = _simpleXmlUnskipXmlEntities(keyEnd, key);
332
333         value = _skipWhiteSpacesAndXmlEntities(value, itrEnd);
334         if (value == itrEnd) goto error;
335
336         if ((*value == '"') || (*value == '\'')) {
337             valueEnd = (const char*)memchr(value + 1, *value, itrEnd - value);
338             if (!valueEnd) goto error;
339             value++;
340         } else {
341             valueEnd = _simpleXmlFindWhiteSpace(value, itrEnd);
342         }
343
344         itr = valueEnd + 1;
345
346         value = _skipWhiteSpacesAndXmlEntities(value, itrEnd);
347         valueEnd = _unskipWhiteSpacesAndXmlEntities(valueEnd, value);
348
349         memcpy(tmpBuf, key, keyEnd - key);
350         tmpBuf[keyEnd - key] = '\0';
351
352         tval = tmpBuf + (keyEnd - key) + 1;
353         int i = 0;
354         while (value < valueEnd) {
355             value = _simpleXmlSkipXmlEntities(value, valueEnd);
356             tval[i++] = *value;
357             value++;
358         }
359         tval[i] = '\0';
360
361         if (!func((void*)data, tmpBuf, tval)) {
362             if (!_isIgnoreUnsupportedLogAttributes(tmpBuf, tval)) {
363                 TVGLOG("SVG", "Unsupported attributes used [Elements type: %s][Id : %s][Attribute: %s][Value: %s]", simpleXmlNodeTypeToString(((SvgLoaderData*)data)->svgParse->node->type), ((SvgLoaderData*)data)->svgParse->node->id ? ((SvgLoaderData*)data)->svgParse->node->id : "NO_ID", tmpBuf, tval ? tval : "NONE");
364             }
365         }
366     }
367
368 success:
369     free(tmpBuf);
370     return true;
371
372 error:
373     free(tmpBuf);
374     return false;
375 }
376
377
378 bool simpleXmlParse(const char* buf, unsigned bufLength, bool strip, simpleXMLCb func, const void* data)
379 {
380     const char *itr = buf, *itrEnd = buf + bufLength;
381
382     if (!buf || !func) return false;
383
384     while (itr < itrEnd) {
385         if (itr[0] == '<') {
386             //Invalid case
387             if (itr + 1 >= itrEnd) return false;
388
389             size_t toff = 0;
390             SimpleXMLType type = _getXMLType(itr, itrEnd, toff);
391
392             const char* p;
393             if (type == SimpleXMLType::CData) p = _simpleXmlFindEndCdataTag(itr + 1 + toff, itrEnd);
394             else if (type == SimpleXMLType::DoctypeChild) p = _simpleXmlFindDoctypeChildEndTag(itr + 1 + toff, itrEnd);
395             else if (type == SimpleXMLType::Comment) p = _simpleXmlFindEndCommentTag(itr + 1 + toff, itrEnd);
396             else p = _simpleXmlFindEndTag(itr + 1 + toff, itrEnd);
397
398             if (p) {
399                 //Invalid case: '<' nested
400                 if (*p == '<' && type != SimpleXMLType::Doctype) return false;
401                 const char *start, *end;
402
403                 start = itr + 1 + toff;
404                 end = p;
405
406                 switch (type) {
407                     case SimpleXMLType::Open: {
408                         if (p[-1] == '/') {
409                             type = SimpleXMLType::OpenEmpty;
410                             end--;
411                         }
412                         break;
413                     }
414                     case SimpleXMLType::CData: {
415                         if (!memcmp(p - 2, "]]", 2)) end -= 2;
416                         break;
417                     }
418                     case SimpleXMLType::Processing: {
419                         if (p[-1] == '?') end--;
420                         break;
421                     }
422                     case SimpleXMLType::Comment: {
423                         if (!memcmp(p - 2, "--", 2)) end -= 2;
424                         break;
425                     }
426                     default: {
427                         break;
428                     }
429                 }
430
431                 if (strip && (type != SimpleXMLType::CData)) {
432                     start = _skipWhiteSpacesAndXmlEntities(start, end);
433                     end = _unskipWhiteSpacesAndXmlEntities(end, start);
434                 }
435
436                 if (!func((void*)data, type, start, (unsigned int)(end - start))) return false;
437
438                 itr = p + 1;
439             } else {
440                 return false;
441             }
442         } else {
443             const char *p, *end;
444
445             if (strip) {
446                 p = itr;
447                 p = _skipWhiteSpacesAndXmlEntities(p, itrEnd);
448                 if (p) {
449                     if (!func((void*)data, SimpleXMLType::Ignored, itr, (unsigned int)(p - itr))) return false;
450                     itr = p;
451                 }
452             }
453
454             p = _simpleXmlFindStartTag(itr, itrEnd);
455             if (!p) p = itrEnd;
456
457             end = p;
458             if (strip) end = _unskipWhiteSpacesAndXmlEntities(end, itr);
459
460             if (itr != end && !func((void*)data, SimpleXMLType::Data, itr, (unsigned int)(end - itr))) return false;
461
462             if (strip && (end < p) && !func((void*)data, SimpleXMLType::Ignored, end, (unsigned int)(p - end))) return false;
463
464             itr = p;
465         }
466     }
467     return true;
468 }
469
470
471 bool simpleXmlParseW3CAttribute(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data)
472 {
473     const char* end;
474     char* key;
475     char* val;
476     char* next;
477
478     if (!buf) return false;
479
480     end = buf + bufLength;
481     key = (char*)alloca(end - buf + 1);
482     val = (char*)alloca(end - buf + 1);
483
484     if (buf == end) return true;
485
486     do {
487         char* sep = (char*)strchr(buf, ':');
488         next = (char*)strchr(buf, ';');
489         if (sep >= end) {
490             next = nullptr;
491             sep = nullptr;
492         }
493         if (next >= end) next = nullptr;
494
495         key[0] = '\0';
496         val[0] = '\0';
497
498         if (next == nullptr && sep != nullptr) {
499             memcpy(key, buf, sep - buf);
500             key[sep - buf] = '\0';
501
502             memcpy(val, sep + 1, end - sep - 1);
503             val[end - sep - 1] = '\0';
504         } else if (sep < next && sep != nullptr) {
505             memcpy(key, buf, sep - buf);
506             key[sep - buf] = '\0';
507
508             memcpy(val, sep + 1, next - sep - 1);
509             val[next - sep - 1] = '\0';
510         } else if (next) {
511             memcpy(key, buf, next - buf);
512             key[next - buf] = '\0';
513         }
514
515         if (key[0]) {
516             key = const_cast<char*>(_simpleXmlSkipWhiteSpace(key, key + strlen(key)));
517             key[_simpleXmlUnskipWhiteSpace(key + strlen(key) , key) - key] = '\0';
518             val = const_cast<char*>(_simpleXmlSkipWhiteSpace(val, val + strlen(val)));
519             val[_simpleXmlUnskipWhiteSpace(val + strlen(val) , val) - val] = '\0';
520
521             if (!func((void*)data, key, val)) {
522                 if (!_isIgnoreUnsupportedLogAttributes(key, val)) {
523                     TVGLOG("SVG", "Unsupported attributes used [Elements type: %s][Id : %s][Attribute: %s][Value: %s]", simpleXmlNodeTypeToString(((SvgLoaderData*)data)->svgParse->node->type), ((SvgLoaderData*)data)->svgParse->node->id ? ((SvgLoaderData*)data)->svgParse->node->id : "NO_ID", key, val ? val : "NONE");
524                 }
525             }
526         }
527
528         buf = next + 1;
529     } while (next != nullptr);
530
531     return true;
532 }
533
534
535 /*
536  * Supported formats:
537  * tag {}, .name {}, tag.name{}
538  */
539 const char* simpleXmlParseCSSAttribute(const char* buf, unsigned bufLength, char** tag, char** name, const char** attrs, unsigned* attrsLength)
540 {
541     if (!buf) return nullptr;
542
543     *tag = *name = nullptr;
544     *attrsLength = 0;
545
546     auto itr = _simpleXmlSkipWhiteSpace(buf, buf + bufLength);
547     auto itrEnd = (const char*)memchr(buf, '{', bufLength);
548
549     if (!itrEnd || itr == itrEnd) return nullptr;
550
551     auto nextElement = (const char*)memchr(itrEnd, '}', bufLength - (itrEnd - buf));
552     if (!nextElement) return nullptr;
553
554     *attrs = itrEnd + 1;
555     *attrsLength = nextElement - *attrs;
556
557     const char *p;
558
559     itrEnd = _simpleXmlUnskipWhiteSpace(itrEnd, itr);
560     if (*(itrEnd - 1) == '.') return nullptr;
561
562     for (p = itr; p < itrEnd; p++) {
563         if (*p == '.') break;
564     }
565
566     if (p == itr) *tag = strdup("all");
567     else *tag = _strndup(itr, p - itr);
568
569     if (p == itrEnd) *name = nullptr;
570     else *name = _strndup(p + 1, itrEnd - p - 1);
571
572     return (nextElement ? nextElement + 1 : nullptr);
573 }
574
575
576 const char* simpleXmlFindAttributesTag(const char* buf, unsigned bufLength)
577 {
578     const char *itr = buf, *itrEnd = buf + bufLength;
579
580     for (; itr < itrEnd; itr++) {
581         if (!isspace((unsigned char)*itr)) {
582             //User skip tagname and already gave it the attributes.
583             if (*itr == '=') return buf;
584         } else {
585             itr = _simpleXmlUnskipXmlEntities(itr, buf);
586             if (itr == itrEnd) return nullptr;
587             return itr;
588         }
589     }
590
591     return nullptr;
592 }