svg_loader: buflen arg used in the simpleXmlParseW3CAttribute()
[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 /************************************************************************/
240 /* External Class Implementation                                        */
241 /************************************************************************/
242
243 const char* simpleXmlNodeTypeToString(TVG_UNUSED SvgNodeType type)
244 {
245 #ifdef THORVG_LOG_ENABLED
246     static const char* TYPE_NAMES[] = {
247         "Svg",
248         "G",
249         "Defs",
250         "Animation",
251         "Arc",
252         "Circle",
253         "Ellipse",
254         "Image",
255         "Line",
256         "Path",
257         "Polygon",
258         "Polyline",
259         "Rect",
260         "Text",
261         "TextArea",
262         "Tspan",
263         "Use",
264         "Video",
265         "ClipPath",
266         "Mask",
267         "Unknown",
268     };
269     return TYPE_NAMES[(int) type];
270 #endif
271     return nullptr;
272 }
273
274
275 bool isIgnoreUnsupportedLogElements(TVG_UNUSED const char* tagName)
276 {
277 #ifdef THORVG_LOG_ENABLED
278     const auto elementsNum = 1;
279     const char* const elements[] = { "title" };
280
281     for (unsigned int i = 0; i < elementsNum; ++i) {
282         if (!strncmp(tagName, elements[i], strlen(tagName))) {
283             return true;
284         }
285     }
286     return false;
287 #else
288     return true;
289 #endif
290 }
291
292
293 bool simpleXmlParseAttributes(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data)
294 {
295     const char *itr = buf, *itrEnd = buf + bufLength;
296     char* tmpBuf = (char*)alloca(bufLength + 1);
297
298     if (!buf || !func) return false;
299
300     while (itr < itrEnd) {
301         const char* p = _skipWhiteSpacesAndXmlEntities(itr, itrEnd);
302         const char *key, *keyEnd, *value, *valueEnd;
303         char* tval;
304
305         if (p == itrEnd) return true;
306
307         key = p;
308         for (keyEnd = key; keyEnd < itrEnd; keyEnd++) {
309             if ((*keyEnd == '=') || (isspace((unsigned char)*keyEnd))) break;
310         }
311         if (keyEnd == itrEnd) return false;
312         if (keyEnd == key) continue;
313
314         if (*keyEnd == '=') value = keyEnd + 1;
315         else {
316             value = (const char*)memchr(keyEnd, '=', itrEnd - keyEnd);
317             if (!value) return false;
318             value++;
319         }
320         keyEnd = _simpleXmlUnskipXmlEntities(keyEnd, key);
321
322         value = _skipWhiteSpacesAndXmlEntities(value, itrEnd);
323         if (value == itrEnd) return false;
324
325         if ((*value == '"') || (*value == '\'')) {
326             valueEnd = (const char*)memchr(value + 1, *value, itrEnd - value);
327             if (!valueEnd) return false;
328             value++;
329         } else {
330             valueEnd = _simpleXmlFindWhiteSpace(value, itrEnd);
331         }
332
333         itr = valueEnd + 1;
334
335         value = _skipWhiteSpacesAndXmlEntities(value, itrEnd);
336         valueEnd = _unskipWhiteSpacesAndXmlEntities(valueEnd, value);
337
338         memcpy(tmpBuf, key, keyEnd - key);
339         tmpBuf[keyEnd - key] = '\0';
340
341         tval = tmpBuf + (keyEnd - key) + 1;
342         int i = 0;
343         while (value < valueEnd) {
344             value = _simpleXmlSkipXmlEntities(value, valueEnd);
345             tval[i++] = *value;
346             value++;
347         }
348         tval[i] = '\0';
349
350         if (!func((void*)data, tmpBuf, tval)) {
351             if (!_isIgnoreUnsupportedLogAttributes(tmpBuf, tval)) {
352                 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");
353             }
354         }
355     }
356     return true;
357 }
358
359
360 bool simpleXmlParse(const char* buf, unsigned bufLength, bool strip, simpleXMLCb func, const void* data)
361 {
362     const char *itr = buf, *itrEnd = buf + bufLength;
363
364     if (!buf || !func) return false;
365
366     while (itr < itrEnd) {
367         if (itr[0] == '<') {
368             //Invalid case
369             if (itr + 1 >= itrEnd) return false;
370
371             size_t toff = 0;
372             SimpleXMLType type = _getXMLType(itr, itrEnd, toff);
373
374             const char* p;
375             if (type == SimpleXMLType::CData) p = _simpleXmlFindEndCdataTag(itr + 1 + toff, itrEnd);
376             else if (type == SimpleXMLType::DoctypeChild) p = _simpleXmlFindDoctypeChildEndTag(itr + 1 + toff, itrEnd);
377             else if (type == SimpleXMLType::Comment) p = _simpleXmlFindEndCommentTag(itr + 1 + toff, itrEnd);
378             else p = _simpleXmlFindEndTag(itr + 1 + toff, itrEnd);
379
380             if (p) {
381                 //Invalid case: '<' nested
382                 if (*p == '<') return false;
383                 const char *start, *end;
384
385                 start = itr + 1 + toff;
386                 end = p;
387
388                 switch (type) {
389                     case SimpleXMLType::Open: {
390                         if (p[-1] == '/') {
391                             type = SimpleXMLType::OpenEmpty;
392                             end--;
393                         }
394                         break;
395                     }
396                     case SimpleXMLType::CData: {
397                         if (!memcmp(p - 2, "]]", 2)) end -= 2;
398                         break;
399                     }
400                     case SimpleXMLType::Processing: {
401                         if (p[-1] == '?') end--;
402                         break;
403                     }
404                     case SimpleXMLType::Comment: {
405                         if (!memcmp(p - 2, "--", 2)) end -= 2;
406                         break;
407                     }
408                     default: {
409                         break;
410                     }
411                 }
412
413                 if (strip && (type != SimpleXMLType::CData)) {
414                     start = _skipWhiteSpacesAndXmlEntities(start, end);
415                     end = _unskipWhiteSpacesAndXmlEntities(end, start);
416                 }
417
418                 if (!func((void*)data, type, start, (unsigned int)(end - start))) return false;
419
420                 itr = p + 1;
421             } else {
422                 return false;
423             }
424         } else {
425             const char *p, *end;
426
427             if (strip) {
428                 p = itr;
429                 p = _skipWhiteSpacesAndXmlEntities(p, itrEnd);
430                 if (p) {
431                     if (!func((void*)data, SimpleXMLType::Ignored, itr, (unsigned int)(p - itr))) return false;
432                     itr = p;
433                 }
434             }
435
436             p = _simpleXmlFindStartTag(itr, itrEnd);
437             if (!p) p = itrEnd;
438
439             end = p;
440             if (strip) end = _unskipWhiteSpacesAndXmlEntities(end, itr);
441
442             if (itr != end && !func((void*)data, SimpleXMLType::Data, itr, (unsigned int)(end - itr))) return false;
443
444             if (strip && (end < p) && !func((void*)data, SimpleXMLType::Ignored, end, (unsigned int)(p - end))) return false;
445
446             itr = p;
447         }
448     }
449     return true;
450 }
451
452
453 bool simpleXmlParseW3CAttribute(const char* buf, unsigned buflen, simpleXMLAttributeCb func, const void* data)
454 {
455     const char* end;
456     char* key;
457     char* val;
458     char* next;
459
460     if (!buf) return false;
461
462     end = buf + buflen;
463     key = (char*)alloca(end - buf + 1);
464     val = (char*)alloca(end - buf + 1);
465
466     if (buf == end) return true;
467
468     do {
469         char* sep = (char*)strchr(buf, ':');
470         next = (char*)strchr(buf, ';');
471         if (sep >= end)
472         {
473             next = nullptr;
474             sep = nullptr;
475         }
476         if (next >= end) next = nullptr;
477
478         key[0] = '\0';
479         val[0] = '\0';
480
481         if (next == nullptr && sep != nullptr) {
482             memcpy(key, buf, sep - buf);
483             key[sep - buf] = '\0';
484
485             memcpy(val, sep + 1, end - sep - 1);
486             val[end - sep - 1] = '\0';
487         } else if (sep < next && sep != nullptr) {
488             memcpy(key, buf, sep - buf);
489             key[sep - buf] = '\0';
490
491             memcpy(val, sep + 1, next - sep - 1);
492             val[next - sep - 1] = '\0';
493         } else if (next) {
494             memcpy(key, buf, next - buf);
495             key[next - buf] = '\0';
496         }
497
498         if (key[0]) {
499             key = const_cast<char*>(_simpleXmlSkipWhiteSpace(key, key + strlen(key)));
500             key[_simpleXmlUnskipWhiteSpace(key + strlen(key) , key) - key] = '\0';
501             val = const_cast<char*>(_simpleXmlSkipWhiteSpace(val, val + strlen(val)));
502             val[_simpleXmlUnskipWhiteSpace(val + strlen(val) , val) - val] = '\0';
503
504             if (!func((void*)data, key, val)) {
505                 if (!_isIgnoreUnsupportedLogAttributes(key, val)) {
506                     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");
507                 }
508             }
509         }
510
511         buf = next + 1;
512     } while (next != nullptr);
513
514     return true;
515 }
516
517
518 /*
519  * Supported formats:
520  * tag {}, .name {}, tag.name{}
521  */
522 const char* simpleXmlParseCSSAttribute(const char* buf, unsigned bufLength, char** tag, char** name, const char** attrs, unsigned* attrsLength)
523 {
524     if (!buf) return nullptr;
525
526     *tag = *name = nullptr;
527     *attrsLength = 0;
528
529     auto itr = _simpleXmlSkipWhiteSpace(buf, buf + bufLength);
530     auto itrEnd = (const char*)memchr(buf, '{', bufLength);
531
532     if (!itrEnd || itr == itrEnd) return nullptr;
533
534     auto nextElement = (const char*)memchr(itrEnd, '}', bufLength - (itrEnd - buf));
535     if (!nextElement) return nullptr;
536
537     *attrs = itrEnd + 1;
538     *attrsLength = nextElement - *attrs;
539
540     const char *p;
541
542     itrEnd = _simpleXmlUnskipWhiteSpace(itrEnd, itr);
543     if (*(itrEnd - 1) == '.') return nullptr;
544
545     for (p = itr; p < itrEnd; p++) {
546         if (*p == '.') break;
547     }
548
549     if (p == itr) *tag = strdup("all");
550     else *tag = strndup(itr, p - itr);
551
552     if (p == itrEnd) *name = nullptr;
553     else *name = strndup(p + 1, itrEnd - p - 1);
554
555     return (nextElement ? nextElement + 1 : nullptr);
556 }
557
558
559 const char* simpleXmlFindAttributesTag(const char* buf, unsigned bufLength)
560 {
561     const char *itr = buf, *itrEnd = buf + bufLength;
562
563     for (; itr < itrEnd; itr++) {
564         if (!isspace((unsigned char)*itr)) {
565             //User skip tagname and already gave it the attributes.
566             if (*itr == '=') return buf;
567         } else {
568             itr = _simpleXmlUnskipXmlEntities(itr, buf);
569             if (itr == itrEnd) return nullptr;
570             return itr;
571         }
572     }
573
574     return nullptr;
575 }