Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2012 Tim Ruehsen |
3 | | * Copyright (c) 2015-2026 Free Software Foundation, Inc. |
4 | | * |
5 | | * This file is part of libwget. |
6 | | * |
7 | | * Libwget is free software: you can redistribute it and/or modify |
8 | | * it under the terms of the GNU Lesser General Public License as published by |
9 | | * the Free Software Foundation, either version 3 of the License, or |
10 | | * (at your option) any later version. |
11 | | * |
12 | | * Libwget is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | | * GNU Lesser General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General Public License |
18 | | * along with libwget. If not, see <https://www.gnu.org/licenses/>. |
19 | | * |
20 | | * |
21 | | * xml parsing routines |
22 | | * |
23 | | * Changelog |
24 | | * 22.06.2012 Tim Ruehsen created, but needs definitely a rewrite |
25 | | * |
26 | | * This derives from an old source code that I wrote in 2001. |
27 | | * It is short, fast and has a low memory print, BUT it is a hack. |
28 | | * It has to be replaced by e.g. libxml2 or something better. |
29 | | * |
30 | | * HTML parsing is (very) different from XML parsing, see here: |
31 | | * https://html.spec.whatwg.org/multipage/syntax.html |
32 | | * It is a PITA and should be handled by a specialized, external library ! |
33 | | * |
34 | | */ |
35 | | |
36 | | #include <config.h> |
37 | | |
38 | | #include <unistd.h> |
39 | | #include <stdio.h> |
40 | | #include <string.h> |
41 | | #include <fcntl.h> |
42 | | #include <sys/stat.h> |
43 | | #ifdef HAVE_MMAP |
44 | | #include <sys/mman.h> |
45 | | #endif |
46 | | |
47 | | #include <wget.h> |
48 | | #include "private.h" |
49 | | |
50 | 81.1k | #define MAX_XML_DEPTH 1024 |
51 | | |
52 | | typedef struct { |
53 | | const char |
54 | | *buf, //!< pointer to original start of buffer (0-terminated) |
55 | | *p, //!< pointer next char in buffer |
56 | | *token; //!< token buffer |
57 | | int |
58 | | hints; //!< XML_HINT... |
59 | | size_t |
60 | | token_size, //!< size of token buffer |
61 | | token_len; //!< used bytes of token buffer (not counting terminating 0 byte) |
62 | | void |
63 | | *user_ctx; //!< user context (not needed if we were using nested functions) |
64 | | wget_xml_callback |
65 | | *callback; //!< callback function for tokens |
66 | | } xml_context; |
67 | | |
68 | | /* \cond _hide_internal_symbols */ |
69 | 2.41M | #define ascii_isspace(c) (c == ' ' || (c >= 9 && c <= 13)) |
70 | | |
71 | | // working only for consecutive alphabets, e.g. EBCDIC would not work |
72 | 1.02M | #define ascii_isalpha(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) |
73 | | /* \endcond */ |
74 | | |
75 | | // append a char to token buffer |
76 | | |
77 | | static const char *getToken(xml_context *context) |
78 | 593k | { |
79 | 593k | int c; |
80 | 593k | const char *p; |
81 | | |
82 | | // skip leading whitespace |
83 | 604k | while ((c = *context->p) && ascii_isspace(c)) |
84 | 10.9k | context->p++; |
85 | 593k | if (!c) return NULL; // eof |
86 | 513k | context->token = context->p++; |
87 | | |
88 | | // info_printf("a c=%c\n", c); |
89 | | |
90 | 513k | if (ascii_isalpha(c) || c == '_') { |
91 | 498k | while ((c = *context->p) && !ascii_isspace(c) && c != '>' && c != '=') |
92 | 398k | context->p++; |
93 | 100k | if (!c) return NULL; // syntax error |
94 | | |
95 | 99.3k | context->token_len = context->p - context->token; |
96 | 99.3k | return context->token; |
97 | 100k | } |
98 | | |
99 | 413k | if (c == '/') { |
100 | 3.61k | if (!(c = *context->p)) return NULL; // syntax error |
101 | 3.60k | context->p++; |
102 | 3.60k | if (c == '>') { |
103 | 2.88k | context->token_len = 2; |
104 | 2.88k | return context->token; |
105 | 2.88k | } else return NULL; // syntax error |
106 | 3.60k | } |
107 | | |
108 | 410k | if (c == '\"' || c == '\'') { // read in quoted value |
109 | 2.77k | int quote = c; |
110 | | |
111 | 2.77k | context->token = context->p; |
112 | | |
113 | 2.77k | if (!(p = strchr(context->p, quote))) |
114 | 147 | return NULL; |
115 | 2.62k | context->p = p + 1; |
116 | | |
117 | 2.62k | context->token_len = context->p - context->token - 1; |
118 | 2.62k | return context->token; |
119 | 2.77k | } |
120 | | |
121 | 407k | if (c == '<') { // fetch specials, e.g. start of comments '<!--' |
122 | 206k | if (!(c = *context->p)) return NULL; // syntax error |
123 | 206k | context->p++; |
124 | 206k | if (c == '?' || c == '/') { |
125 | 8.82k | context->token_len = 2; |
126 | 8.82k | return context->token; |
127 | 8.82k | } |
128 | | |
129 | 197k | if (c == '!') { |
130 | | // left: <!--, <![CDATA[ and <!WHATEVER |
131 | 47.9k | if (!(c = *context->p)) return NULL; // syntax error |
132 | 47.9k | if (c == '-') { |
133 | 4.44k | context->p++; |
134 | 4.44k | if (!(c = *context->p)) return NULL; // syntax error |
135 | 4.43k | context->p++; |
136 | 4.43k | if (c == '-') { |
137 | 3.55k | context->token_len = 4; |
138 | 3.55k | return context->token; |
139 | 3.55k | } else { |
140 | 882 | context->p -= 2; |
141 | 882 | context->token_len = 2; |
142 | 882 | return context->token; |
143 | 882 | } |
144 | 43.5k | } else { |
145 | 43.5k | context->token_len = 2; |
146 | 43.5k | return context->token; |
147 | 43.5k | } |
148 | 149k | } else { |
149 | 149k | context->p--; |
150 | 149k | context->token_len = 1; |
151 | 149k | return context->token; |
152 | 149k | } |
153 | 197k | } |
154 | | |
155 | 200k | if (c == '>' || c == '=') { |
156 | 188k | context->token_len = 1; |
157 | 188k | return context->token; |
158 | 188k | } |
159 | | |
160 | 12.1k | if (c == '-') { // fetch specials, e.g. end of comments '-->' |
161 | 2.43k | if (!(c = *context->p)) return NULL; // syntax error |
162 | 2.40k | if (c != '-') { |
163 | 1.10k | c = '-'; //??? |
164 | 1.30k | } else { |
165 | 1.30k | context->p++; |
166 | 1.30k | if (!(c = *context->p)) return NULL; // syntax error |
167 | 1.29k | context->p++; |
168 | 1.29k | if (c != '>') { |
169 | 761 | context->p -= 2; |
170 | 761 | c = '-'; |
171 | 761 | } else { |
172 | 530 | context->token_len = 3; |
173 | 530 | return context->token; |
174 | 530 | } |
175 | 1.29k | } |
176 | 2.40k | } |
177 | | |
178 | 11.6k | if (c == '?') { // fetch specials, e.g. '?>' |
179 | 1.01k | if (!(c = *context->p)) return NULL; // syntax error |
180 | 1.00k | if (c != '>') { |
181 | | // c = '?'; |
182 | 501 | } else { |
183 | 500 | context->p++; |
184 | 500 | context->token_len = 2; |
185 | 500 | return context->token; |
186 | 500 | } |
187 | 1.00k | } |
188 | | |
189 | 828k | while ((c = *context->p) && !ascii_isspace(c)) |
190 | 817k | context->p++; |
191 | | |
192 | 11.1k | if (c) { |
193 | 9.95k | debug_printf("getToken =%.*s\n", (int)(context->p - context->token), context->token); |
194 | 9.95k | context->token_len = context->p - context->token; |
195 | 9.95k | return context->token; |
196 | 9.95k | } |
197 | | |
198 | 1.15k | return NULL; |
199 | 11.1k | } |
200 | | |
201 | | static const char *getHTMLValue(xml_context *context) |
202 | 2.44k | { |
203 | 2.44k | int c; |
204 | 2.44k | const char *p; |
205 | | |
206 | | // skip leading whitespace |
207 | 2.83k | while ((c = *context->p) && ascii_isspace(c)) |
208 | 384 | context->p++; |
209 | 2.44k | if (!c) return NULL; // eof |
210 | 2.41k | context->token = context->p++; |
211 | | |
212 | | // Check for and read in quoted value. |
213 | 2.41k | if (c == '\"' || c == '\'' || c == '`') { |
214 | 780 | int quote = c; |
215 | | |
216 | 780 | context->token = context->p; |
217 | | |
218 | 780 | if (!(p = strchr(context->p, quote))) |
219 | 36 | return NULL; |
220 | 744 | context->p = p + 1; |
221 | | |
222 | 744 | context->token_len = context->p - context->token - 1; |
223 | 744 | return context->token; |
224 | 780 | } |
225 | | |
226 | | // Read in unquoted value. |
227 | 4.40k | while ((c = *context->p) && !ascii_isspace(c) && c != '<' && c != '>' && !(c == '/' && *context->p == '>')) |
228 | 2.77k | context->p++; |
229 | 1.63k | if (c) { |
230 | 1.45k | debug_printf("getHTMLValue =%.*s\n", (int)(context->p - context->token), context->token); |
231 | 1.45k | context->token_len = context->p - context->token; |
232 | 1.45k | return context->token; |
233 | 1.45k | } |
234 | | |
235 | 176 | return NULL; |
236 | 1.63k | } |
237 | | |
238 | | static int getValue(xml_context *context) |
239 | 37.2k | { |
240 | 37.2k | int c; |
241 | | |
242 | 37.2k | context->token_len = 0; |
243 | 37.2k | context->token = context->p; |
244 | | |
245 | | // remove leading spaces |
246 | 52.7k | while ((c = *context->p) && ascii_isspace(c)) |
247 | 15.4k | context->p++; |
248 | 37.2k | if (!c) return EOF; |
249 | | |
250 | 36.6k | if (c == '=') { |
251 | 11.2k | context->p++; |
252 | | |
253 | 11.2k | if (context->hints&XML_HINT_HTML) { |
254 | 2.44k | if (!getHTMLValue(context)) |
255 | 250 | return EOF; // syntax error |
256 | 2.19k | else |
257 | 2.19k | return 1; // token valid |
258 | 2.44k | } |
259 | | |
260 | 8.77k | if (!getToken(context)) |
261 | 240 | return EOF; // syntax error |
262 | 8.53k | else |
263 | 8.53k | return 1; // token valid |
264 | 8.77k | } |
265 | | |
266 | | // attribute without value |
267 | 25.4k | context->token = context->p; |
268 | 25.4k | return 1; |
269 | 36.6k | } |
270 | | |
271 | | // special HTML <script> content parsing |
272 | | // see https://html.spec.whatwg.org/multipage/scripting.html#the-script-element |
273 | | // see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements |
274 | | |
275 | | static const char *getScriptContent(xml_context *context) |
276 | 1.38k | { |
277 | 1.38k | int comment = 0, length_valid = 0; |
278 | 1.38k | const char *p; |
279 | | |
280 | 100k | for (p = context->token = context->p; *p; p++) { |
281 | 100k | if (comment) { |
282 | 34.7k | if (*p == '-' && !strncmp(p, "-->", 3)) { |
283 | 1.02k | p += 3 - 1; |
284 | 1.02k | comment = 0; |
285 | 1.02k | } |
286 | 65.5k | } else { |
287 | 65.5k | if (*p == '<' && !strncmp(p, "<!--", 4)) { |
288 | 1.09k | p += 4 - 1; |
289 | 1.09k | comment = 1; |
290 | 64.4k | } else if (*p == '<' && !wget_strncasecmp_ascii(p, "</script", 8)) { |
291 | 1.50k | context->token_len = p - context->token; |
292 | 1.50k | length_valid = 1; |
293 | 2.11k | for (p += 8; ascii_isspace(*p); p++); |
294 | 1.50k | if (*p == '>') { |
295 | 1.00k | p++; |
296 | 1.00k | break; // found end of <script> |
297 | 1.00k | } else if (!*p) |
298 | 42 | break; // end of input |
299 | 1.50k | } |
300 | 65.5k | } |
301 | 100k | } |
302 | 1.38k | context->p = p; |
303 | | |
304 | 1.38k | if (!length_valid) |
305 | 270 | context->token_len = p - context->token; |
306 | | |
307 | 1.38k | if (!*p && !context->token_len) |
308 | 104 | return NULL; |
309 | | |
310 | 1.27k | if (context->callback) |
311 | 1.27k | context->callback(context->user_ctx, XML_FLG_CONTENT | XML_FLG_END, "script", NULL, context->token, context->token_len, context->token - context->buf); |
312 | | |
313 | 1.27k | return context->token; |
314 | 1.38k | } |
315 | | |
316 | | // special HTML <style> content parsing |
317 | | // see https://html.spec.whatwg.org/multipage/semantics.html#the-style-element |
318 | | static const char *getStyleContent(xml_context *context) |
319 | 1.49k | { |
320 | 1.49k | int comment = 0, length_valid = 0; |
321 | 1.49k | const char *p; |
322 | | |
323 | 72.1k | for (p = context->token = context->p; *p; p++) { |
324 | 71.8k | if (comment) { |
325 | 33.1k | if (p[0] == '*' && p[1] == '/') { |
326 | 764 | p += 2 - 1; |
327 | 764 | comment = 0; |
328 | 764 | } |
329 | 38.6k | } else { |
330 | 38.6k | if (p[0] == '/' && p[1] == '*') { |
331 | 840 | p += 2 - 1; |
332 | 840 | comment = 1; |
333 | 37.8k | } else if (*p == '<' && !wget_strncasecmp_ascii(p, "</style", 7)) { |
334 | 1.60k | context->token_len = p - context->token; |
335 | 1.60k | length_valid = 1; |
336 | 2.35k | for (p += 7; ascii_isspace(*p); p++); |
337 | 1.60k | if (*p == '>') { |
338 | 1.07k | p++; |
339 | 1.07k | break; // found end of <style> |
340 | 1.07k | } else if (!*p) |
341 | 46 | break; // end of input |
342 | 1.60k | } |
343 | 38.6k | } |
344 | 71.8k | } |
345 | 1.49k | context->p = p; |
346 | | |
347 | 1.49k | if (!length_valid) |
348 | 316 | context->token_len = p - context->token; |
349 | | |
350 | 1.49k | if (!*p && !context->token_len) |
351 | 104 | return NULL; |
352 | | |
353 | 1.39k | if (context->callback) |
354 | 1.39k | context->callback(context->user_ctx, XML_FLG_CONTENT | XML_FLG_END, "style", NULL, context->token, context->token_len, context->token - context->buf); |
355 | | |
356 | 1.39k | return context->token; |
357 | 1.49k | } |
358 | | |
359 | | static const char *getUnparsed(xml_context *context, int flags, const char *end, size_t len, const char *directory) |
360 | 49.3k | { |
361 | 49.3k | int c; |
362 | | |
363 | 49.3k | if (len == 1) { |
364 | 55.4k | for (context->token = context->p; (c = *context->p) && c != *end; context->p++); |
365 | 43.9k | } else { |
366 | 45.8k | for (context->token = context->p; (c = *context->p); context->p++) { |
367 | 45.1k | if (c == *end && context->p[1] == end[1] && (len == 2 || context->p[2] == end[2])) { |
368 | 4.62k | break; |
369 | 4.62k | } |
370 | 45.1k | } |
371 | 5.34k | } |
372 | | |
373 | 49.3k | context->token_len = context->p - context->token; |
374 | 49.3k | if (c) context->p += len; |
375 | | |
376 | 49.3k | if (!c && !context->token_len) |
377 | 77 | return NULL; |
378 | | /* |
379 | | if (context->token && context->token_len && context->hints & XML_HINT_REMOVE_EMPTY_CONTENT) { |
380 | | int notempty = 0; |
381 | | char *p; |
382 | | |
383 | | for (p = context->token; *p; p++) { |
384 | | if (!ascii_isspace(*p)) { |
385 | | notempty = 1; |
386 | | break; |
387 | | } |
388 | | } |
389 | | |
390 | | if (notempty) { |
391 | | if (context->callback) |
392 | | context->callback(context->user_ctx, flags, directory, NULL, context->token, context->token_len, context->token - context->buf); |
393 | | } else { |
394 | | // ignore empty content |
395 | | context->token_len = 0; |
396 | | context->token[0] = 0; |
397 | | } |
398 | | } else { |
399 | | */ |
400 | 49.2k | if (context->callback) |
401 | 47.2k | context->callback(context->user_ctx, flags, directory, NULL, context->token, context->token_len, context->token - context->buf); |
402 | | |
403 | | // } |
404 | | |
405 | 49.2k | return context->token; |
406 | 49.3k | } |
407 | | |
408 | | static const char *getComment(xml_context *context) |
409 | 3.07k | { |
410 | 3.07k | return getUnparsed(context, XML_FLG_COMMENT, "-->", 3, NULL); |
411 | 3.07k | } |
412 | | |
413 | | static const char *getProcessing(xml_context *context) |
414 | 2.26k | { |
415 | 2.26k | return getUnparsed(context, XML_FLG_PROCESSING, "?>", 2, NULL); |
416 | 2.26k | } |
417 | | |
418 | | static const char *getSpecial(xml_context *context) |
419 | 43.9k | { |
420 | 43.9k | return getUnparsed(context, XML_FLG_SPECIAL, ">", 1, NULL); |
421 | 43.9k | } |
422 | | |
423 | | static const char *getContent(xml_context *context, const char *directory) |
424 | 268k | { |
425 | 268k | int c; |
426 | | |
427 | 843k | for (context->token = context->p; (c = *context->p) && c != '<'; context->p++); |
428 | | |
429 | 268k | context->token_len = context->p - context->token; |
430 | | |
431 | 268k | if (!c && !context->token_len) |
432 | 72.9k | return NULL; |
433 | | |
434 | | // debug_printf("content=%.*s\n", (int)context->token_len, context->token); |
435 | 195k | if (context->callback && context->token_len) |
436 | 49.2k | context->callback(context->user_ctx, XML_FLG_CONTENT, directory, NULL, context->token, context->token_len, context->token - context->buf); |
437 | | |
438 | 195k | return context->token; |
439 | 268k | } |
440 | | |
441 | | static int parseXML(const char *dir, xml_context *context, int depth) |
442 | 94.3k | { |
443 | 94.3k | const char *tok; |
444 | 94.3k | char directory[256] = ""; |
445 | 94.3k | size_t pos = 0; |
446 | | |
447 | 94.3k | if (!(context->hints & XML_HINT_HTML)) { |
448 | 91.0k | pos = wget_strlcpy(directory, dir, sizeof(directory)); |
449 | 91.0k | if (pos >= sizeof(directory)) pos = sizeof(directory) - 1; |
450 | 91.0k | } |
451 | | |
452 | 268k | do { |
453 | 268k | getContent(context, directory); |
454 | 268k | if (context->token_len) |
455 | 50.5k | debug_printf("%s='%.*s'\n", directory, (int)context->token_len, context->token); |
456 | | |
457 | 268k | if (!(tok = getToken(context))) return WGET_E_SUCCESS; //eof |
458 | | // debug_printf("A Token '%.*s' len=%zu tok='%s'\n", (int)context->token_len, context->token, context->token_len, tok); |
459 | | |
460 | 189k | if (context->token_len == 1 && *tok == '<') { |
461 | | // get element name and add it to directory |
462 | 135k | int flags = XML_FLG_BEGIN; |
463 | | |
464 | 135k | if (!(tok = getToken(context))) return WGET_E_XML_PARSE_ERR; // syntax error |
465 | | |
466 | | // debug_printf("A2 Token '%.*s'\n", (int)context->token_len, context->token); |
467 | | |
468 | 134k | if (!(context->hints & XML_HINT_HTML)) { |
469 | 85.3k | if (!pos || directory[pos - 1] != '/') |
470 | 39.1k | wget_snprintf(&directory[pos], sizeof(directory) - pos, "/%.*s", (int)context->token_len, tok); |
471 | 46.1k | else |
472 | 46.1k | wget_snprintf(&directory[pos], sizeof(directory) - pos, "%.*s", (int)context->token_len, tok); |
473 | 85.3k | } else { |
474 | | // wget_snprintf(directory, sizeof(directory), "%.*s", (int)context->token_len, tok); |
475 | 48.9k | size_t dirlen = context->token_len >= sizeof(directory) ? sizeof(directory) - 1 : context->token_len; |
476 | | |
477 | 48.9k | memcpy(directory, tok, dirlen); |
478 | 48.9k | directory[dirlen] = 0; |
479 | 48.9k | } |
480 | | |
481 | 170k | while ((tok = getToken(context))) { |
482 | | // debug_printf("C Token %.*s %zu %p %p dir=%s tok=%s\n", (int)context->token_len, context->token, context->token_len, context->token, context->p, directory, tok); |
483 | 167k | if (context->token_len == 2 && !strncmp(tok, "/>", 2)) { |
484 | 2.14k | if (context->callback) |
485 | 1.69k | context->callback(context->user_ctx, flags | XML_FLG_END, directory, NULL, NULL, 0, 0); |
486 | 2.14k | break; // stay in this level |
487 | 165k | } else if (context->token_len == 1 && *tok == '>') { |
488 | 128k | if (context->callback) |
489 | 101k | context->callback(context->user_ctx, flags | XML_FLG_CLOSE, directory, NULL, NULL, 0, 0); |
490 | 128k | if (context->hints & XML_HINT_HTML) { |
491 | 47.1k | if (!wget_strcasecmp_ascii(directory, "script")) { |
492 | | // special HTML <script> content parsing |
493 | | // see https://html.spec.whatwg.org/multipage/scripting.html#the-script-element |
494 | | // 4.3.1.2 Restrictions for contents of script elements |
495 | 1.38k | debug_printf("*** need special <script> handling\n"); |
496 | 1.38k | getScriptContent(context); |
497 | 1.38k | if (context->token_len) |
498 | 502 | debug_printf("%s=%.*s\n", directory, (int)context->token_len, context->token); |
499 | 1.38k | } |
500 | 45.7k | else if (!wget_strcasecmp_ascii(directory, "style")) { |
501 | 1.49k | getStyleContent(context); |
502 | 1.49k | if (context->token_len) |
503 | 522 | debug_printf("%s=%.*s\n", directory, (int)context->token_len, context->token); |
504 | 1.49k | } |
505 | 81.1k | } else { |
506 | 81.1k | if (depth >= MAX_XML_DEPTH) return WGET_E_XML_MAX_DEPTH; |
507 | | |
508 | 81.1k | int ret = parseXML(directory, context, depth + 1); |
509 | 81.1k | if (ret != WGET_E_SUCCESS) |
510 | 6.80k | return ret; |
511 | 81.1k | } |
512 | 121k | break; |
513 | 128k | } else { |
514 | 37.2k | char attribute[256]; |
515 | 37.2k | size_t attrlen = context->token_len >= sizeof(attribute) ? sizeof(attribute) - 1 : context->token_len; |
516 | | |
517 | 37.2k | memcpy(attribute, tok, attrlen); |
518 | 37.2k | attribute[attrlen] = 0; |
519 | | |
520 | 37.2k | if (getValue(context) == EOF) return WGET_E_XML_PARSE_ERR; // syntax error |
521 | | |
522 | 36.1k | if (context->token_len) { |
523 | 9.65k | debug_printf("%s/@%s=%.*s\n", directory, attribute, (int)context->token_len, context->token); |
524 | 9.65k | if (context->callback) |
525 | 8.03k | context->callback(context->user_ctx, flags | XML_FLG_ATTRIBUTE, directory, attribute, context->token, context->token_len, context->token - context->buf); |
526 | 26.5k | } else { |
527 | 26.5k | debug_printf("%s/@%s\n", directory, attribute); |
528 | 26.5k | if (context->callback) |
529 | 21.7k | context->callback(context->user_ctx, flags | XML_FLG_ATTRIBUTE, directory, attribute, NULL, 0, 0); |
530 | 26.5k | } |
531 | 36.1k | flags = 0; |
532 | 36.1k | } |
533 | 167k | } |
534 | 126k | directory[pos] = 0; |
535 | 126k | } else if (context->token_len == 2) { |
536 | 51.5k | if (!strncmp(tok, "</", 2)) { |
537 | | // ascend one level |
538 | | // cleanup - get name and '>' |
539 | 5.33k | if (!(tok = getToken(context))) return WGET_E_XML_PARSE_ERR; |
540 | | // debug_printf("X Token %s\n",tok); |
541 | 5.10k | if (context->callback) { |
542 | 3.51k | if (!(context->hints & XML_HINT_HTML)) |
543 | 2.24k | context->callback(context->user_ctx, XML_FLG_END, directory, NULL, NULL, 0, 0); |
544 | 1.27k | else { |
545 | 1.27k | char tmp[128], *tag = tmp; // we need to \0 terminate tok |
546 | 1.27k | if (context->token_len >= sizeof(tmp)) |
547 | 200 | tag = wget_malloc(context->token_len + 1); |
548 | 1.27k | if (tag) { |
549 | 1.27k | memcpy(tag, tok, context->token_len); |
550 | 1.27k | tag[context->token_len] = 0; |
551 | 1.27k | context->callback(context->user_ctx, XML_FLG_END, tag, NULL, NULL, 0, 0); |
552 | 1.27k | if (tag != tmp) |
553 | 200 | xfree(tag); |
554 | 1.27k | } |
555 | 1.27k | } |
556 | 3.51k | } |
557 | 5.10k | if (!(tok = getToken(context))) return WGET_E_XML_PARSE_ERR; |
558 | | // debug_printf("Y Token %s\n",tok); |
559 | 4.96k | if (!(context->hints & XML_HINT_HTML)) |
560 | 3.73k | return WGET_E_SUCCESS; |
561 | 1.23k | else |
562 | 1.23k | continue; |
563 | 46.2k | } else if (!strncmp(tok, "<?", 2)) { // special info - ignore |
564 | 2.26k | getProcessing(context); |
565 | 2.26k | debug_printf("%s=<?%.*s?>\n", directory, (int)context->token_len, context->token); |
566 | 2.26k | continue; |
567 | 43.9k | } else if (!strncmp(tok, "<!", 2)) { |
568 | 43.9k | getSpecial(context); |
569 | 43.9k | debug_printf("%s=<!%.*s>\n", directory, (int)context->token_len, context->token); |
570 | 43.9k | } |
571 | 51.5k | } else if (context->token_len == 4 && !strncmp(tok, "<!--", 4)) { // comment - ignore |
572 | 3.07k | getComment(context); |
573 | 3.07k | debug_printf("%s=<!--%.*s-->\n", directory, (int)context->token_len, context->token); |
574 | 3.07k | continue; |
575 | 3.07k | } |
576 | 189k | } while (tok); |
577 | 2.76k | return WGET_E_SUCCESS; |
578 | 94.3k | } |
579 | | |
580 | | /** |
581 | | * \file |
582 | | * \brief XML parsing functions |
583 | | * \defgroup libwget-xml XML parsing functions |
584 | | * @{ |
585 | | */ |
586 | | |
587 | | /** |
588 | | * \param[in] buf Zero-terminated XML or HTML input data |
589 | | * \param[in] callback Function called for each token scan result |
590 | | * \param[in] user_ctx User-defined context variable, handed to \p callback |
591 | | * \param[in] hints Flags to influence parsing |
592 | | * |
593 | | * This function scans the XML input from \p buf and calls \p callback for each token |
594 | | * found. \p user_ctx is a user-defined context variable and given to each call of \p callback. |
595 | | * |
596 | | * \p hints may be 0 or any combination of %XML_HINT_REMOVE_EMPTY_CONTENT and %XML_HINT_HTML. |
597 | | * |
598 | | * %XML_HINT_REMOVE_EMPTY_CONTENT reduces the number of calls to \p callback by ignoring |
599 | | * empty content and superfluous spaces. |
600 | | * |
601 | | * %XML_HINT_HTML turns on HTML scanning. |
602 | | */ |
603 | | int wget_xml_parse_buffer( |
604 | | const char *buf, |
605 | | wget_xml_callback *callback, |
606 | | void *user_ctx, |
607 | | int hints) |
608 | 13.1k | { |
609 | 13.1k | xml_context context; |
610 | | |
611 | 13.1k | context.token = NULL; |
612 | 13.1k | context.token_size = 0; |
613 | 13.1k | context.token_len = 0; |
614 | 13.1k | context.buf = buf; |
615 | 13.1k | context.p = buf; |
616 | 13.1k | context.user_ctx = user_ctx; |
617 | 13.1k | context.callback = callback; |
618 | 13.1k | context.hints = hints; |
619 | | |
620 | 13.1k | return parseXML ("/", &context, 0); |
621 | 13.1k | } |
622 | | |
623 | | /** |
624 | | * \param[in] buf Zero-terminated HTML input data |
625 | | * \param[in] callback Function called for each token scan result |
626 | | * \param[in] user_ctx User-defined context variable, handed to \p callback |
627 | | * \param[in] hints Flags to influence parsing |
628 | | * |
629 | | * Convenience function that calls wget_xml_parse_buffer() with HTML parsing turned on. |
630 | | */ |
631 | | void wget_html_parse_buffer( |
632 | | const char *buf, |
633 | | wget_xml_callback *callback, |
634 | | void *user_ctx, |
635 | | int hints) |
636 | 3.21k | { |
637 | 3.21k | wget_xml_parse_buffer(buf, callback, user_ctx, hints | XML_HINT_HTML); |
638 | 3.21k | } |
639 | | |
640 | | /** |
641 | | * \param[in] fname Name of XML or HTML input file |
642 | | * \param[in] callback Function called for each token scan result |
643 | | * \param[in] user_ctx User-defined context variable, handed to \p callback |
644 | | * \param[in] hints Flags to influence parsing |
645 | | * |
646 | | * Convenience function that calls wget_xml_parse_buffer() with the file content. |
647 | | * |
648 | | * If \p fname is `-`, the data is read from stdin. |
649 | | */ |
650 | | void wget_xml_parse_file( |
651 | | const char *fname, |
652 | | wget_xml_callback *callback, |
653 | | void *user_ctx, |
654 | | int hints) |
655 | 3.21k | { |
656 | 3.21k | if (strcmp(fname,"-")) { |
657 | 1.60k | int fd; |
658 | | |
659 | 1.60k | if ((fd = open(fname, O_RDONLY|O_BINARY)) != -1) { |
660 | 1.60k | struct stat st; |
661 | 1.60k | if (fstat(fd, &st) == 0) { |
662 | 1.60k | #ifdef HAVE_MMAP |
663 | 1.60k | size_t nread = st.st_size; |
664 | 1.60k | char *buf = mmap(NULL, nread + 1, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); |
665 | | #else |
666 | | char *buf = wget_malloc(st.st_size + 1); |
667 | | if (!buf) |
668 | | error_printf(_("Failed to allocate %zu bytes for XML parse buffer\n"), st.st_size + 1); |
669 | | size_t nread = buf ? read(fd, buf, st.st_size) : -1; |
670 | | #endif |
671 | | |
672 | 1.60k | if (nread > 0) { |
673 | 0 | buf[nread] = 0; // PROT_WRITE allows this write, MAP_PRIVATE prevents changes in underlying file system |
674 | 0 | wget_xml_parse_buffer(buf, callback, user_ctx, hints); |
675 | 0 | } |
676 | | |
677 | 1.60k | #ifdef HAVE_MMAP |
678 | 1.60k | munmap(buf, nread); |
679 | | #else |
680 | | xfree(buf); |
681 | | #endif |
682 | 1.60k | } |
683 | 1.60k | close(fd); |
684 | 1.60k | } else |
685 | 0 | error_printf(_("Failed to open %s\n"), fname); |
686 | 1.60k | } else { |
687 | | // read data from STDIN. |
688 | | // maybe should use yy_scan_bytes instead of buffering into memory. |
689 | 1.60k | char tmp[4096]; |
690 | 1.60k | ssize_t nbytes; |
691 | 1.60k | wget_buffer buf; |
692 | | |
693 | 1.60k | wget_buffer_init(&buf, NULL, 4096); |
694 | | |
695 | 1.60k | while ((nbytes = read(STDIN_FILENO, tmp, sizeof(tmp))) > 0) { |
696 | 0 | wget_buffer_memcat(&buf, tmp, nbytes); |
697 | 0 | } |
698 | | |
699 | 1.60k | if (buf.length) |
700 | 0 | wget_xml_parse_buffer(buf.data, callback, user_ctx, hints); |
701 | | |
702 | 1.60k | wget_buffer_deinit(&buf); |
703 | 1.60k | } |
704 | 3.21k | } |
705 | | |
706 | | /** |
707 | | * \param[in] fname Name of XML or HTML input file |
708 | | * \param[in] callback Function called for each token scan result |
709 | | * \param[in] user_ctx User-defined context variable, handed to \p callback |
710 | | * \param[in] hints Flags to influence parsing |
711 | | * |
712 | | * Convenience function that calls wget_xml_parse_file() with HTML parsing turned on. |
713 | | * |
714 | | * If \p fname is `-`, the data is read from stdin. |
715 | | */ |
716 | | void wget_html_parse_file( |
717 | | const char *fname, |
718 | | wget_xml_callback *callback, |
719 | | void *user_ctx, |
720 | | int hints) |
721 | 3.21k | { |
722 | 3.21k | wget_xml_parse_file(fname, callback, user_ctx, hints | XML_HINT_HTML); |
723 | 3.21k | } |
724 | | |
725 | | /** |
726 | | * \param[in] src A string |
727 | | * \return A pointer to \p src, after the XML entities have been converted |
728 | | * |
729 | | * Decode XML entities from \p src. |
730 | | * |
731 | | * **The transformation is done inline**, so `src` will be modified after this function returns. |
732 | | * If no XML entities have been found, \p src is left untouched. |
733 | | * |
734 | | * Only a small subset of available XML entities is currently recognized. |
735 | | */ |
736 | | char *wget_xml_decode_entities_inline(char *src) |
737 | 0 | { |
738 | 0 | char *ret = NULL; |
739 | 0 | unsigned char *s = (unsigned char *)src; // just a helper to avoid casting a lot |
740 | 0 | unsigned char *d = s; |
741 | |
|
742 | 0 | while (*s) { |
743 | 0 | if (*s == '&') { |
744 | | // entities are case sensitive (RFC1866, 3.2.3) |
745 | 0 | if (s[1] == '#') { |
746 | 0 | if (s[2] == 'x') |
747 | 0 | *d = (unsigned char) strtol((char *) s + 3, (char **) &s, 16); |
748 | 0 | else |
749 | 0 | *d = (unsigned char) strtol((char *) s + 2, (char **) &s, 10); |
750 | 0 | if (*d == ' ') *d = '+'; // hack |
751 | 0 | d++; |
752 | 0 | if (*s == ';') s++; |
753 | 0 | ret = src; |
754 | 0 | continue; |
755 | 0 | } else if (!strncmp((char *) s + 1, "amp;", 4)) { |
756 | 0 | *d++ = '&'; |
757 | 0 | s += 5; |
758 | 0 | ret = src; |
759 | 0 | continue; |
760 | 0 | } else if (!strncmp((char *) s + 1, "gt;", 3)) { |
761 | 0 | *d++ = '>'; |
762 | 0 | s += 4; |
763 | 0 | ret = src; |
764 | 0 | continue; |
765 | 0 | } else if (!strncmp((char *) s + 1, "lt;", 3)) { |
766 | 0 | *d++ = '<'; |
767 | 0 | s += 4; |
768 | 0 | ret = src; |
769 | 0 | continue; |
770 | 0 | } else if (!strncmp((char *) s + 1, "quot;", 5)) { |
771 | 0 | *d++ = '\"'; |
772 | 0 | s += 6; |
773 | 0 | ret = src; |
774 | 0 | continue; |
775 | 0 | } else if (!strncmp((char *) s + 1, "apos;", 5)) { |
776 | 0 | *d++ = '\''; |
777 | 0 | s += 6; |
778 | 0 | ret = src; |
779 | 0 | continue; |
780 | 0 | } |
781 | 0 | } |
782 | | |
783 | 0 | *d++ = *s++; |
784 | 0 | } |
785 | 0 | *d = 0; |
786 | |
|
787 | 0 | return ret; |
788 | 0 | } |
789 | | |
790 | | |
791 | | /** @} */ |