Coverage Report

Created: 2026-08-14 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wget2/libwget/xml.c
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
/** @} */