xref: /freebsd/contrib/expat/examples/element_declarations.c (revision dece5b05f577fc4c6b89af7ad48dfbdf7cc4945c)
1 /* Read an XML document from standard input and print
2    element declarations (if any) to standard output.
3    It must be used with Expat compiled for UTF-8 output.
4                             __  __            _
5                          ___\ \/ /_ __   __ _| |_
6                         / _ \\  /| '_ \ / _` | __|
7                        |  __//  \| |_) | (_| | |_
8                         \___/_/\_\ .__/ \__,_|\__|
9                                  |_| XML parser
10 
11    Copyright (c) 1997-2000 Thai Open Source Software Center Ltd
12    Copyright (c) 2001-2003 Fred L. Drake, Jr. <fdrake@users.sourceforge.net>
13    Copyright (c) 2004-2006 Karl Waclawek <karl@waclawek.net>
14    Copyright (c) 2005-2007 Steven Solie <steven@solie.ca>
15    Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org>
16    Copyright (c) 2017      Rhodri James <rhodri@wildebeest.org.uk>
17    Copyright (c) 2019      Zhongyuan Zhou <zhouzhongyuan@huawei.com>
18    Copyright (c) 2024      Hanno Böck <hanno@gentoo.org>
19    Copyright (c) 2026      Matthew Fernandez <matthew.fernandez@gmail.com>
20    Licensed under the MIT license:
21 
22    Permission is  hereby granted,  free of charge,  to any  person obtaining
23    a  copy  of  this  software   and  associated  documentation  files  (the
24    "Software"),  to  deal in  the  Software  without restriction,  including
25    without  limitation the  rights  to use,  copy,  modify, merge,  publish,
26    distribute, sublicense, and/or sell copies of the Software, and to permit
27    persons  to whom  the Software  is  furnished to  do so,  subject to  the
28    following conditions:
29 
30    The above copyright  notice and this permission notice  shall be included
31    in all copies or substantial portions of the Software.
32 
33    THE  SOFTWARE  IS  PROVIDED  "AS  IS",  WITHOUT  WARRANTY  OF  ANY  KIND,
34    EXPRESS  OR IMPLIED,  INCLUDING  BUT  NOT LIMITED  TO  THE WARRANTIES  OF
35    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
36    NO EVENT SHALL THE AUTHORS OR  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
37    DAMAGES OR  OTHER LIABILITY, WHETHER  IN AN  ACTION OF CONTRACT,  TORT OR
38    OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
39    USE OR OTHER DEALINGS IN THE SOFTWARE.
40 
41    SPDX-License-Identifier: MIT
42 */
43 
44 #include <inttypes.h> // PRIu64
45 #include <stdbool.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <expat.h>
49 
50 #ifdef XML_UNICODE_WCHAR_T
51 #  define XML_FMT_STR "ls"
52 #else
53 #  define XML_FMT_STR "s"
54 #endif
55 
56 // While traversing the XML_Content tree, we avoid recursion
57 // to not be vulnerable to a denial of service attack.
58 typedef struct StackStruct {
59   const XML_Content *model;
60   unsigned level;
61   struct StackStruct *prev;
62 } Stack;
63 
64 static Stack *
65 stackPushMalloc(Stack *stackTop, const XML_Content *model, unsigned level) {
66   Stack *const newStackTop = malloc(sizeof(Stack));
67   if (! newStackTop) {
68     return NULL;
69   }
70   newStackTop->model = model;
71   newStackTop->level = level;
72   newStackTop->prev = stackTop;
73   return newStackTop;
74 }
75 
76 static Stack *
77 stackPopFree(Stack *stackTop) {
78   Stack *const newStackTop = stackTop->prev;
79   free(stackTop);
80   return newStackTop;
81 }
82 
83 static const char *
84 contentTypeName(enum XML_Content_Type contentType) {
85   switch (contentType) {
86   case XML_CTYPE_EMPTY:
87     return "EMPTY";
88   case XML_CTYPE_ANY:
89     return "ANY";
90   case XML_CTYPE_MIXED:
91     return "MIXED";
92   case XML_CTYPE_NAME:
93     return "NAME";
94   case XML_CTYPE_CHOICE:
95     return "CHOICE";
96   case XML_CTYPE_SEQ:
97     return "SEQ";
98   default:
99     return "???";
100   }
101 }
102 
103 static const char *
104 contentQuantName(enum XML_Content_Quant contentQuant) {
105   switch (contentQuant) {
106   case XML_CQUANT_NONE:
107     return "NONE";
108   case XML_CQUANT_OPT:
109     return "OPT";
110   case XML_CQUANT_REP:
111     return "REP";
112   case XML_CQUANT_PLUS:
113     return "PLUS";
114   default:
115     return "???";
116   }
117 }
118 
119 static void
120 dumpContentModelElement(const XML_Content *model, unsigned level,
121                         const XML_Content *root) {
122   // Indent
123   unsigned u = 0;
124   for (; u < level; u++) {
125     printf("  ");
126   }
127 
128   // Node
129   printf("[%u] type=%s(%u), quant=%s(%u)", (unsigned)(model - root),
130          contentTypeName(model->type), (unsigned int)model->type,
131          contentQuantName(model->quant), (unsigned int)model->quant);
132   if (model->name) {
133     printf(", name=\"%" XML_FMT_STR "\"", model->name);
134   } else {
135     printf(", name=NULL");
136   }
137   printf(", numchildren=%u", model->numchildren);
138   printf("\n");
139 }
140 
141 static bool
142 dumpContentModel(const XML_Char *name, const XML_Content *root) {
143   printf("Element \"%" XML_FMT_STR "\":\n", name);
144   Stack *stackTop = stackPushMalloc(NULL, root, 1);
145   if (! stackTop) {
146     return false;
147   }
148 
149   while (stackTop) {
150     const XML_Content *const model = stackTop->model;
151     const unsigned level = stackTop->level;
152 
153     dumpContentModelElement(model, level, root);
154 
155     stackTop = stackPopFree(stackTop);
156 
157     for (size_t u = model->numchildren; u >= 1; u--) {
158       Stack *const newStackTop
159           = stackPushMalloc(stackTop, model->children + (u - 1), level + 1);
160       if (! newStackTop) {
161         // We ran out of memory, so let's free all memory allocated
162         // earlier in this function, to be leak-clean:
163         while (stackTop != NULL) {
164           stackTop = stackPopFree(stackTop);
165         }
166         return false;
167       }
168       stackTop = newStackTop;
169     }
170   }
171 
172   printf("\n");
173   return true;
174 }
175 
176 static void XMLCALL
177 handleElementDeclaration(void *userData, const XML_Char *name,
178                          XML_Content *model) {
179   XML_Parser parser = (XML_Parser)userData;
180   const bool success = dumpContentModel(name, model);
181   XML_FreeContentModel(parser, model);
182   if (! success) {
183     XML_StopParser(parser, /* resumable= */ XML_FALSE);
184   }
185 }
186 
187 int
188 main(void) {
189   XML_Parser parser = XML_ParserCreate(NULL);
190   int done;
191 
192   if (! parser) {
193     fprintf(stderr, "Couldn't allocate memory for parser\n");
194     return 1;
195   }
196 
197   XML_SetUserData(parser, parser);
198   XML_SetElementDeclHandler(parser, handleElementDeclaration);
199 
200   do {
201     void *const buf = XML_GetBuffer(parser, BUFSIZ);
202     if (! buf) {
203       fprintf(stderr, "Couldn't allocate memory for buffer\n");
204       XML_ParserFree(parser);
205       return 1;
206     }
207 
208     const size_t len = fread(buf, 1, BUFSIZ, stdin);
209 
210     if (ferror(stdin)) {
211       fprintf(stderr, "Read error\n");
212       XML_ParserFree(parser);
213       return 1;
214     }
215 
216     done = feof(stdin);
217 
218     if (XML_ParseBuffer(parser, (int)len, done) == XML_STATUS_ERROR) {
219       enum XML_Error errorCode = XML_GetErrorCode(parser);
220       if (errorCode == XML_ERROR_ABORTED) {
221         errorCode = XML_ERROR_NO_MEMORY;
222       }
223       fprintf(stderr, "Parse error at line %" PRIu64 ":\n%" XML_FMT_STR "\n",
224               XML_GetCurrentLineNumber64(parser), XML_ErrorString(errorCode));
225       XML_ParserFree(parser);
226       return 1;
227     }
228   } while (! done);
229 
230   XML_ParserFree(parser);
231   return 0;
232 }
233