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