svg_loader: custom _strndup added
[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 #else
30     #include <alloca.h>
31 #endif
32
33 #include "tvgXmlParser.h"
34
35 /************************************************************************/
36 /* Internal Class Implementation                                        */
37 /************************************************************************/
38
39 bool _isIgnoreUnsupportedLogAttributes(TVG_UNUSED const char* tagAttribute, TVG_UNUSED const char* tagValue)
40 {
41 #ifdef THORVG_LOG_ENABLED
42     const auto attributesNum = 6;
43     const struct
44     {
45         const char* tag;
46         bool tagWildcard; //If true, it is assumed that a wildcard is used after the tag. (ex: tagName*)
47         const char* value;
48     } attributes[] = {
49         {"id", false, nullptr},
50         {"data-name", false, nullptr},
51         {"overflow", false, "visible"},
52         {"version", false, nullptr},
53         {"xmlns", true, nullptr},
54         {"xml:space", false, nullptr},
55     };
56
57     for (unsigned int i = 0; i < attributesNum; ++i) {
58         if (!strncmp(tagAttribute, attributes[i].tag, attributes[i].tagWildcard ? strlen(attributes[i].tag) : strlen(tagAttribute))) {
59             if (attributes[i].value && tagValue) {
60                 if (!strncmp(tagValue, attributes[i].value, strlen(tagValue))) {
61                     return true;
62                 } else continue;
63             }
64             return true;
65         }
66     }
67     return false;
68 #endif
69     return true;
70 }
71
72
73 static const char* _simpleXmlFindWhiteSpace(const char* itr, const char* itrEnd)
74 {
75     for (; itr < itrEnd; itr++) {
76         if (isspace((unsigned char)*itr)) break;
77     }
78     return itr;
79 }
80
81
82 static const char* _simpleXmlSkipWhiteSpace(const char* itr, const char* itrEnd)
83 {
84     for (; itr < itrEnd; itr++) {
85         if (!isspace((unsigned char)*itr)) break;
86     }
87     return itr;
88 }
89
90
91 static const char* _simpleXmlUnskipWhiteSpace(const char* itr, const char* itrStart)
92 {
93     for (itr--; itr > itrStart; itr--) {
94         if (!isspace((unsigned char)*itr)) break;
95     }
96     return itr + 1;
97 }
98
99
100 static const char* _simpleXmlSkipXmlEntities(const char* itr, const char* itrEnd)
101 {
102     auto p = itr;
103     while (itr < itrEnd && *itr == '&') {
104         for (int i = 0; i < NUMBER_OF_XML_ENTITIES; ++i) {
105             if (strncmp(itr, xmlEntity[i], xmlEntityLength[i]) == 0) {
106                 itr += xmlEntityLength[i];
107                 break;
108             }
109         }
110         if (itr == p) break;
111         p = itr;
112     }
113     return itr;
114 }
115
116
117 static const char* _simpleXmlUnskipXmlEntities(const char* itr, const char* itrStart)
118 {
119     auto p = itr;
120     while (itr > itrStart && *(itr - 1) == ';') {
121         for (int i = 0; i < NUMBER_OF_XML_ENTITIES; ++i) {
122             if (itr - xmlEntityLength[i] > itrStart &&
123                 strncmp(itr - xmlEntityLength[i], xmlEntity[i], xmlEntityLength[i]) == 0) {
124                 itr -= xmlEntityLength[i];
125                 break;
126             }
127         }
128         if (itr == p) break;
129         p = itr;
130     }
131     return itr;
132 }
133
134
135 static const char* _skipWhiteSpacesAndXmlEntities(const char* itr, const char* itrEnd)
136 {
137     itr = _simpleXmlSkipWhiteSpace(itr, itrEnd);
138     auto p = itr;
139     while (true) {
140         if (p != (itr = _simpleXmlSkipXmlEntities(itr, itrEnd))) p = itr;
141         else break;
142         if (p != (itr = _simpleXmlSkipWhiteSpace(itr, itrEnd))) p = itr;
143         else break;
144     }
145     return itr;
146 }
147
148
149 static const char* _unskipWhiteSpacesAndXmlEntities(const char* itr, const char* itrStart)
150 {
151     itr = _simpleXmlUnskipWhiteSpace(itr, itrStart);
152     auto p = itr;
153     while (true) {
154         if (p != (itr = _simpleXmlUnskipXmlEntities(itr, itrStart))) p = itr;
155         else break;
156         if (p != (itr = _simpleXmlUnskipWhiteSpace(itr, itrStart))) p = itr;
157         else break;
158     }
159     return itr;
160 }
161
162
163 static const char* _simpleXmlFindStartTag(const char* itr, const char* itrEnd)
164 {
165     return (const char*)memchr(itr, '<', itrEnd - itr);
166 }
167
168
169 static const char* _simpleXmlFindEndTag(const char* itr, const char* itrEnd)
170 {
171     bool insideQuote = false;
172     for (; itr < itrEnd; itr++) {
173         if (*itr == '"') insideQuote = !insideQuote;
174         if (!insideQuote) {
175             if ((*itr == '>') || (*itr == '<'))
176                 return itr;
177         }
178     }
179     return nullptr;
180 }
181
182
183 static const char* _simpleXmlFindEndCommentTag(const char* itr, const char* itrEnd)
184 {
185     for (; itr < itrEnd; itr++) {
186         if ((*itr == '-') && ((itr + 1 < itrEnd) && (*(itr + 1) == '-')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2;
187     }
188     return nullptr;
189 }
190
191
192 static const char* _simpleXmlFindEndCdataTag(const char* itr, const char* itrEnd)
193 {
194     for (; itr < itrEnd; itr++) {
195         if ((*itr == ']') && ((itr + 1 < itrEnd) && (*(itr + 1) == ']')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2;
196     }
197     return nullptr;
198 }
199
200
201 static const char* _simpleXmlFindDoctypeChildEndTag(const char* itr, const char* itrEnd)
202 {
203     for (; itr < itrEnd; itr++) {
204         if (*itr == '>') return itr;
205     }
206     return nullptr;
207 }
208
209
210 static SimpleXMLType _getXMLType(const char* itr, const char* itrEnd, size_t &toff)
211 {
212     toff = 0;
213     if (itr[1] == '/') {
214         toff = 1;
215         return SimpleXMLType::Close;
216     } else if (itr[1] == '?') {
217         toff = 1;
218         return SimpleXMLType::Processing;
219     } else if (itr[1] == '!') {
220         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])))) {
221             toff = sizeof("!DOCTYPE") - 1;
222             return SimpleXMLType::Doctype;
223         } else if ((itr + sizeof("<![CDATA[]]>") - 1 < itrEnd) && (!memcmp(itr + 2, "[CDATA[", sizeof("[CDATA[") - 1))) {
224             toff = sizeof("![CDATA[") - 1;
225             return SimpleXMLType::CData;
226         } else if ((itr + sizeof("<!---->") - 1 < itrEnd) && (!memcmp(itr + 2, "--", sizeof("--") - 1))) {
227             toff = sizeof("!--") - 1;
228             return SimpleXMLType::Comment;
229         } else if (itr + sizeof("<!>") - 1 < itrEnd) {
230             toff = sizeof("!") - 1;
231             return SimpleXMLType::DoctypeChild;
232         }
233         return SimpleXMLType::Open;
234     }
235     return SimpleXMLType::Open;
236 }
237
238
239 static char* _strndup(const char* src, unsigned len)
240 {
241     auto ret = (char*)malloc(len + 1);
242     if (!ret) return nullptr;
243     ret[len] = '\0';
244     return (char*)memcpy(ret, src, len);
245 }
246
247 /************************************************************************/
248 /* External Class Implementation                                        */
249 /************************************************************************/
250
251 const char* simpleXmlNodeTypeToString(TVG_UNUSED SvgNodeType type)
252 {
253 #ifdef THORVG_LOG_ENABLED
254     static const char* TYPE_NAMES[] = {
255         "Svg",
256         "G",
257         "Defs",
258         "Animation",
259         "Arc",
260         "Circle",
261         "Ellipse",
262         "Image",
263         "Line",
264         "Path",
265         "Polygon",
266         "Polyline",
267         "Rect",
268         "Text",
269         "TextArea",
270         "Tspan",
271         "Use",
272         "Video",
273         "ClipPath",
274         "Mask",
275         "Unknown",
276     };
277     return TYPE_NAMES[(int) type];
278 #endif
279     return nullptr;
280 }
281
282
283 bool isIgnoreUnsupportedLogElements(TVG_UNUSED const char* tagName)
284 {
285 #ifdef THORVG_LOG_ENABLED
286     const auto elementsNum = 1;
287     const char* const elements[] = { "title" };
288
289     for (unsigned int i = 0; i < elementsNum; ++i) {
290         if (!strncmp(tagName, elements[i], strlen(tagName))) {
291             return true;
292         }
293     }
294     return false;
295 #else
296     return true;
297 #endif
298 }
299
300
301 bool simpleXmlParseAttributes(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data)
302 {
303     const char *itr = buf, *itrEnd = buf + bufLength;
304     char* tmpBuf = (char*)alloca(bufLength + 1);
305
306     if (!buf || !func) return false;
307
308     while (itr < itrEnd) {
309         const char* p = _skipWhiteSpacesAndXmlEntities(itr, itrEnd);
310         const char *key, *keyEnd, *value, *valueEnd;
311         char* tval;
312
313         if (p == itrEnd) return true;
314
315         key = p;
316         for (keyEnd = key; keyEnd < itrEnd; keyEnd++) {
317             if ((*keyEnd == '=') || (isspace((unsigned char)*keyEnd))) break;
318         }
319         if (keyEnd == itrEnd) return false;
320         if (keyEnd == key) continue;
321
322         if (*keyEnd == '=') value = keyEnd + 1;
323         else {
324             value = (const char*)memchr(keyEnd, '=', itrEnd - keyEnd);
325             if (!value) return false;
326             value++;
327         }
328         keyEnd = _simpleXmlUnskipXmlEntities(keyEnd, key);
329
330         value = _skipWhiteSpacesAndXmlEntities(value, itrEnd);
331         if (value == itrEnd) return false;
332
333         if ((*value == '"') || (*value == '\'')) {
334             valueEnd = (const char*)memchr(value + 1, *value, itrEnd - value);
335             if (!valueEnd) return false;
336             value++;
337         } else {
338             valueEnd = _simpleXmlFindWhiteSpace(value, itrEnd);
339         }
340
341         itr = valueEnd + 1;
342
343         value = _skipWhiteSpacesAndXmlEntities(value, itrEnd);
344         valueEnd = _unskipWhiteSpacesAndXmlEntities(valueEnd, value);
345
346         memcpy(tmpBuf, key, keyEnd - key);
347         tmpBuf[keyEnd - key] = '\0';
348
349         tval = tmpBuf + (keyEnd - key) + 1;
350         int i = 0;
351         while (value < valueEnd) {
352             value = _simpleXmlSkipXmlEntities(value, valueEnd);
353             tval[i++] = *value;
354             value++;
355         }
356         tval[i] = '\0';
357
358         if (!func((void*)data, tmpBuf, tval)) {
359             if (!_isIgnoreUnsupportedLogAttributes(tmpBuf, tval)) {
360                 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");
361             }
362         }
363     }
364     return true;
365 }
366
367
368 bool simpleXmlParse(const char* buf, unsigned bufLength, bool strip, simpleXMLCb func, const void* data)
369 {
370     const char *itr = buf, *itrEnd = buf + bufLength;
371
372     if (!buf || !func) return false;
373
374     while (itr < itrEnd) {
375         if (itr[0] == '<') {
376             //Invalid case
377             if (itr + 1 >= itrEnd) return false;
378
379             size_t toff = 0;
380             SimpleXMLType type = _getXMLType(itr, itrEnd, toff);
381
382             const char* p;
383             if (type == SimpleXMLType::CData) p = _simpleXmlFindEndCdataTag(itr + 1 + toff, itrEnd);
384             else if (type == SimpleXMLType::DoctypeChild) p = _simpleXmlFindDoctypeChildEndTag(itr + 1 + toff, itrEnd);
385             else if (type == SimpleXMLType::Comment) p = _simpleXmlFindEndCommentTag(itr + 1 + toff, itrEnd);
386             else p = _simpleXmlFindEndTag(itr + 1 + toff, itrEnd);
387
388             if (p) {
389                 //Invalid case: '<' nested
390                 if (*p == '<') return false;
391                 const char *start, *end;
392
393                 start = itr + 1 + toff;
394                 end = p;
395
396                 switch (type) {
397                     case SimpleXMLType::Open: {
398                         if (p[-1] == '/') {
399                             type = SimpleXMLType::OpenEmpty;
400                             end--;
401                         }
402                         break;
403                     }
404                     case SimpleXMLType::CData: {
405                         if (!memcmp(p - 2, "]]", 2)) end -= 2;
406                         break;
407                     }
408                     case SimpleXMLType::Processing: {
409                         if (p[-1] == '?') end--;
410                         break;
411                     }
412                     case SimpleXMLType::Comment: {
413                         if (!memcmp(p - 2, "--", 2)) end -= 2;
414                         break;
415                     }
416                     default: {
417                         break;
418                     }
419                 }
420
421                 if (strip && (type != SimpleXMLType::CData)) {
422                     start = _skipWhiteSpacesAndXmlEntities(start, end);
423                     end = _unskipWhiteSpacesAndXmlEntities(end, start);
424                 }
425
426                 if (!func((void*)data, type, start, (unsigned int)(end - start))) return false;
427
428                 itr = p + 1;
429             } else {
430                 return false;
431             }
432         } else {
433             const char *p, *end;
434
435             if (strip) {
436                 p = itr;
437                 p = _skipWhiteSpacesAndXmlEntities(p, itrEnd);
438                 if (p) {
439                     if (!func((void*)data, SimpleXMLType::Ignored, itr, (unsigned int)(p - itr))) return false;
440                     itr = p;
441                 }
442             }
443
444             p = _simpleXmlFindStartTag(itr, itrEnd);
445             if (!p) p = itrEnd;
446
447             end = p;
448             if (strip) end = _unskipWhiteSpacesAndXmlEntities(end, itr);
449
450             if (itr != end && !func((void*)data, SimpleXMLType::Data, itr, (unsigned int)(end - itr))) return false;
451
452             if (strip && (end < p) && !func((void*)data, SimpleXMLType::Ignored, end, (unsigned int)(p - end))) return false;
453
454             itr = p;
455         }
456     }
457     return true;
458 }
459
460
461 bool simpleXmlParseW3CAttribute(const char* buf, unsigned buflen, simpleXMLAttributeCb func, const void* data)
462 {
463     const char* end;
464     char* key;
465     char* val;
466     char* next;
467
468     if (!buf) return false;
469
470     end = buf + buflen;
471     key = (char*)alloca(end - buf + 1);
472     val = (char*)alloca(end - buf + 1);
473
474     if (buf == end) return true;
475
476     do {
477         char* sep = (char*)strchr(buf, ':');
478         next = (char*)strchr(buf, ';');
479         if (sep >= end)
480         {
481             next = nullptr;
482             sep = nullptr;
483         }
484         if (next >= end) next = nullptr;
485
486         key[0] = '\0';
487         val[0] = '\0';
488
489         if (next == nullptr && sep != nullptr) {
490             memcpy(key, buf, sep - buf);
491             key[sep - buf] = '\0';
492
493             memcpy(val, sep + 1, end - sep - 1);
494             val[end - sep - 1] = '\0';
495         } else if (sep < next && sep != nullptr) {
496             memcpy(key, buf, sep - buf);
497             key[sep - buf] = '\0';
498
499             memcpy(val, sep + 1, next - sep - 1);
500             val[next - sep - 1] = '\0';
501         } else if (next) {
502             memcpy(key, buf, next - buf);
503             key[next - buf] = '\0';
504         }
505
506         if (key[0]) {
507             key = const_cast<char*>(_simpleXmlSkipWhiteSpace(key, key + strlen(key)));
508             key[_simpleXmlUnskipWhiteSpace(key + strlen(key) , key) - key] = '\0';
509             val = const_cast<char*>(_simpleXmlSkipWhiteSpace(val, val + strlen(val)));
510             val[_simpleXmlUnskipWhiteSpace(val + strlen(val) , val) - val] = '\0';
511
512             if (!func((void*)data, key, val)) {
513                 if (!_isIgnoreUnsupportedLogAttributes(key, val)) {
514                     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");
515                 }
516             }
517         }
518
519         buf = next + 1;
520     } while (next != nullptr);
521
522     return true;
523 }
524
525
526 /*
527  * Supported formats:
528  * tag {}, .name {}, tag.name{}
529  */
530 const char* simpleXmlParseCSSAttribute(const char* buf, unsigned bufLength, char** tag, char** name, const char** attrs, unsigned* attrsLength)
531 {
532     if (!buf) return nullptr;
533
534     *tag = *name = nullptr;
535     *attrsLength = 0;
536
537     auto itr = _simpleXmlSkipWhiteSpace(buf, buf + bufLength);
538     auto itrEnd = (const char*)memchr(buf, '{', bufLength);
539
540     if (!itrEnd || itr == itrEnd) return nullptr;
541
542     auto nextElement = (const char*)memchr(itrEnd, '}', bufLength - (itrEnd - buf));
543     if (!nextElement) return nullptr;
544
545     *attrs = itrEnd + 1;
546     *attrsLength = nextElement - *attrs;
547
548     const char *p;
549
550     itrEnd = _simpleXmlUnskipWhiteSpace(itrEnd, itr);
551     if (*(itrEnd - 1) == '.') return nullptr;
552
553     for (p = itr; p < itrEnd; p++) {
554         if (*p == '.') break;
555     }
556
557     if (p == itr) *tag = strdup("all");
558     else *tag = _strndup(itr, p - itr);
559
560     if (p == itrEnd) *name = nullptr;
561     else *name = _strndup(p + 1, itrEnd - p - 1);
562
563     return (nextElement ? nextElement + 1 : nullptr);
564 }
565
566
567 const char* simpleXmlFindAttributesTag(const char* buf, unsigned bufLength)
568 {
569     const char *itr = buf, *itrEnd = buf + bufLength;
570
571     for (; itr < itrEnd; itr++) {
572         if (!isspace((unsigned char)*itr)) {
573             //User skip tagname and already gave it the attributes.
574             if (*itr == '=') return buf;
575         } else {
576             itr = _simpleXmlUnskipXmlEntities(itr, buf);
577             if (itr == itrEnd) return nullptr;
578             return itr;
579         }
580     }
581
582     return nullptr;
583 }