xref: /freebsd/contrib/expat/xmlwf/xmlwf.c (revision dece5b05f577fc4c6b89af7ad48dfbdf7cc4945c)
1 /*
2                             __  __            _
3                          ___\ \/ /_ __   __ _| |_
4                         / _ \\  /| '_ \ / _` | __|
5                        |  __//  \| |_) | (_| | |_
6                         \___/_/\_\ .__/ \__,_|\__|
7                                  |_| XML parser
8 
9    Copyright (c) 1997-2000 Thai Open Source Software Center Ltd
10    Copyright (c) 2000      Clark Cooper <coopercc@users.sourceforge.net>
11    Copyright (c) 2001-2003 Fred L. Drake, Jr. <fdrake@users.sourceforge.net>
12    Copyright (c) 2004-2009 Karl Waclawek <karl@waclawek.net>
13    Copyright (c) 2005-2007 Steven Solie <steven@solie.ca>
14    Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org>
15    Copyright (c) 2017      Rhodri James <rhodri@wildebeest.org.uk>
16    Copyright (c) 2019      David Loffredo <loffredo@steptools.com>
17    Copyright (c) 2020      Joe Orton <jorton@redhat.com>
18    Copyright (c) 2020      Kleber Tarcísio <klebertarcisio@yahoo.com.br>
19    Copyright (c) 2021      Tim Bray <tbray@textuality.com>
20    Copyright (c) 2022      Martin Ettl <ettl.martin78@googlemail.com>
21    Copyright (c) 2022      Sean McBride <sean@rogue-research.com>
22    Copyright (c) 2025      Alfonso Gregory <gfunni234@gmail.com>
23    Copyright (c) 2026      Matthew Fernandez <matthew.fernandez@gmail.com>
24    Copyright (c) 2026      Nick Begg <nick@stunttruck.net>
25    Copyright (c) 2026      Kartik Kenchi <netliomax25@gmail.com>
26    Licensed under the MIT license:
27 
28    Permission is  hereby granted,  free of charge,  to any  person obtaining
29    a  copy  of  this  software   and  associated  documentation  files  (the
30    "Software"),  to  deal in  the  Software  without restriction,  including
31    without  limitation the  rights  to use,  copy,  modify, merge,  publish,
32    distribute, sublicense, and/or sell copies of the Software, and to permit
33    persons  to whom  the Software  is  furnished to  do so,  subject to  the
34    following conditions:
35 
36    The above copyright  notice and this permission notice  shall be included
37    in all copies or substantial portions of the Software.
38 
39    THE  SOFTWARE  IS  PROVIDED  "AS  IS",  WITHOUT  WARRANTY  OF  ANY  KIND,
40    EXPRESS  OR IMPLIED,  INCLUDING  BUT  NOT LIMITED  TO  THE WARRANTIES  OF
41    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
42    NO EVENT SHALL THE AUTHORS OR  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
43    DAMAGES OR  OTHER LIABILITY, WHETHER  IN AN  ACTION OF CONTRACT,  TORT OR
44    OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
45    USE OR OTHER DEALINGS IN THE SOFTWARE.
46 
47    SPDX-License-Identifier: MIT
48 */
49 
50 #include "expat_config.h"
51 
52 #include <assert.h>
53 #include <inttypes.h> // for PRIu64
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <stddef.h>
57 #include <string.h>
58 #include <math.h> /* for isnan */
59 #include <errno.h>
60 
61 #include "expat.h"
62 #include "codepage.h"
63 #include "internal.h" /* for UNUSED_P only */
64 #include "fallthrough.h"
65 #include "xmlfile.h"
66 #include "xmltchar.h"
67 
68 #ifdef _MSC_VER
69 #  include <crtdbg.h>
70 #endif
71 
72 #ifdef XML_UNICODE
73 #  include <wchar.h>
74 #endif
75 
76 #include "../lib/xcs.h"
77 
78 enum ExitCode {
79   XMLWF_EXIT_SUCCESS = 0,
80   XMLWF_EXIT_INTERNAL_ERROR = 1,
81   XMLWF_EXIT_NOT_WELLFORMED = 2,
82   XMLWF_EXIT_OUTPUT_ERROR = 3,
83   XMLWF_EXIT_USAGE_ERROR = 4,
84 };
85 
86 /* Structures for handler user data */
87 typedef struct NotationList {
88   struct NotationList *next;
89   const XML_Char *notationName;
90   const XML_Char *systemId;
91   const XML_Char *publicId;
92 } NotationList;
93 
94 typedef struct xmlwfUserData {
95   FILE *fp;
96   NotationList *notationListHead;
97   const XML_Char *currentDoctypeName;
98 } XmlwfUserData;
99 
100 /* This ensures proper sorting. */
101 
102 #define NSSEP T('\001')
103 
104 static void XMLCALL
105 characterData(void *userData, const XML_Char *s, int len) {
106   FILE *fp = ((XmlwfUserData *)userData)->fp;
107   for (; len > 0; --len, ++s) {
108     switch (*s) {
109     case T('&'):
110       fputts(T("&amp;"), fp);
111       break;
112     case T('<'):
113       fputts(T("&lt;"), fp);
114       break;
115     case T('>'):
116       fputts(T("&gt;"), fp);
117       break;
118 #ifdef W3C14N
119     case 13:
120       fputts(T("&#xD;"), fp);
121       break;
122 #else
123     case T('"'):
124       fputts(T("&quot;"), fp);
125       break;
126     case 9:
127     case 10:
128     case 13:
129       ftprintf(fp, T("&#%d;"), *s);
130       break;
131 #endif
132     default:
133       puttc(*s, fp);
134       break;
135     }
136   }
137 }
138 
139 static void
140 attributeValue(FILE *fp, const XML_Char *s) {
141   puttc(T('='), fp);
142   puttc(T('"'), fp);
143   assert(s);
144   for (;;) {
145     switch (*s) {
146     case 0:
147     case NSSEP:
148       puttc(T('"'), fp);
149       return;
150     case T('&'):
151       fputts(T("&amp;"), fp);
152       break;
153     case T('<'):
154       fputts(T("&lt;"), fp);
155       break;
156     case T('"'):
157       fputts(T("&quot;"), fp);
158       break;
159 #ifdef W3C14N
160     case 9:
161       fputts(T("&#x9;"), fp);
162       break;
163     case 10:
164       fputts(T("&#xA;"), fp);
165       break;
166     case 13:
167       fputts(T("&#xD;"), fp);
168       break;
169 #else
170     case T('>'):
171       fputts(T("&gt;"), fp);
172       break;
173     case 9:
174     case 10:
175     case 13:
176       ftprintf(fp, T("&#%d;"), *s);
177       break;
178 #endif
179     default:
180       puttc(*s, fp);
181       break;
182     }
183     s++;
184   }
185 }
186 
187 /* Lexicographically comparing UTF-8 encoded attribute values,
188 is equivalent to lexicographically comparing based on the character number. */
189 
190 static int
191 attcmp(const void *att1, const void *att2) {
192   return tcscmp(*(const XML_Char *const *)att1, *(const XML_Char *const *)att2);
193 }
194 
195 static void XMLCALL
196 startElement(void *userData, const XML_Char *name, const XML_Char **atts) {
197   int nAtts;
198   const XML_Char **p;
199   FILE *fp = ((XmlwfUserData *)userData)->fp;
200   puttc(T('<'), fp);
201   fputts(name, fp);
202 
203   p = atts;
204   while (*p)
205     ++p;
206   nAtts = (int)((p - atts) >> 1);
207   if (nAtts > 1)
208     qsort(atts, nAtts, sizeof(XML_Char *) * 2, attcmp);
209   while (*atts) {
210     puttc(T(' '), fp);
211     fputts(*atts++, fp);
212     attributeValue(fp, *atts);
213     atts++;
214   }
215   puttc(T('>'), fp);
216 }
217 
218 static void XMLCALL
219 endElement(void *userData, const XML_Char *name) {
220   FILE *fp = ((XmlwfUserData *)userData)->fp;
221   puttc(T('<'), fp);
222   puttc(T('/'), fp);
223   fputts(name, fp);
224   puttc(T('>'), fp);
225 }
226 
227 static int
228 nsattcmp(const void *p1, const void *p2) {
229   const XML_Char *att1 = *(const XML_Char *const *)p1;
230   const XML_Char *att2 = *(const XML_Char *const *)p2;
231   int sep1 = (tcsrchr(att1, NSSEP) != 0);
232   int sep2 = (tcsrchr(att2, NSSEP) != 0);
233   if (sep1 != sep2)
234     return sep1 - sep2;
235   return tcscmp(att1, att2);
236 }
237 
238 static void XMLCALL
239 startElementNS(void *userData, const XML_Char *name, const XML_Char **atts) {
240   int nAtts;
241   int nsi;
242   const XML_Char **p;
243   FILE *fp = ((XmlwfUserData *)userData)->fp;
244   const XML_Char *sep;
245   puttc(T('<'), fp);
246 
247   sep = tcsrchr(name, NSSEP);
248   if (sep) {
249     fputts(T("n1:"), fp);
250     fputts(sep + 1, fp);
251     fputts(T(" xmlns:n1"), fp);
252     attributeValue(fp, name);
253     nsi = 2;
254   } else {
255     fputts(name, fp);
256     nsi = 1;
257   }
258 
259   p = atts;
260   while (*p)
261     ++p;
262   nAtts = (int)((p - atts) >> 1);
263   if (nAtts > 1)
264     qsort(atts, nAtts, sizeof(XML_Char *) * 2, nsattcmp);
265   while (*atts) {
266     name = *atts++;
267     sep = tcsrchr(name, NSSEP);
268     puttc(T(' '), fp);
269     if (sep) {
270       ftprintf(fp, T("n%d:"), nsi);
271       fputts(sep + 1, fp);
272     } else
273       fputts(name, fp);
274     attributeValue(fp, *atts);
275     if (sep) {
276       ftprintf(fp, T(" xmlns:n%d"), nsi++);
277       attributeValue(fp, name);
278     }
279     atts++;
280   }
281   puttc(T('>'), fp);
282 }
283 
284 static void XMLCALL
285 endElementNS(void *userData, const XML_Char *name) {
286   FILE *fp = ((XmlwfUserData *)userData)->fp;
287   const XML_Char *sep;
288   puttc(T('<'), fp);
289   puttc(T('/'), fp);
290   sep = tcsrchr(name, NSSEP);
291   if (sep) {
292     fputts(T("n1:"), fp);
293     fputts(sep + 1, fp);
294   } else
295     fputts(name, fp);
296   puttc(T('>'), fp);
297 }
298 
299 #ifndef W3C14N
300 
301 static void XMLCALL
302 processingInstruction(void *userData, const XML_Char *target,
303                       const XML_Char *data) {
304   FILE *fp = ((XmlwfUserData *)userData)->fp;
305   puttc(T('<'), fp);
306   puttc(T('?'), fp);
307   fputts(target, fp);
308   puttc(T(' '), fp);
309   fputts(data, fp);
310   puttc(T('?'), fp);
311   puttc(T('>'), fp);
312 }
313 
314 static XML_Char *
315 xcsdup(const XML_Char *s) {
316   const size_t count = xcslen(s) + /* null terminator */ 1;
317 
318   // Detect and prevent integer overflow
319   if (count > SIZE_MAX / sizeof(XML_Char))
320     return NULL;
321 
322   const size_t numBytes = count * sizeof(XML_Char);
323   XML_Char *const result = malloc(numBytes);
324   if (result == NULL)
325     return NULL;
326   memcpy(result, s, numBytes);
327   return result;
328 }
329 
330 static void XMLCALL
331 startDoctypeDecl(void *userData, const XML_Char *doctypeName,
332                  const XML_Char *sysid, const XML_Char *publid,
333                  int has_internal_subset) {
334   XmlwfUserData *data = userData;
335   UNUSED_P(sysid);
336   UNUSED_P(publid);
337   UNUSED_P(has_internal_subset);
338   data->currentDoctypeName = xcsdup(doctypeName);
339 }
340 
341 static void
342 freeNotations(XmlwfUserData *data) {
343   NotationList *notationListHead = data->notationListHead;
344 
345   while (notationListHead != NULL) {
346     NotationList *next = notationListHead->next;
347     free((void *)notationListHead->notationName);
348     free((void *)notationListHead->systemId);
349     free((void *)notationListHead->publicId);
350     free(notationListHead);
351     notationListHead = next;
352   }
353   data->notationListHead = NULL;
354 }
355 
356 static void
357 cleanupUserData(XmlwfUserData *userData) {
358   free((void *)userData->currentDoctypeName);
359   userData->currentDoctypeName = NULL;
360   freeNotations(userData);
361 }
362 
363 static int
364 notationCmp(const void *a, const void *b) {
365   const NotationList *const n1 = *(const NotationList *const *)a;
366   const NotationList *const n2 = *(const NotationList *const *)b;
367 
368   return xcscmp(n1->notationName, n2->notationName);
369 }
370 
371 /* Write a SystemLiteral/PubidLiteral, choosing a delimiter that does not
372    occur in the value.  The grammar forbids a literal from containing its
373    own delimiter, so a value reported by Expat never holds both quote
374    characters and a safe delimiter always exists. */
375 static void
376 writeLiteral(FILE *fp, const XML_Char *value) {
377   const XML_Char quote = (tcschr(value, T('\'')) != NULL) ? T('"') : T('\'');
378   puttc(quote, fp);
379   fputts(value, fp);
380   puttc(quote, fp);
381 }
382 
383 static void XMLCALL
384 endDoctypeDecl(void *userData) {
385   XmlwfUserData *data = userData;
386   NotationList **notations;
387   size_t notationCount = 0;
388   NotationList *p;
389   size_t i;
390 
391   /* How many notations do we have? */
392   for (p = data->notationListHead; p != NULL; p = p->next)
393     notationCount++;
394   if (notationCount == 0) {
395     /* Nothing to report */
396     goto cleanUp;
397   }
398 
399   /* Detect and prevent integer overflow in the multiplication, mirroring
400      the guards in xcsdup() and resolveSystemId() */
401   if (notationCount > SIZE_MAX / sizeof(NotationList *)) {
402     fprintf(stderr, "Unable to sort notations");
403     goto cleanUp;
404   }
405 
406   notations = malloc(notationCount * sizeof(NotationList *));
407   if (notations == NULL) {
408     fprintf(stderr, "Unable to sort notations");
409     goto cleanUp;
410   }
411 
412   for (p = data->notationListHead, i = 0; i < notationCount; p = p->next, i++) {
413     notations[i] = p;
414   }
415   qsort(notations, notationCount, sizeof(NotationList *), notationCmp);
416 
417   /* Output the DOCTYPE header */
418   fputts(T("<!DOCTYPE "), data->fp);
419   fputts(data->currentDoctypeName, data->fp);
420   fputts(T(" [\n"), data->fp);
421 
422   /* Now the NOTATIONs */
423   for (i = 0; i < notationCount; i++) {
424     fputts(T("<!NOTATION "), data->fp);
425     fputts(notations[i]->notationName, data->fp);
426     if (notations[i]->publicId != NULL) {
427       fputts(T(" PUBLIC "), data->fp);
428       writeLiteral(data->fp, notations[i]->publicId);
429       if (notations[i]->systemId != NULL) {
430         puttc(T(' '), data->fp);
431         writeLiteral(data->fp, notations[i]->systemId);
432       }
433     } else if (notations[i]->systemId != NULL) {
434       fputts(T(" SYSTEM "), data->fp);
435       writeLiteral(data->fp, notations[i]->systemId);
436     }
437     puttc(T('>'), data->fp);
438     puttc(T('\n'), data->fp);
439   }
440 
441   /* Finally end the DOCTYPE */
442   fputts(T("]>\n"), data->fp);
443 
444   free(notations);
445 
446 cleanUp:
447   freeNotations(data);
448   free((void *)data->currentDoctypeName);
449   data->currentDoctypeName = NULL;
450 }
451 
452 static void XMLCALL
453 notationDecl(void *userData, const XML_Char *notationName, const XML_Char *base,
454              const XML_Char *systemId, const XML_Char *publicId) {
455   XmlwfUserData *data = userData;
456   NotationList *entry = malloc(sizeof(NotationList));
457   const char *errorMessage = "Unable to store NOTATION for output\n";
458 
459   UNUSED_P(base);
460   if (entry == NULL) {
461     fputs(errorMessage, stderr);
462     return; /* Nothing we can really do about this */
463   }
464   entry->notationName = xcsdup(notationName);
465   if (entry->notationName == NULL) {
466     fputs(errorMessage, stderr);
467     free(entry);
468     return;
469   }
470   if (systemId != NULL) {
471     entry->systemId = xcsdup(systemId);
472     if (entry->systemId == NULL) {
473       fputs(errorMessage, stderr);
474       free((void *)entry->notationName);
475       free(entry);
476       return;
477     }
478   } else {
479     entry->systemId = NULL;
480   }
481   if (publicId != NULL) {
482     entry->publicId = xcsdup(publicId);
483     if (entry->publicId == NULL) {
484       fputs(errorMessage, stderr);
485       free((void *)entry->systemId); /* Safe if it's NULL */
486       free((void *)entry->notationName);
487       free(entry);
488       return;
489     }
490   } else {
491     entry->publicId = NULL;
492   }
493 
494   entry->next = data->notationListHead;
495   data->notationListHead = entry;
496 }
497 
498 #endif /* not W3C14N */
499 
500 static void XMLCALL
501 defaultCharacterData(void *userData, const XML_Char *s, int len) {
502   UNUSED_P(s);
503   UNUSED_P(len);
504   XML_DefaultCurrent(userData);
505 }
506 
507 static void XMLCALL
508 defaultStartElement(void *userData, const XML_Char *name,
509                     const XML_Char **atts) {
510   UNUSED_P(name);
511   UNUSED_P(atts);
512   XML_DefaultCurrent(userData);
513 }
514 
515 static void XMLCALL
516 defaultEndElement(void *userData, const XML_Char *name) {
517   UNUSED_P(name);
518   XML_DefaultCurrent(userData);
519 }
520 
521 static void XMLCALL
522 defaultProcessingInstruction(void *userData, const XML_Char *target,
523                              const XML_Char *data) {
524   UNUSED_P(target);
525   UNUSED_P(data);
526   XML_DefaultCurrent(userData);
527 }
528 
529 static void XMLCALL
530 nopCharacterData(void *userData, const XML_Char *s, int len) {
531   UNUSED_P(userData);
532   UNUSED_P(s);
533   UNUSED_P(len);
534 }
535 
536 static void XMLCALL
537 nopStartElement(void *userData, const XML_Char *name, const XML_Char **atts) {
538   UNUSED_P(userData);
539   UNUSED_P(name);
540   UNUSED_P(atts);
541 }
542 
543 static void XMLCALL
544 nopEndElement(void *userData, const XML_Char *name) {
545   UNUSED_P(userData);
546   UNUSED_P(name);
547 }
548 
549 static void XMLCALL
550 nopProcessingInstruction(void *userData, const XML_Char *target,
551                          const XML_Char *data) {
552   UNUSED_P(userData);
553   UNUSED_P(target);
554   UNUSED_P(data);
555 }
556 
557 static void XMLCALL
558 markup(void *userData, const XML_Char *s, int len) {
559   FILE *fp = ((XmlwfUserData *)XML_GetUserData(userData))->fp;
560   for (; len > 0; --len, ++s)
561     puttc(*s, fp);
562 }
563 
564 static void
565 metaLocation(XML_Parser parser) {
566   const XML_Char *uri = XML_GetBase(parser);
567   FILE *fp = ((XmlwfUserData *)XML_GetUserData(parser))->fp;
568   if (uri) {
569     fputts(T(" uri=\""), fp);
570     characterData(XML_GetUserData(parser), uri, (int)tcslen(uri));
571     puttc(T('"'), fp);
572   }
573   ftprintf(fp,
574            T(" byte=\"%") T(PRId64) T("\"") T(" nbytes=\"%") T(PRIu64) T("\"")
575                T(" line=\"%") T(PRIu64) T("\"") T(" col=\"%") T(PRIu64) T("\""),
576            XML_GetCurrentByteIndex64(parser), XML_GetCurrentByteCount64(parser),
577            XML_GetCurrentLineNumber64(parser),
578            XML_GetCurrentColumnNumber64(parser));
579 }
580 
581 static void
582 metaStartDocument(void *userData) {
583   fputts(T("<document>\n"), ((XmlwfUserData *)XML_GetUserData(userData))->fp);
584 }
585 
586 static void
587 metaEndDocument(void *userData) {
588   fputts(T("</document>\n"), ((XmlwfUserData *)XML_GetUserData(userData))->fp);
589 }
590 
591 static void XMLCALL
592 metaStartElement(void *userData, const XML_Char *name, const XML_Char **atts) {
593   XML_Parser parser = userData;
594   XmlwfUserData *data = XML_GetUserData(parser);
595   FILE *fp = data->fp;
596   const XML_Char **specifiedAttsEnd
597       = atts + XML_GetSpecifiedAttributeCount(parser);
598   const XML_Char **idAttPtr;
599   int idAttIndex = XML_GetIdAttributeIndex(parser);
600   if (idAttIndex < 0)
601     idAttPtr = 0;
602   else
603     idAttPtr = atts + idAttIndex;
604 
605   fputts(T("<starttag name=\""), fp);
606   characterData(data, name, (int)tcslen(name));
607   puttc(T('"'), fp);
608   metaLocation(parser);
609   if (*atts) {
610     fputts(T(">\n"), fp);
611     do {
612       fputts(T("<attribute name=\""), fp);
613       characterData(data, atts[0], (int)tcslen(atts[0]));
614       fputts(T("\" value=\""), fp);
615       characterData(data, atts[1], (int)tcslen(atts[1]));
616       if (atts >= specifiedAttsEnd)
617         fputts(T("\" defaulted=\"yes\"/>\n"), fp);
618       else if (atts == idAttPtr)
619         fputts(T("\" id=\"yes\"/>\n"), fp);
620       else
621         fputts(T("\"/>\n"), fp);
622     } while (*(atts += 2));
623     fputts(T("</starttag>\n"), fp);
624   } else
625     fputts(T("/>\n"), fp);
626 }
627 
628 static void XMLCALL
629 metaEndElement(void *userData, const XML_Char *name) {
630   XML_Parser parser = userData;
631   XmlwfUserData *data = XML_GetUserData(parser);
632   FILE *fp = data->fp;
633   fputts(T("<endtag name=\""), fp);
634   characterData(data, name, (int)tcslen(name));
635   puttc(T('"'), fp);
636   metaLocation(parser);
637   fputts(T("/>\n"), fp);
638 }
639 
640 static void XMLCALL
641 metaProcessingInstruction(void *userData, const XML_Char *target,
642                           const XML_Char *data) {
643   XML_Parser parser = userData;
644   XmlwfUserData *usrData = XML_GetUserData(parser);
645   FILE *fp = usrData->fp;
646   ftprintf(fp, T("<pi target=\"%s\" data=\""), target);
647   characterData(usrData, data, (int)tcslen(data));
648   puttc(T('"'), fp);
649   metaLocation(parser);
650   fputts(T("/>\n"), fp);
651 }
652 
653 static void XMLCALL
654 metaComment(void *userData, const XML_Char *data) {
655   XML_Parser parser = userData;
656   XmlwfUserData *usrData = XML_GetUserData(parser);
657   FILE *fp = usrData->fp;
658   fputts(T("<comment data=\""), fp);
659   characterData(usrData, data, (int)tcslen(data));
660   puttc(T('"'), fp);
661   metaLocation(parser);
662   fputts(T("/>\n"), fp);
663 }
664 
665 static void XMLCALL
666 metaStartCdataSection(void *userData) {
667   XML_Parser parser = userData;
668   XmlwfUserData *data = XML_GetUserData(parser);
669   FILE *fp = data->fp;
670   fputts(T("<startcdata"), fp);
671   metaLocation(parser);
672   fputts(T("/>\n"), fp);
673 }
674 
675 static void XMLCALL
676 metaEndCdataSection(void *userData) {
677   XML_Parser parser = userData;
678   XmlwfUserData *data = XML_GetUserData(parser);
679   FILE *fp = data->fp;
680   fputts(T("<endcdata"), fp);
681   metaLocation(parser);
682   fputts(T("/>\n"), fp);
683 }
684 
685 static void XMLCALL
686 metaCharacterData(void *userData, const XML_Char *s, int len) {
687   XML_Parser parser = userData;
688   XmlwfUserData *data = XML_GetUserData(parser);
689   FILE *fp = data->fp;
690   fputts(T("<chars str=\""), fp);
691   characterData(data, s, len);
692   puttc(T('"'), fp);
693   metaLocation(parser);
694   fputts(T("/>\n"), fp);
695 }
696 
697 static void XMLCALL
698 metaStartDoctypeDecl(void *userData, const XML_Char *doctypeName,
699                      const XML_Char *sysid, const XML_Char *pubid,
700                      int has_internal_subset) {
701   XML_Parser parser = userData;
702   XmlwfUserData *data = XML_GetUserData(parser);
703   FILE *fp = data->fp;
704   UNUSED_P(sysid);
705   UNUSED_P(pubid);
706   UNUSED_P(has_internal_subset);
707   ftprintf(fp, T("<startdoctype name=\"%s\""), doctypeName);
708   metaLocation(parser);
709   fputts(T("/>\n"), fp);
710 }
711 
712 static void XMLCALL
713 metaEndDoctypeDecl(void *userData) {
714   XML_Parser parser = userData;
715   XmlwfUserData *data = XML_GetUserData(parser);
716   FILE *fp = data->fp;
717   fputts(T("<enddoctype"), fp);
718   metaLocation(parser);
719   fputts(T("/>\n"), fp);
720 }
721 
722 static void XMLCALL
723 metaNotationDecl(void *userData, const XML_Char *notationName,
724                  const XML_Char *base, const XML_Char *systemId,
725                  const XML_Char *publicId) {
726   XML_Parser parser = userData;
727   XmlwfUserData *data = XML_GetUserData(parser);
728   FILE *fp = data->fp;
729   UNUSED_P(base);
730   ftprintf(fp, T("<notation name=\"%s\""), notationName);
731   if (publicId)
732     ftprintf(fp, T(" public=\"%s\""), publicId);
733   if (systemId) {
734     fputts(T(" system=\""), fp);
735     characterData(data, systemId, (int)tcslen(systemId));
736     puttc(T('"'), fp);
737   }
738   metaLocation(parser);
739   fputts(T("/>\n"), fp);
740 }
741 
742 static void XMLCALL
743 metaEntityDecl(void *userData, const XML_Char *entityName, int is_param,
744                const XML_Char *value, int value_length, const XML_Char *base,
745                const XML_Char *systemId, const XML_Char *publicId,
746                const XML_Char *notationName) {
747   XML_Parser parser = userData;
748   XmlwfUserData *data = XML_GetUserData(parser);
749   FILE *fp = data->fp;
750 
751   UNUSED_P(is_param);
752   UNUSED_P(base);
753   if (value) {
754     ftprintf(fp, T("<entity name=\"%s\""), entityName);
755     metaLocation(parser);
756     puttc(T('>'), fp);
757     characterData(data, value, value_length);
758     fputts(T("</entity/>\n"), fp);
759   } else if (notationName) {
760     ftprintf(fp, T("<entity name=\"%s\""), entityName);
761     if (publicId)
762       ftprintf(fp, T(" public=\"%s\""), publicId);
763     fputts(T(" system=\""), fp);
764     characterData(data, systemId, (int)tcslen(systemId));
765     puttc(T('"'), fp);
766     ftprintf(fp, T(" notation=\"%s\""), notationName);
767     metaLocation(parser);
768     fputts(T("/>\n"), fp);
769   } else {
770     ftprintf(fp, T("<entity name=\"%s\""), entityName);
771     if (publicId)
772       ftprintf(fp, T(" public=\"%s\""), publicId);
773     fputts(T(" system=\""), fp);
774     characterData(data, systemId, (int)tcslen(systemId));
775     puttc(T('"'), fp);
776     metaLocation(parser);
777     fputts(T("/>\n"), fp);
778   }
779 }
780 
781 static void XMLCALL
782 metaStartNamespaceDecl(void *userData, const XML_Char *prefix,
783                        const XML_Char *uri) {
784   XML_Parser parser = userData;
785   XmlwfUserData *data = XML_GetUserData(parser);
786   FILE *fp = data->fp;
787   fputts(T("<startns"), fp);
788   if (prefix)
789     ftprintf(fp, T(" prefix=\"%s\""), prefix);
790   if (uri) {
791     fputts(T(" ns=\""), fp);
792     characterData(data, uri, (int)tcslen(uri));
793     fputts(T("\"/>\n"), fp);
794   } else
795     fputts(T("/>\n"), fp);
796 }
797 
798 static void XMLCALL
799 metaEndNamespaceDecl(void *userData, const XML_Char *prefix) {
800   XML_Parser parser = userData;
801   XmlwfUserData *data = XML_GetUserData(parser);
802   FILE *fp = data->fp;
803   if (! prefix)
804     fputts(T("<endns/>\n"), fp);
805   else
806     ftprintf(fp, T("<endns prefix=\"%s\"/>\n"), prefix);
807 }
808 
809 static int XMLCALL
810 unknownEncodingConvert(void *data, const char *p) {
811   return codepageConvert(*(int *)data, p);
812 }
813 
814 static int XMLCALL
815 unknownEncoding(void *userData, const XML_Char *name, XML_Encoding *info) {
816   int cp;
817   static const XML_Char prefixL[] = T("windows-");
818   static const XML_Char prefixU[] = T("WINDOWS-");
819   int i;
820 
821   UNUSED_P(userData);
822   for (i = 0; prefixU[i]; i++)
823     if (name[i] != prefixU[i] && name[i] != prefixL[i])
824       return 0;
825 
826   cp = 0;
827   for (; name[i]; i++) {
828     static const XML_Char digits[] = T("0123456789");
829     const XML_Char *s = tcschr(digits, name[i]);
830     if (! s)
831       return 0;
832     cp *= 10;
833     cp += (int)(s - digits);
834     if (cp >= 0x10000)
835       return 0;
836   }
837   if (! codepageMap(cp, info->map))
838     return 0;
839   info->convert = unknownEncodingConvert;
840   /* We could just cast the code page integer to a void *,
841   and avoid the use of release. */
842   info->release = free;
843   info->data = malloc(sizeof(int));
844   if (! info->data)
845     return 0;
846   *(int *)info->data = cp;
847   return 1;
848 }
849 
850 static int XMLCALL
851 notStandalone(void *userData) {
852   UNUSED_P(userData);
853   return 0;
854 }
855 
856 static void
857 showVersion(XML_Char *prog) {
858   XML_Char *s = prog;
859   XML_Char ch;
860   const XML_Feature *features = XML_GetFeatureList();
861   while ((ch = *s) != 0) {
862     if (ch == '/'
863 #if defined(_WIN32)
864         || ch == '\\'
865 #endif
866     )
867       prog = s + 1;
868     ++s;
869   }
870   ftprintf(stdout, T("%s using %s\n"), prog, XML_ExpatVersion());
871   if (features != NULL && features[0].feature != XML_FEATURE_END) {
872     int i = 1;
873     ftprintf(stdout, T("%s"), features[0].name);
874     if (features[0].value)
875       ftprintf(stdout, T("=%ld"), features[0].value);
876     while (features[i].feature != XML_FEATURE_END) {
877       ftprintf(stdout, T(", %s"), features[i].name);
878       if (features[i].value)
879         ftprintf(stdout, T("=%ld"), features[i].value);
880       ++i;
881     }
882     ftprintf(stdout, T("\n"));
883   }
884 }
885 
886 #if defined(__GNUC__)
887 __attribute__((noreturn))
888 #endif
889 static void
890 usage(const XML_Char *prog, int rc) {
891   ftprintf(
892       stderr,
893       /* Generated with:
894        * $ xmlwf/xmlwf_helpgen.sh
895        * To update, change xmlwf/xmlwf_helpgen.py, then paste the output of
896        * xmlwf/xmlwf_helpgen.sh in here.
897        */
898       /* clang-format off */
899       T("usage:\n")
900       T("  %s [OPTIONS] [FILE ...]\n")
901       T("  %s -h|--help\n")
902       T("  %s -v|--version\n")
903       T("\n")
904       T("xmlwf - Determines if an XML document is well-formed\n")
905       T("\n")
906       T("positional arguments:\n")
907       T("  FILE           file to process (default: STDIN)\n")
908       T("\n")
909       T("input control arguments:\n")
910       T("  -s             print an error if the document is not [s]tandalone\n")
911       T("  -n             enable [n]amespace processing\n")
912       T("  -p             enable processing of external DTDs and [p]arameter entities\n")
913       T("  -x             enable processing of e[x]ternal entities\n")
914       T("                 (CAREFUL! This makes xmlwf vulnerable to external entity attacks (XXE).)\n")
915       T("  -e ENCODING    override any in-document [e]ncoding declaration\n")
916       T("  -w             enable support for [W]indows code pages\n")
917       T("  -r             disable memory-mapping and use [r]ead calls instead\n")
918       T("  -g BYTES       buffer size to request per call pair to XML_[G]etBuffer and read (default: 8 KiB)\n")
919       T("  -k             when processing multiple files, [k]eep processing after first file with error, and make the last error determine the exit code.\n")
920       T("\n")
921       T("output control arguments:\n")
922       T("  -d DIRECTORY   output [d]estination directory\n")
923       T("  -c             write a [c]opy of input XML, not canonical XML\n")
924       T("  -m             write [m]eta XML, not canonical XML\n")
925       T("  -t             write no XML output for [t]iming of plain parsing\n")
926       T("  -N             enable adding doctype and [n]otation declarations\n")
927       T("\n")
928       T("amplification attack protection (e.g. billion laughs):\n")
929       T("  NOTE: If you ever need to increase these values for non-attack payload, please file a bug report.\n")
930       T("\n")
931       T("  -a FACTOR      set maximum tolerated [a]mplification factor (default: 100.0)\n")
932       T("  -b BYTES       set number of output [b]ytes needed to activate (default: 8 MiB/64 MiB)\n")
933       T("\n")
934       T("reparse deferral:\n")
935       T("  -q             disable reparse deferral, and allow [q]uadratic parse runtime with large tokens\n")
936       T("\n")
937       T("info arguments:\n")
938       T("  -h, --help     show this [h]elp message and exit\n")
939       T("  -v, --version  show program's [v]ersion number and exit\n")
940       T("\n")
941       T("environment variables:\n")
942       T("  EXPAT_ACCOUNTING_DEBUG=(0|1|2|3)\n")
943       T("                 Control verbosity of accounting debugging (default: 0)\n")
944       T("  EXPAT_ENTITY_DEBUG=(0|1|2)\n")
945       T("                 Control verbosity of entity debugging (default: 0)\n")
946       T("  EXPAT_ENTROPY_DEBUG=(0|1)\n")
947       T("                 Control verbosity of entropy debugging (default: 0)\n")
948       T("  EXPAT_MALLOC_DEBUG=(0|1|2)\n")
949       T("                 Control verbosity of allocation tracker (default: 0)\n")
950       T("\n")
951       T("exit status:\n")
952       T("  0              the input files are well-formed and the output (if requested) was written successfully\n")
953       T("  1              could not allocate data structures, signals a serious problem with execution environment\n")
954       T("  2              one or more input files were not well-formed\n")
955       T("  3              one or more output files could not be written\n")
956       T("  4              command-line argument error\n")
957       T("\n")
958       T("xmlwf of libexpat is software libre, licensed under the MIT license.\n")
959       T("Please report bugs at https://github.com/libexpat/libexpat/issues -- thank you!\n")
960       , /* clang-format on */
961       prog, prog, prog);
962   exit(rc);
963 }
964 
965 #if defined(__MINGW32__) && defined(XML_UNICODE)
966 /* Silence warning about missing prototype */
967 int wmain(int argc, XML_Char **argv);
968 #endif
969 
970 #define XMLWF_SHIFT_ARG_INTO(constCharStarTarget, argc, argv, i, j)            \
971   {                                                                            \
972     if (argv[i][j + 1] == T('\0')) {                                           \
973       if (++i == argc) {                                                       \
974         usage(argv[0], XMLWF_EXIT_USAGE_ERROR);                                \
975         /* usage called exit(..), never gets here */                           \
976       }                                                                        \
977       constCharStarTarget = argv[i];                                           \
978     } else {                                                                   \
979       constCharStarTarget = argv[i] + j + 1;                                   \
980     }                                                                          \
981     i++;                                                                       \
982     j = 0;                                                                     \
983   }
984 
985 int
986 tmain(int argc, XML_Char **argv) {
987   int i, j;
988   const XML_Char *outputDir = NULL;
989   const XML_Char *encoding = NULL;
990   unsigned processFlags = XML_MAP_FILE;
991   int windowsCodePages = 0;
992   int outputType = 0;
993   int useNamespaces = 0;
994   int requireStandalone = 0;
995   int requiresNotations = 0;
996   int continueOnError = 0;
997 
998   float attackMaximumAmplification = -1.0f; /* signaling "not set" */
999   unsigned long long attackThresholdBytes = 0;
1000   XML_Bool attackThresholdGiven = XML_FALSE;
1001 
1002   XML_Bool disableDeferral = XML_FALSE;
1003 
1004   int exitCode = XMLWF_EXIT_SUCCESS;
1005   enum XML_ParamEntityParsing paramEntityParsing
1006       = XML_PARAM_ENTITY_PARSING_NEVER;
1007   int useStdin = 0;
1008   XmlwfUserData userData = {NULL, NULL, NULL};
1009 
1010 #ifdef _MSC_VER
1011   _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
1012 #endif
1013 
1014   i = 1;
1015   j = 0;
1016   while (i < argc) {
1017     if (j == 0) {
1018       if (argv[i][0] != T('-'))
1019         break;
1020       if (argv[i][1] == T('-')) {
1021         if (argv[i][2] == T('\0')) {
1022           i++;
1023           break;
1024         } else if (tcscmp(argv[i] + 2, T("help")) == 0) {
1025           usage(argv[0], XMLWF_EXIT_SUCCESS);
1026           // usage called exit(..), never gets here
1027         } else if (tcscmp(argv[i] + 2, T("version")) == 0) {
1028           showVersion(argv[0]);
1029           return XMLWF_EXIT_SUCCESS;
1030         }
1031       }
1032       j++;
1033     }
1034     switch (argv[i][j]) {
1035     case T('r'):
1036       processFlags &= ~XML_MAP_FILE;
1037       j++;
1038       break;
1039     case T('s'):
1040       requireStandalone = 1;
1041       j++;
1042       break;
1043     case T('n'):
1044       useNamespaces = 1;
1045       j++;
1046       break;
1047     case T('p'):
1048       paramEntityParsing = XML_PARAM_ENTITY_PARSING_ALWAYS;
1049       EXPAT_FALLTHROUGH;
1050     case T('x'):
1051       processFlags |= XML_EXTERNAL_ENTITIES;
1052       j++;
1053       break;
1054     case T('w'):
1055       windowsCodePages = 1;
1056       j++;
1057       break;
1058     case T('m'):
1059       outputType = 'm';
1060       j++;
1061       break;
1062     case T('c'):
1063       outputType = 'c';
1064       useNamespaces = 0;
1065       j++;
1066       break;
1067     case T('t'):
1068       outputType = 't';
1069       j++;
1070       break;
1071     case T('N'):
1072       requiresNotations = 1;
1073       j++;
1074       break;
1075     case T('d'):
1076       XMLWF_SHIFT_ARG_INTO(outputDir, argc, argv, i, j);
1077       break;
1078     case T('e'):
1079       XMLWF_SHIFT_ARG_INTO(encoding, argc, argv, i, j);
1080       break;
1081     case T('h'):
1082       usage(argv[0], XMLWF_EXIT_SUCCESS);
1083       // usage called exit(..), never gets here
1084     case T('v'):
1085       showVersion(argv[0]);
1086       return XMLWF_EXIT_SUCCESS;
1087     case T('g'): {
1088       const XML_Char *valueText = NULL;
1089       XMLWF_SHIFT_ARG_INTO(valueText, argc, argv, i, j);
1090 
1091       errno = 0;
1092       XML_Char *afterValueText = (XML_Char *)valueText;
1093       const long long read_size_bytes_candidate
1094           = tcstoull(valueText, &afterValueText, 10);
1095       if ((errno != 0) || (afterValueText[0] != T('\0'))
1096           || (read_size_bytes_candidate < 1)
1097           || (read_size_bytes_candidate > (INT_MAX / 2 + 1))) {
1098         // This prevents tperror(..) from reporting misleading "[..]: Success"
1099         errno = ERANGE;
1100         tperror(T("invalid buffer size") T(
1101             " (needs an integer from 1 to INT_MAX/2+1 i.e. 1,073,741,824 on most platforms)"));
1102         exit(XMLWF_EXIT_USAGE_ERROR);
1103       }
1104       g_read_size_bytes = (int)read_size_bytes_candidate;
1105       break;
1106     }
1107     case T('k'):
1108       continueOnError = 1;
1109       j++;
1110       break;
1111     case T('a'): {
1112       const XML_Char *valueText = NULL;
1113       XMLWF_SHIFT_ARG_INTO(valueText, argc, argv, i, j);
1114 
1115       errno = 0;
1116       XML_Char *afterValueText = NULL;
1117       attackMaximumAmplification = tcstof(valueText, &afterValueText);
1118       if ((errno != 0) || (afterValueText[0] != T('\0'))
1119           || isnan(attackMaximumAmplification)
1120           || (attackMaximumAmplification < 1.0f)) {
1121         // This prevents tperror(..) from reporting misleading "[..]: Success"
1122         errno = ERANGE;
1123         tperror(T("invalid amplification limit") T(
1124             " (needs a floating point number greater or equal than 1.0)"));
1125         exit(XMLWF_EXIT_USAGE_ERROR);
1126       }
1127 #if XML_GE == 0
1128       ftprintf(stderr,
1129                T("Warning: Given amplification limit ignored")
1130                    T(", xmlwf has been compiled without DTD/GE support.\n"));
1131 #endif
1132       break;
1133     }
1134     case T('b'): {
1135       const XML_Char *valueText = NULL;
1136       XMLWF_SHIFT_ARG_INTO(valueText, argc, argv, i, j);
1137 
1138       errno = 0;
1139       XML_Char *afterValueText = (XML_Char *)valueText;
1140       attackThresholdBytes = tcstoull(valueText, &afterValueText, 10);
1141       if ((errno != 0) || (afterValueText[0] != T('\0'))) {
1142         // This prevents tperror(..) from reporting misleading "[..]: Success"
1143         errno = ERANGE;
1144         tperror(T("invalid ignore threshold")
1145                     T(" (needs an integer from 0 to 2^64-1)"));
1146         exit(XMLWF_EXIT_USAGE_ERROR);
1147       }
1148       attackThresholdGiven = XML_TRUE;
1149 #if XML_GE == 0
1150       ftprintf(stderr,
1151                T("Warning: Given attack threshold ignored")
1152                    T(", xmlwf has been compiled without DTD/GE support.\n"));
1153 #endif
1154       break;
1155     }
1156     case T('q'): {
1157       disableDeferral = XML_TRUE;
1158       j++;
1159       break;
1160     }
1161     case T('\0'):
1162       if (j > 1) {
1163         i++;
1164         j = 0;
1165         break;
1166       }
1167       EXPAT_FALLTHROUGH;
1168     default:
1169       usage(argv[0], XMLWF_EXIT_USAGE_ERROR);
1170       // usage called exit(..), never gets here
1171     }
1172   }
1173   if (i == argc) {
1174     useStdin = 1;
1175     processFlags &= ~XML_MAP_FILE;
1176     i--;
1177   }
1178   for (; i < argc; i++) {
1179     XML_Char *outName = 0;
1180     XML_Parser parser;
1181     if (useNamespaces)
1182       parser = XML_ParserCreateNS(encoding, NSSEP);
1183     else
1184       parser = XML_ParserCreate(encoding);
1185 
1186     if (! parser) {
1187       tperror(T("Could not instantiate parser"));
1188       exit(XMLWF_EXIT_INTERNAL_ERROR);
1189     }
1190 
1191     if (attackMaximumAmplification != -1.0f) {
1192 #if XML_GE == 1
1193       XML_SetBillionLaughsAttackProtectionMaximumAmplification(
1194           parser, attackMaximumAmplification);
1195       XML_SetAllocTrackerMaximumAmplification(parser,
1196                                               attackMaximumAmplification);
1197 #endif
1198     }
1199     if (attackThresholdGiven) {
1200 #if XML_GE == 1
1201       XML_SetBillionLaughsAttackProtectionActivationThreshold(
1202           parser, attackThresholdBytes);
1203       XML_SetAllocTrackerActivationThreshold(parser, attackThresholdBytes);
1204 #else
1205       (void)attackThresholdBytes; // silence -Wunused-but-set-variable
1206 #endif
1207     }
1208 
1209     if (disableDeferral) {
1210       const XML_Bool success = XML_SetReparseDeferralEnabled(parser, XML_FALSE);
1211       if (! success) {
1212         // This prevents tperror(..) from reporting misleading "[..]: Success"
1213         errno = EINVAL;
1214         tperror(T("Failed to disable reparse deferral"));
1215         exit(XMLWF_EXIT_INTERNAL_ERROR);
1216       }
1217     }
1218 
1219     if (requireStandalone)
1220       XML_SetNotStandaloneHandler(parser, notStandalone);
1221     XML_SetParamEntityParsing(parser, paramEntityParsing);
1222     if (outputType == 't') {
1223       /* This is for doing timings; this gives a more realistic estimate of
1224          the parsing time. */
1225       outputDir = 0;
1226       XML_SetElementHandler(parser, nopStartElement, nopEndElement);
1227       XML_SetCharacterDataHandler(parser, nopCharacterData);
1228       XML_SetProcessingInstructionHandler(parser, nopProcessingInstruction);
1229     } else if (outputDir) {
1230       const XML_Char *delim = T("/");
1231       const XML_Char *file = useStdin ? T("STDIN") : argv[i];
1232       if (! useStdin) {
1233         /* Jump after last (back)slash */
1234         const XML_Char *lastDelim = tcsrchr(file, delim[0]);
1235         if (lastDelim)
1236           file = lastDelim + 1;
1237 #if defined(_WIN32)
1238         else {
1239           const XML_Char *winDelim = T("\\");
1240           lastDelim = tcsrchr(file, winDelim[0]);
1241           if (lastDelim) {
1242             file = lastDelim + 1;
1243             delim = winDelim;
1244           }
1245         }
1246 #endif
1247       }
1248       const size_t outputDirLen = tcslen(outputDir);
1249       const size_t fileLen = tcslen(file);
1250 
1251       /* Detect and prevent integer overflow in the addition (without
1252          risking underflow) and the multiplication, mirroring the guards
1253          in xcsdup() and resolveSystemId() */
1254       if (outputDirLen > SIZE_MAX - fileLen
1255           || outputDirLen > SIZE_MAX - fileLen - 2) {
1256         tperror(T("Could not allocate memory"));
1257         exit(XMLWF_EXIT_INTERNAL_ERROR);
1258       }
1259 
1260       const size_t charsRequired = outputDirLen + fileLen + 2;
1261 
1262       if (charsRequired > SIZE_MAX / sizeof(XML_Char)) {
1263         tperror(T("Could not allocate memory"));
1264         exit(XMLWF_EXIT_INTERNAL_ERROR);
1265       }
1266 
1267       outName = malloc(charsRequired * sizeof(XML_Char));
1268       if (! outName) {
1269         tperror(T("Could not allocate memory"));
1270         exit(XMLWF_EXIT_INTERNAL_ERROR);
1271       }
1272       tcscpy(outName, outputDir);
1273       tcscat(outName, delim);
1274       tcscat(outName, file);
1275       userData.fp = tfopen(outName, T("wb"));
1276       if (! userData.fp) {
1277         tperror(outName);
1278         exitCode = XMLWF_EXIT_OUTPUT_ERROR;
1279         free(outName);
1280         XML_ParserFree(parser);
1281         if (continueOnError) {
1282           continue;
1283         } else {
1284           break;
1285         }
1286       }
1287       setvbuf(userData.fp, NULL, _IOFBF, 16384);
1288 #ifdef XML_UNICODE
1289       puttc(0xFEFF, userData.fp);
1290 #endif
1291       XML_SetUserData(parser, &userData);
1292       switch (outputType) {
1293       case 'm':
1294         XML_UseParserAsHandlerArg(parser);
1295         XML_SetElementHandler(parser, metaStartElement, metaEndElement);
1296         XML_SetProcessingInstructionHandler(parser, metaProcessingInstruction);
1297         XML_SetCommentHandler(parser, metaComment);
1298         XML_SetCdataSectionHandler(parser, metaStartCdataSection,
1299                                    metaEndCdataSection);
1300         XML_SetCharacterDataHandler(parser, metaCharacterData);
1301         XML_SetDoctypeDeclHandler(parser, metaStartDoctypeDecl,
1302                                   metaEndDoctypeDecl);
1303         XML_SetEntityDeclHandler(parser, metaEntityDecl);
1304         XML_SetNotationDeclHandler(parser, metaNotationDecl);
1305         XML_SetNamespaceDeclHandler(parser, metaStartNamespaceDecl,
1306                                     metaEndNamespaceDecl);
1307         metaStartDocument(parser);
1308         break;
1309       case 'c':
1310         XML_UseParserAsHandlerArg(parser);
1311         XML_SetDefaultHandler(parser, markup);
1312         XML_SetElementHandler(parser, defaultStartElement, defaultEndElement);
1313         XML_SetCharacterDataHandler(parser, defaultCharacterData);
1314         XML_SetProcessingInstructionHandler(parser,
1315                                             defaultProcessingInstruction);
1316         break;
1317       default:
1318         if (useNamespaces)
1319           XML_SetElementHandler(parser, startElementNS, endElementNS);
1320         else
1321           XML_SetElementHandler(parser, startElement, endElement);
1322         XML_SetCharacterDataHandler(parser, characterData);
1323 #ifndef W3C14N
1324         XML_SetProcessingInstructionHandler(parser, processingInstruction);
1325         if (requiresNotations) {
1326           XML_SetDoctypeDeclHandler(parser, startDoctypeDecl, endDoctypeDecl);
1327           XML_SetNotationDeclHandler(parser, notationDecl);
1328         }
1329 #endif /* not W3C14N */
1330         break;
1331       }
1332     }
1333     if (windowsCodePages)
1334       XML_SetUnknownEncodingHandler(parser, unknownEncoding, 0);
1335     const bool processingSuccess
1336         = XML_ProcessFile(parser, useStdin ? NULL : argv[i], processFlags);
1337     bool closingSuccess = true;
1338     if (outputDir) {
1339       if (outputType == 'm')
1340         metaEndDocument(parser);
1341       closingSuccess = (fclose(userData.fp) == 0);
1342       if (! closingSuccess)
1343         tperror(outName);
1344       if (! processingSuccess || ! closingSuccess) {
1345         tremove(outName);
1346       }
1347       free(outName);
1348     }
1349     XML_ParserFree(parser);
1350     if (! processingSuccess || ! closingSuccess) {
1351       // NOTE: If both failed, the last error should determine the exit code.
1352       //       Failure to close happened after failure to process.
1353       if (! closingSuccess)
1354         exitCode = XMLWF_EXIT_OUTPUT_ERROR;
1355       else if (! processingSuccess)
1356         exitCode = XMLWF_EXIT_NOT_WELLFORMED;
1357 
1358       cleanupUserData(&userData);
1359       if (! continueOnError) {
1360         break;
1361       }
1362     }
1363   }
1364   return exitCode;
1365 }
1366