Coverage Report

Created: 2026-09-01 06:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tidy-html5/src/lexer.c
Line
Count
Source
1
/* lexer.c -- Lexer for html parser
2
  
3
  (c) 1998-2008 (W3C) MIT, ERCIM, Keio University
4
  See tidy.h for the copyright notice.
5
6
*/
7
8
/*
9
  Given a file stream fp it returns a sequence of tokens.
10
11
     GetToken(fp) gets the next token
12
     UngetToken(fp) provides one level undo
13
14
  The tags include an attribute list:
15
16
    - linked list of attribute/value nodes
17
    - each node has 2 NULL-terminated strings.
18
    - entities are replaced in attribute values
19
20
  white space is compacted if not in preformatted mode
21
  If not in preformatted mode then leading white space
22
  is discarded and subsequent white space sequences
23
  compacted to single space characters.
24
25
  If XmlTags is no then Tag names are folded to upper
26
  case and attribute names to lower case.
27
28
 Not yet done:
29
    -   Doctype subset and marked sections
30
*/
31
32
#include "tidy-int.h"
33
#include "lexer.h"
34
#include "parser.h"
35
#include "entities.h"
36
#include "streamio.h"
37
#include "message.h"
38
#include "tmbstr.h"
39
#include "clean.h"
40
#include "utf8.h"
41
#include "streamio.h"
42
#include "sprtf.h"
43
44
#if defined(ENABLE_DEBUG_LOG)
45
/* #define DEBUG_ALLOCATION   special EXTRA allocation debug information - VERY NOISY */
46
static void check_me(char *name);
47
static Bool show_attrs = yes;
48
#define MX_TXT 8
49
static char buffer[(MX_TXT*4)+8]; /* NOTE extra for '...'\0 tail */
50
static tmbstr get_text_string(Lexer* lexer, Node *node)
51
{
52
    uint len = node->end - node->start;
53
    tmbstr cp = lexer->lexbuf + node->start;
54
    tmbstr end = lexer->lexbuf + node->end;
55
    unsigned char c;
56
    uint i = 0;
57
    Bool insp = no;
58
    if (len <= ((MX_TXT * 2) + 3)) {
59
        buffer[0] = 0;
60
        while (cp < end) {
61
            c = *cp;
62
            cp++;
63
            if (c == '\n') {
64
                buffer[i++] = '\\';
65
                buffer[i++] = 'n';
66
            } else if (c == '\t') {
67
                buffer[i++] = '\\';
68
                buffer[i++] = 't';
69
            } else if ( c == ' ' ) {
70
                if (!insp)
71
                    buffer[i++] = c;
72
                insp = yes;
73
            } else {
74
                buffer[i++] = c;
75
                insp = no;
76
            }
77
        }
78
    } else {
79
        char *end1 = cp + MX_TXT;
80
        char *bgn = cp + (len - MX_TXT);
81
        buffer[0] = 0;
82
        if (bgn < end1)
83
            bgn = end1;
84
        while (cp < end1) {
85
            c = *cp;
86
            cp++;
87
            if (c == '\n') {
88
                buffer[i++] = '\\';
89
                buffer[i++] = 'n';
90
            } else if (c == '\t') {
91
                buffer[i++] = '\\';
92
                buffer[i++] = 't';
93
            } else if ( c == ' ' ) {
94
                if (!insp)
95
                    buffer[i++] = c;
96
                insp = yes;
97
            } else {
98
                buffer[i++] = c;
99
                insp = no;
100
            }
101
            if (i >= MX_TXT)
102
                break;
103
        }
104
        c = '.';
105
        if ((i < len)&&(cp < bgn)) {
106
            buffer[i++] = c;
107
            cp++;
108
            if ((i < len)&&(cp < bgn)) {
109
                buffer[i++] = c;
110
                cp++;
111
                if ((i < len)&&(cp < bgn)) {
112
                    buffer[i++] = c;
113
                    cp++;
114
                }
115
            }
116
        }
117
        cp = bgn;
118
        insp = no;
119
        while (cp < end) {
120
            c = *cp;
121
            cp++;
122
            if (c == '\n') {
123
                buffer[i++] = '\\';
124
                buffer[i++] = 'n';
125
            } else if (c == '\t') {
126
                buffer[i++] = '\\';
127
                buffer[i++] = 't';
128
            } else if ( c == ' ' ) {
129
                if (!insp)
130
                    buffer[i++] = c;
131
                insp = yes;
132
            } else {
133
                buffer[i++] = c;
134
                insp = no;
135
            }
136
        }
137
    }
138
    buffer[i] = 0;
139
    return buffer;
140
}
141
static void Show_Node( TidyDocImpl* doc, const char *msg, Node *node )
142
{
143
    Lexer* lexer = doc->lexer;
144
    Bool lex = ((msg[0] == 'l')&&(msg[1] == 'e')) ? yes : no;
145
    int line = ( doc->lexer ? doc->lexer->lines : 0 );
146
    int col  = ( doc->lexer ? doc->lexer->columns : 0 );
147
    tmbstr src = lex ? "lexer" : "stream";
148
    SPRTF("R=%d C=%d: ", line, col );
149
    /* DEBUG: Be able to set a TRAP on a SPECIFIC row,col */
150
    if ((line == 3) && (col == 1)) {
151
        check_me("Show_Node"); /* just a debug trap */
152
    }
153
    if (lexer && lexer->token && 
154
        ((lexer->token->type == TextNode)||(node && (node->type == TextNode)))) {
155
        if (show_attrs) {
156
            uint len = node ? node->end - node->start : 0;
157
            tmbstr cp = node ? get_text_string( lexer, node ) : "NULL";
158
            SPRTF("Returning %s TextNode [%s]%u %s\n", msg, cp, len, src );
159
        } else {
160
            SPRTF("Returning %s TextNode %p... %s\n", msg, node, src );
161
        }
162
    } else {
163
        tmbstr name = node ? node->element ? node->element : "blank" : "NULL";
164
        if (show_attrs) {
165
            AttVal* av;
166
            SPRTF("Returning %s node <%s", msg, name);
167
            if (node) {
168
                for (av = node->attributes; av; av = av->next) {
169
                    name = av->attribute;
170
                    if (name) {
171
                        SPRTF(" %s",name);
172
                        if (av->value) {
173
                            SPRTF("=\"%s\"", av->value);
174
                        }
175
                    }
176
                }
177
            }
178
            SPRTF("> %s\n", src);
179
        } else {
180
            SPRTF("Returning %s node %p <%s>... %s\n", msg, node,
181
                name, src );
182
        }
183
    }
184
}
185
#define GTDBG(a,b,c) Show_Node(a,b,c)
186
#else /* ENABLE_DEBUG_LOG */
187
#define GTDBG(a,b,c)
188
#endif /* defined(ENABLE_DEBUG_LOG) */
189
190
/* Forward references
191
*/
192
/* swallows closing '>' */
193
static AttVal *ParseAttrs( TidyDocImpl* doc, Bool *isempty );
194
195
static tmbstr ParseAttribute( TidyDocImpl* doc, Bool* isempty, 
196
                             Node **asp, Node **php );
197
198
static tmbstr ParseValue( TidyDocImpl* doc, ctmbstr name, Bool foldCase,
199
                         Bool *isempty, int *pdelim );
200
201
static Node *ParseDocTypeDecl(TidyDocImpl* doc);
202
203
static void AddAttrToList( AttVal** list, AttVal* av );
204
205
/* used to classify characters for lexical purposes */
206
0
#define MAP(c) ((unsigned)c < 128 ? lexmap[(unsigned)c] : 0)
207
static uint lexmap[128];
208
209
0
#define IsValidXMLAttrName(name) TY_(IsValidXMLID)(name)
210
0
#define IsValidXMLElemName(name) TY_(IsValidXMLID)(name)
211
212
static struct _doctypes
213
{
214
    uint score;
215
    uint vers;
216
    uint vers_out;
217
    Bool xhtml;
218
    ctmbstr name;
219
    ctmbstr fpi;
220
    ctmbstr si;
221
} const W3C_Doctypes[] =
222
{
223
  {  2, HT20, 200, no,  "HTML 2.0",               "-//IETF//DTD HTML 2.0//EN",              NULL,                                                       },
224
  {  2, HT20, 200, no,  "HTML 2.0",               "-//IETF//DTD HTML//EN",                  NULL,                                                       },
225
  {  2, HT20, 200, no,  "HTML 2.0",               "-//W3C//DTD HTML 2.0//EN",               NULL,                                                       },
226
  {  1, HT32, 320, no,  "HTML 3.2",               "-//W3C//DTD HTML 3.2//EN",               NULL,                                                       },
227
  {  1, HT32, 320, no,  "HTML 3.2",               "-//W3C//DTD HTML 3.2 Final//EN",         NULL,                                                       },
228
  {  1, HT32, 320, no,  "HTML 3.2",               "-//W3C//DTD HTML 3.2 Draft//EN",         NULL,                                                       },
229
  {  6, H40S, 400, no,  "HTML 4.0 Strict",        "-//W3C//DTD HTML 4.0//EN",               "http://www.w3.org/TR/REC-html40/strict.dtd"                },
230
  {  8, H40T, 400, no,  "HTML 4.0 Transitional",  "-//W3C//DTD HTML 4.0 Transitional//EN",  "http://www.w3.org/TR/REC-html40/loose.dtd"                 },
231
  {  7, H40F, 400, no,  "HTML 4.0 Frameset",      "-//W3C//DTD HTML 4.0 Frameset//EN",      "http://www.w3.org/TR/REC-html40/frameset.dtd"              },
232
  {  3, H41S, 401, no,  "HTML 4.01 Strict",       "-//W3C//DTD HTML 4.01//EN",              "http://www.w3.org/TR/html4/strict.dtd"                     },
233
  {  5, H41T, 401, no,  "HTML 4.01 Transitional", "-//W3C//DTD HTML 4.01 Transitional//EN", "http://www.w3.org/TR/html4/loose.dtd"                      },
234
  {  4, H41F, 401, no,  "HTML 4.01 Frameset",     "-//W3C//DTD HTML 4.01 Frameset//EN",     "http://www.w3.org/TR/html4/frameset.dtd"                   },
235
  {  9, X10S, 100, yes, "XHTML 1.0 Strict",       "-//W3C//DTD XHTML 1.0 Strict//EN",       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"         },
236
  { 11, X10T, 100, yes, "XHTML 1.0 Transitional", "-//W3C//DTD XHTML 1.0 Transitional//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"   },
237
  { 10, X10F, 100, yes, "XHTML 1.0 Frameset",     "-//W3C//DTD XHTML 1.0 Frameset//EN",     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"       },
238
  { 12, XH11, 110, yes, "XHTML 1.1",              "-//W3C//DTD XHTML 1.1//EN",              "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"              },
239
  { 13, XB10, 100, yes, "XHTML Basic 1.0",        "-//W3C//DTD XHTML Basic 1.0//EN",        "http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd"        },
240
241
  { 20, HT50, 500, no,  "HTML5",                  NULL,                                     NULL                                                        },
242
  { 21, XH50, 500, yes, "XHTML5",                 NULL,                                     NULL                                                        },
243
244
  /* final entry */
245
  {  0,    0, 0,  no,  NULL,                     NULL,                                     NULL                                                        }
246
};
247
248
/* 
249
 * Issue #643 - Since VERS_FROM40 was extended to include VERS_HTML5
250
 * to be used in the expanded entity table some 155 times,
251
 * need a special macro here to denote just HTML 4 plus XHTML,
252
 * which is actually the former define of VERS_FROM40
253
 */
254
0
#define VERS_HMTL40PX        (VERS_HTML40|VERS_XHTML11|VERS_BASIC)
255
256
int TY_(HTMLVersion)(TidyDocImpl* doc)
257
0
{
258
0
    uint i;
259
0
    uint j = 0;
260
0
    uint score = 0;
261
0
    uint vers = doc->lexer->versions;
262
0
    uint dtver = doc->lexer->doctype;
263
0
    TidyDoctypeModes dtmode = (TidyDoctypeModes)cfg(doc, TidyDoctypeMode);
264
0
    Bool xhtml = (cfgBool(doc, TidyXmlOut) || doc->lexer->isvoyager) &&
265
0
                 !cfgBool(doc, TidyHtmlOut);
266
0
    Bool html4 = ((dtmode == TidyDoctypeStrict) || (dtmode == TidyDoctypeLoose) ||
267
0
                  (VERS_HMTL40PX & dtver) ? yes : no);
268
0
    Bool html5 = (!html4 && ((dtmode == TidyDoctypeAuto) ||
269
0
                  (dtmode == TidyDoctypeHtml5)) ? yes : no);
270
271
0
    if (xhtml && dtver == VERS_UNKNOWN) return XH50;
272
0
    if (dtver == VERS_UNKNOWN) return HT50;
273
    /* Issue #167 - if NOT XHTML, and doctype is default VERS_HTML5, then return HT50 */
274
0
    if (!xhtml && (dtver == VERS_HTML5)) return HT50;
275
    /* Issue #377 - If xhtml and (doctype == html5) and constrained vers contains XH50 return that,
276
       and really if tidy defaults to 'html5', then maybe 'auto' should also apply! */
277
0
    if (xhtml && html5 && ((vers & VERS_HTML5) == XH50)) return XH50;
278
279
0
    for (i = 0; W3C_Doctypes[i].name; ++i)
280
0
    {
281
0
        if ((xhtml && !(VERS_XHTML & W3C_Doctypes[i].vers)) ||
282
0
            (html4 && !(VERS_HMTL40PX & W3C_Doctypes[i].vers)))
283
0
            continue;
284
285
0
        if (vers & W3C_Doctypes[i].vers &&
286
0
            (W3C_Doctypes[i].score < score || !score))
287
0
        {
288
0
            score = W3C_Doctypes[i].score;
289
0
            j = i;
290
0
        }
291
0
    }
292
293
0
    if (score)
294
0
        return W3C_Doctypes[j].vers;
295
296
0
    return VERS_UNKNOWN;
297
0
}
298
299
static ctmbstr GetFPIFromVers(uint vers)
300
0
{
301
0
    uint i;
302
303
0
    for (i = 0; W3C_Doctypes[i].name; ++i)
304
0
        if (W3C_Doctypes[i].vers == vers)
305
0
            return W3C_Doctypes[i].fpi;
306
307
0
    return NULL;
308
0
}
309
310
static ctmbstr GetSIFromVers(uint vers)
311
0
{
312
0
    uint i;
313
314
0
    for (i = 0; W3C_Doctypes[i].name; ++i)
315
0
        if (W3C_Doctypes[i].vers == vers)
316
0
            return W3C_Doctypes[i].si;
317
318
0
    return NULL;
319
0
}
320
321
static ctmbstr GetNameFromVers(uint vers)
322
0
{
323
0
    uint i;
324
325
0
    for (i = 0; W3C_Doctypes[i].name; ++i)
326
0
        if (W3C_Doctypes[i].vers == vers)
327
0
            return W3C_Doctypes[i].name;
328
329
0
    return NULL;
330
0
}
331
332
static uint GetVersFromFPI(ctmbstr fpi)
333
0
{
334
0
    uint i;
335
336
0
    for (i = 0; W3C_Doctypes[i].name; ++i)
337
0
        if (W3C_Doctypes[i].fpi != NULL && TY_(tmbstrcasecmp)(W3C_Doctypes[i].fpi, fpi) == 0)
338
0
            return W3C_Doctypes[i].vers;
339
340
0
    return 0;
341
0
}
342
343
#ifdef ENABLE_DEBUG_LOG
344
#  ifndef EndBuf
345
#    define EndBuf(a)   ( a + strlen(a) )
346
#  endif
347
348
/* Issue #377 - Output diminishing version bits */
349
typedef struct tagV2S {
350
    uint bit;
351
    ctmbstr val;
352
}V2S, *PV2S;
353
354
static V2S v2s[] = {
355
    { HT20, "HT20" },
356
    { HT32, "HT32" },
357
    { H40S, "H40S" },
358
    { H40T, "H40T" },
359
    { H40F, "H40F" },
360
    { H41S, "H41S" },
361
    { H41T, "H41T" },
362
    { H41F, "H41F" },
363
    { X10S, "X10S" },
364
    { X10T, "X10T" },
365
    { X10F, "X10F" },
366
    { XH11, "XH11" },
367
    { XB10, "XB10" }, /* 4096u */
368
    /* { VERS_SUN, "VSUN" }, */
369
    /* { VERS_NETSCAPE, "VNET" }, */
370
    /* { VERS_MICROSOFT, "VMIC" }, 32768u */
371
    { VERS_XML, "VXML" }, /* 65536u */
372
        /* HTML5 */
373
    { HT50, "HT50" }, /* 131072u */
374
    { XH50, "XH50" }, /* 262144u */
375
    { 0,     0  }
376
};
377
378
/* Process the above table, adding a bit name,
379
   or '----' when not present   */
380
static char *add_vers_string( tmbstr buf, uint vers )
381
{
382
    PV2S pv2s = v2s;
383
    int len = (int)strlen(buf);
384
    while (pv2s->val) {
385
        if (vers & pv2s->bit) {
386
            if (len) {
387
                strcat(buf,"|");
388
                len++;
389
            }
390
            strcat(buf,pv2s->val);
391
            len += (int)strlen(pv2s->val);
392
            vers &= ~(pv2s->bit);
393
            if (!vers)
394
                break;
395
        } else {
396
            if (len) {
397
                strcat(buf,"|");
398
                len++;
399
            }
400
            strcat(buf,"----");
401
            len += 4;
402
403
        }
404
        pv2s++;
405
    }
406
    if (vers) { /* Should not have any here! */
407
        if (len)
408
            strcat(buf,"|");
409
        sprintf(EndBuf(buf),"%u",vers);
410
    }
411
    return buf;
412
413
}
414
415
/* Issue #377 - Show first Before: list, and then on any change
416
   Note the VERS_PROPRIETARY are exclude since they always remain */
417
void TY_(ConstrainVersion)(TidyDocImpl* doc, uint vers)
418
{
419
    static char vcur[256];
420
    static Bool dnfirst = no;
421
    uint curr = doc->lexer->versions; /* get current */
422
    doc->lexer->versions &= (vers | VERS_PROPRIETARY);
423
    if (curr != doc->lexer->versions) { /* only if different */
424
        if (!dnfirst) {
425
            dnfirst = yes;
426
            vcur[0] = 0;
427
            curr &= ~(VERS_PROPRIETARY);
428
            add_vers_string( vcur, curr );
429
            SPRTF("Before: %s\n", vcur);
430
        }
431
        vcur[0] = 0;
432
        curr = doc->lexer->versions;
433
        curr &= ~(VERS_PROPRIETARY);
434
        add_vers_string( vcur, curr );
435
        SPRTF("After : %s\n", vcur);
436
    }
437
}
438
#else /* !#if defined(ENABLE_DEBUG_LOG) */
439
/* everything is allowed in proprietary version of HTML */
440
/* this is handled here rather than in the tag/attr dicts */
441
void TY_(ConstrainVersion)(TidyDocImpl* doc, uint vers)
442
0
{
443
0
    doc->lexer->versions &= (vers | VERS_PROPRIETARY);
444
0
}
445
#endif /* #if defined(ENABLE_DEBUG_LOG) y/n */
446
447
Bool TY_(IsWhite)(uint c)
448
0
{
449
0
    uint map = MAP(c);
450
451
0
    return (map & white)!=0;
452
0
}
453
454
Bool TY_(IsNewline)(uint c)
455
0
{
456
0
    uint map = MAP(c);
457
0
    return (map & newline)!=0;
458
0
}
459
460
Bool TY_(IsDigit)(uint c)
461
0
{
462
0
    uint map;
463
464
0
    map = MAP(c);
465
466
0
    return (map & digit)!=0;
467
0
}
468
469
static Bool IsDigitHex(uint c)
470
0
{
471
0
    uint map;
472
473
0
    map = MAP(c);
474
475
0
    return (map & digithex)!=0;
476
0
}
477
478
Bool TY_(IsLetter)(uint c)
479
0
{
480
0
    uint map;
481
482
0
    map = MAP(c);
483
484
0
    return (map & letter)!=0;
485
0
}
486
487
Bool TY_(IsHTMLSpace)(uint c)
488
0
{
489
0
    return c == 0x020 || c == 0x009 || c == 0x00a || c == 0x00c || c == 0x00d;
490
0
}
491
492
Bool TY_(IsNamechar)(uint c)
493
0
{
494
0
    uint map = MAP(c);
495
0
    return (map & namechar)!=0;
496
0
}
497
498
Bool TY_(IsXMLLetter)(uint c)
499
0
{
500
0
    return ((c >= 0x41 && c <= 0x5a) ||
501
0
        (c >= 0x61 && c <= 0x7a) ||
502
0
        (c >= 0xc0 && c <= 0xd6) ||
503
0
        (c >= 0xd8 && c <= 0xf6) ||
504
0
        (c >= 0xf8 && c <= 0xff) ||
505
0
        (c >= 0x100 && c <= 0x131) ||
506
0
        (c >= 0x134 && c <= 0x13e) ||
507
0
        (c >= 0x141 && c <= 0x148) ||
508
0
        (c >= 0x14a && c <= 0x17e) ||
509
0
        (c >= 0x180 && c <= 0x1c3) ||
510
0
        (c >= 0x1cd && c <= 0x1f0) ||
511
0
        (c >= 0x1f4 && c <= 0x1f5) ||
512
0
        (c >= 0x1fa && c <= 0x217) ||
513
0
        (c >= 0x250 && c <= 0x2a8) ||
514
0
        (c >= 0x2bb && c <= 0x2c1) ||
515
0
        c == 0x386 ||
516
0
        (c >= 0x388 && c <= 0x38a) ||
517
0
        c == 0x38c ||
518
0
        (c >= 0x38e && c <= 0x3a1) ||
519
0
        (c >= 0x3a3 && c <= 0x3ce) ||
520
0
        (c >= 0x3d0 && c <= 0x3d6) ||
521
0
        c == 0x3da ||
522
0
        c == 0x3dc ||
523
0
        c == 0x3de ||
524
0
        c == 0x3e0 ||
525
0
        (c >= 0x3e2 && c <= 0x3f3) ||
526
0
        (c >= 0x401 && c <= 0x40c) ||
527
0
        (c >= 0x40e && c <= 0x44f) ||
528
0
        (c >= 0x451 && c <= 0x45c) ||
529
0
        (c >= 0x45e && c <= 0x481) ||
530
0
        (c >= 0x490 && c <= 0x4c4) ||
531
0
        (c >= 0x4c7 && c <= 0x4c8) ||
532
0
        (c >= 0x4cb && c <= 0x4cc) ||
533
0
        (c >= 0x4d0 && c <= 0x4eb) ||
534
0
        (c >= 0x4ee && c <= 0x4f5) ||
535
0
        (c >= 0x4f8 && c <= 0x4f9) ||
536
0
        (c >= 0x531 && c <= 0x556) ||
537
0
        c == 0x559 ||
538
0
        (c >= 0x561 && c <= 0x586) ||
539
0
        (c >= 0x5d0 && c <= 0x5ea) ||
540
0
        (c >= 0x5f0 && c <= 0x5f2) ||
541
0
        (c >= 0x621 && c <= 0x63a) ||
542
0
        (c >= 0x641 && c <= 0x64a) ||
543
0
        (c >= 0x671 && c <= 0x6b7) ||
544
0
        (c >= 0x6ba && c <= 0x6be) ||
545
0
        (c >= 0x6c0 && c <= 0x6ce) ||
546
0
        (c >= 0x6d0 && c <= 0x6d3) ||
547
0
        c == 0x6d5 ||
548
0
        (c >= 0x6e5 && c <= 0x6e6) ||
549
0
        (c >= 0x905 && c <= 0x939) ||
550
0
        c == 0x93d ||
551
0
        (c >= 0x958 && c <= 0x961) ||
552
0
        (c >= 0x985 && c <= 0x98c) ||
553
0
        (c >= 0x98f && c <= 0x990) ||
554
0
        (c >= 0x993 && c <= 0x9a8) ||
555
0
        (c >= 0x9aa && c <= 0x9b0) ||
556
0
        c == 0x9b2 ||
557
0
        (c >= 0x9b6 && c <= 0x9b9) ||
558
0
        (c >= 0x9dc && c <= 0x9dd) ||
559
0
        (c >= 0x9df && c <= 0x9e1) ||
560
0
        (c >= 0x9f0 && c <= 0x9f1) ||
561
0
        (c >= 0xa05 && c <= 0xa0a) ||
562
0
        (c >= 0xa0f && c <= 0xa10) ||
563
0
        (c >= 0xa13 && c <= 0xa28) ||
564
0
        (c >= 0xa2a && c <= 0xa30) ||
565
0
        (c >= 0xa32 && c <= 0xa33) ||
566
0
        (c >= 0xa35 && c <= 0xa36) ||
567
0
        (c >= 0xa38 && c <= 0xa39) ||
568
0
        (c >= 0xa59 && c <= 0xa5c) ||
569
0
        c == 0xa5e ||
570
0
        (c >= 0xa72 && c <= 0xa74) ||
571
0
        (c >= 0xa85 && c <= 0xa8b) ||
572
0
        c == 0xa8d ||
573
0
        (c >= 0xa8f && c <= 0xa91) ||
574
0
        (c >= 0xa93 && c <= 0xaa8) ||
575
0
        (c >= 0xaaa && c <= 0xab0) ||
576
0
        (c >= 0xab2 && c <= 0xab3) ||
577
0
        (c >= 0xab5 && c <= 0xab9) ||
578
0
        c == 0xabd ||
579
0
        c == 0xae0 ||
580
0
        (c >= 0xb05 && c <= 0xb0c) ||
581
0
        (c >= 0xb0f && c <= 0xb10) ||
582
0
        (c >= 0xb13 && c <= 0xb28) ||
583
0
        (c >= 0xb2a && c <= 0xb30) ||
584
0
        (c >= 0xb32 && c <= 0xb33) ||
585
0
        (c >= 0xb36 && c <= 0xb39) ||
586
0
        c == 0xb3d ||
587
0
        (c >= 0xb5c && c <= 0xb5d) ||
588
0
        (c >= 0xb5f && c <= 0xb61) ||
589
0
        (c >= 0xb85 && c <= 0xb8a) ||
590
0
        (c >= 0xb8e && c <= 0xb90) ||
591
0
        (c >= 0xb92 && c <= 0xb95) ||
592
0
        (c >= 0xb99 && c <= 0xb9a) ||
593
0
        c == 0xb9c ||
594
0
        (c >= 0xb9e && c <= 0xb9f) ||
595
0
        (c >= 0xba3 && c <= 0xba4) ||
596
0
        (c >= 0xba8 && c <= 0xbaa) ||
597
0
        (c >= 0xbae && c <= 0xbb5) ||
598
0
        (c >= 0xbb7 && c <= 0xbb9) ||
599
0
        (c >= 0xc05 && c <= 0xc0c) ||
600
0
        (c >= 0xc0e && c <= 0xc10) ||
601
0
        (c >= 0xc12 && c <= 0xc28) ||
602
0
        (c >= 0xc2a && c <= 0xc33) ||
603
0
        (c >= 0xc35 && c <= 0xc39) ||
604
0
        (c >= 0xc60 && c <= 0xc61) ||
605
0
        (c >= 0xc85 && c <= 0xc8c) ||
606
0
        (c >= 0xc8e && c <= 0xc90) ||
607
0
        (c >= 0xc92 && c <= 0xca8) ||
608
0
        (c >= 0xcaa && c <= 0xcb3) ||
609
0
        (c >= 0xcb5 && c <= 0xcb9) ||
610
0
        c == 0xcde ||
611
0
        (c >= 0xce0 && c <= 0xce1) ||
612
0
        (c >= 0xd05 && c <= 0xd0c) ||
613
0
        (c >= 0xd0e && c <= 0xd10) ||
614
0
        (c >= 0xd12 && c <= 0xd28) ||
615
0
        (c >= 0xd2a && c <= 0xd39) ||
616
0
        (c >= 0xd60 && c <= 0xd61) ||
617
0
        (c >= 0xe01 && c <= 0xe2e) ||
618
0
        c == 0xe30 ||
619
0
        (c >= 0xe32 && c <= 0xe33) ||
620
0
        (c >= 0xe40 && c <= 0xe45) ||
621
0
        (c >= 0xe81 && c <= 0xe82) ||
622
0
        c == 0xe84 ||
623
0
        (c >= 0xe87 && c <= 0xe88) ||
624
0
        c == 0xe8a ||
625
0
        c == 0xe8d ||
626
0
        (c >= 0xe94 && c <= 0xe97) ||
627
0
        (c >= 0xe99 && c <= 0xe9f) ||
628
0
        (c >= 0xea1 && c <= 0xea3) ||
629
0
        c == 0xea5 ||
630
0
        c == 0xea7 ||
631
0
        (c >= 0xeaa && c <= 0xeab) ||
632
0
        (c >= 0xead && c <= 0xeae) ||
633
0
        c == 0xeb0 ||
634
0
        (c >= 0xeb2 && c <= 0xeb3) ||
635
0
        c == 0xebd ||
636
0
        (c >= 0xec0 && c <= 0xec4) ||
637
0
        (c >= 0xf40 && c <= 0xf47) ||
638
0
        (c >= 0xf49 && c <= 0xf69) ||
639
0
        (c >= 0x10a0 && c <= 0x10c5) ||
640
0
        (c >= 0x10d0 && c <= 0x10f6) ||
641
0
        c == 0x1100 ||
642
0
        (c >= 0x1102 && c <= 0x1103) ||
643
0
        (c >= 0x1105 && c <= 0x1107) ||
644
0
        c == 0x1109 ||
645
0
        (c >= 0x110b && c <= 0x110c) ||
646
0
        (c >= 0x110e && c <= 0x1112) ||
647
0
        c == 0x113c ||
648
0
        c == 0x113e ||
649
0
        c == 0x1140 ||
650
0
        c == 0x114c ||
651
0
        c == 0x114e ||
652
0
        c == 0x1150 ||
653
0
        (c >= 0x1154 && c <= 0x1155) ||
654
0
        c == 0x1159 ||
655
0
        (c >= 0x115f && c <= 0x1161) ||
656
0
        c == 0x1163 ||
657
0
        c == 0x1165 ||
658
0
        c == 0x1167 ||
659
0
        c == 0x1169 ||
660
0
        (c >= 0x116d && c <= 0x116e) ||
661
0
        (c >= 0x1172 && c <= 0x1173) ||
662
0
        c == 0x1175 ||
663
0
        c == 0x119e ||
664
0
        c == 0x11a8 ||
665
0
        c == 0x11ab ||
666
0
        (c >= 0x11ae && c <= 0x11af) ||
667
0
        (c >= 0x11b7 && c <= 0x11b8) ||
668
0
        c == 0x11ba ||
669
0
        (c >= 0x11bc && c <= 0x11c2) ||
670
0
        c == 0x11eb ||
671
0
        c == 0x11f0 ||
672
0
        c == 0x11f9 ||
673
0
        (c >= 0x1e00 && c <= 0x1e9b) ||
674
0
        (c >= 0x1ea0 && c <= 0x1ef9) ||
675
0
        (c >= 0x1f00 && c <= 0x1f15) ||
676
0
        (c >= 0x1f18 && c <= 0x1f1d) ||
677
0
        (c >= 0x1f20 && c <= 0x1f45) ||
678
0
        (c >= 0x1f48 && c <= 0x1f4d) ||
679
0
        (c >= 0x1f50 && c <= 0x1f57) ||
680
0
        c == 0x1f59 ||
681
0
        c == 0x1f5b ||
682
0
        c == 0x1f5d ||
683
0
        (c >= 0x1f5f && c <= 0x1f7d) ||
684
0
        (c >= 0x1f80 && c <= 0x1fb4) ||
685
0
        (c >= 0x1fb6 && c <= 0x1fbc) ||
686
0
        c == 0x1fbe ||
687
0
        (c >= 0x1fc2 && c <= 0x1fc4) ||
688
0
        (c >= 0x1fc6 && c <= 0x1fcc) ||
689
0
        (c >= 0x1fd0 && c <= 0x1fd3) ||
690
0
        (c >= 0x1fd6 && c <= 0x1fdb) ||
691
0
        (c >= 0x1fe0 && c <= 0x1fec) ||
692
0
        (c >= 0x1ff2 && c <= 0x1ff4) ||
693
0
        (c >= 0x1ff6 && c <= 0x1ffc) ||
694
0
        c == 0x2126 ||
695
0
        (c >= 0x212a && c <= 0x212b) ||
696
0
        c == 0x212e ||
697
0
        (c >= 0x2180 && c <= 0x2182) ||
698
0
        (c >= 0x3041 && c <= 0x3094) ||
699
0
        (c >= 0x30a1 && c <= 0x30fa) ||
700
0
        (c >= 0x3105 && c <= 0x312c) ||
701
0
        (c >= 0xac00 && c <= 0xd7a3) ||
702
0
        (c >= 0x4e00 && c <= 0x9fa5) ||
703
0
        c == 0x3007 ||
704
0
        (c >= 0x3021 && c <= 0x3029) ||
705
0
        (c >= 0x4e00 && c <= 0x9fa5) ||
706
0
        c == 0x3007 ||
707
0
        (c >= 0x3021 && c <= 0x3029));
708
0
}
709
710
Bool TY_(IsXMLNamechar)(uint c)
711
0
{
712
0
    return (TY_(IsXMLLetter)(c) ||
713
0
        c == '.' || c == '_' ||
714
0
        c == ':' || c == '-' ||
715
0
        (c >= 0x300 && c <= 0x345) ||
716
0
        (c >= 0x360 && c <= 0x361) ||
717
0
        (c >= 0x483 && c <= 0x486) ||
718
0
        (c >= 0x591 && c <= 0x5a1) ||
719
0
        (c >= 0x5a3 && c <= 0x5b9) ||
720
0
        (c >= 0x5bb && c <= 0x5bd) ||
721
0
        c == 0x5bf ||
722
0
        (c >= 0x5c1 && c <= 0x5c2) ||
723
0
        c == 0x5c4 ||
724
0
        (c >= 0x64b && c <= 0x652) ||
725
0
        c == 0x670 ||
726
0
        (c >= 0x6d6 && c <= 0x6dc) ||
727
0
        (c >= 0x6dd && c <= 0x6df) ||
728
0
        (c >= 0x6e0 && c <= 0x6e4) ||
729
0
        (c >= 0x6e7 && c <= 0x6e8) ||
730
0
        (c >= 0x6ea && c <= 0x6ed) ||
731
0
        (c >= 0x901 && c <= 0x903) ||
732
0
        c == 0x93c ||
733
0
        (c >= 0x93e && c <= 0x94c) ||
734
0
        c == 0x94d ||
735
0
        (c >= 0x951 && c <= 0x954) ||
736
0
        (c >= 0x962 && c <= 0x963) ||
737
0
        (c >= 0x981 && c <= 0x983) ||
738
0
        c == 0x9bc ||
739
0
        c == 0x9be ||
740
0
        c == 0x9bf ||
741
0
        (c >= 0x9c0 && c <= 0x9c4) ||
742
0
        (c >= 0x9c7 && c <= 0x9c8) ||
743
0
        (c >= 0x9cb && c <= 0x9cd) ||
744
0
        c == 0x9d7 ||
745
0
        (c >= 0x9e2 && c <= 0x9e3) ||
746
0
        c == 0xa02 ||
747
0
        c == 0xa3c ||
748
0
        c == 0xa3e ||
749
0
        c == 0xa3f ||
750
0
        (c >= 0xa40 && c <= 0xa42) ||
751
0
        (c >= 0xa47 && c <= 0xa48) ||
752
0
        (c >= 0xa4b && c <= 0xa4d) ||
753
0
        (c >= 0xa70 && c <= 0xa71) ||
754
0
        (c >= 0xa81 && c <= 0xa83) ||
755
0
        c == 0xabc ||
756
0
        (c >= 0xabe && c <= 0xac5) ||
757
0
        (c >= 0xac7 && c <= 0xac9) ||
758
0
        (c >= 0xacb && c <= 0xacd) ||
759
0
        (c >= 0xb01 && c <= 0xb03) ||
760
0
        c == 0xb3c ||
761
0
        (c >= 0xb3e && c <= 0xb43) ||
762
0
        (c >= 0xb47 && c <= 0xb48) ||
763
0
        (c >= 0xb4b && c <= 0xb4d) ||
764
0
        (c >= 0xb56 && c <= 0xb57) ||
765
0
        (c >= 0xb82 && c <= 0xb83) ||
766
0
        (c >= 0xbbe && c <= 0xbc2) ||
767
0
        (c >= 0xbc6 && c <= 0xbc8) ||
768
0
        (c >= 0xbca && c <= 0xbcd) ||
769
0
        c == 0xbd7 ||
770
0
        (c >= 0xc01 && c <= 0xc03) ||
771
0
        (c >= 0xc3e && c <= 0xc44) ||
772
0
        (c >= 0xc46 && c <= 0xc48) ||
773
0
        (c >= 0xc4a && c <= 0xc4d) ||
774
0
        (c >= 0xc55 && c <= 0xc56) ||
775
0
        (c >= 0xc82 && c <= 0xc83) ||
776
0
        (c >= 0xcbe && c <= 0xcc4) ||
777
0
        (c >= 0xcc6 && c <= 0xcc8) ||
778
0
        (c >= 0xcca && c <= 0xccd) ||
779
0
        (c >= 0xcd5 && c <= 0xcd6) ||
780
0
        (c >= 0xd02 && c <= 0xd03) ||
781
0
        (c >= 0xd3e && c <= 0xd43) ||
782
0
        (c >= 0xd46 && c <= 0xd48) ||
783
0
        (c >= 0xd4a && c <= 0xd4d) ||
784
0
        c == 0xd57 ||
785
0
        c == 0xe31 ||
786
0
        (c >= 0xe34 && c <= 0xe3a) ||
787
0
        (c >= 0xe47 && c <= 0xe4e) ||
788
0
        c == 0xeb1 ||
789
0
        (c >= 0xeb4 && c <= 0xeb9) ||
790
0
        (c >= 0xebb && c <= 0xebc) ||
791
0
        (c >= 0xec8 && c <= 0xecd) ||
792
0
        (c >= 0xf18 && c <= 0xf19) ||
793
0
        c == 0xf35 ||
794
0
        c == 0xf37 ||
795
0
        c == 0xf39 ||
796
0
        c == 0xf3e ||
797
0
        c == 0xf3f ||
798
0
        (c >= 0xf71 && c <= 0xf84) ||
799
0
        (c >= 0xf86 && c <= 0xf8b) ||
800
0
        (c >= 0xf90 && c <= 0xf95) ||
801
0
        c == 0xf97 ||
802
0
        (c >= 0xf99 && c <= 0xfad) ||
803
0
        (c >= 0xfb1 && c <= 0xfb7) ||
804
0
        c == 0xfb9 ||
805
0
        (c >= 0x20d0 && c <= 0x20dc) ||
806
0
        c == 0x20e1 ||
807
0
        (c >= 0x302a && c <= 0x302f) ||
808
0
        c == 0x3099 ||
809
0
        c == 0x309a ||
810
0
        (c >= 0x30 && c <= 0x39) ||
811
0
        (c >= 0x660 && c <= 0x669) ||
812
0
        (c >= 0x6f0 && c <= 0x6f9) ||
813
0
        (c >= 0x966 && c <= 0x96f) ||
814
0
        (c >= 0x9e6 && c <= 0x9ef) ||
815
0
        (c >= 0xa66 && c <= 0xa6f) ||
816
0
        (c >= 0xae6 && c <= 0xaef) ||
817
0
        (c >= 0xb66 && c <= 0xb6f) ||
818
0
        (c >= 0xbe7 && c <= 0xbef) ||
819
0
        (c >= 0xc66 && c <= 0xc6f) ||
820
0
        (c >= 0xce6 && c <= 0xcef) ||
821
0
        (c >= 0xd66 && c <= 0xd6f) ||
822
0
        (c >= 0xe50 && c <= 0xe59) ||
823
0
        (c >= 0xed0 && c <= 0xed9) ||
824
0
        (c >= 0xf20 && c <= 0xf29) ||
825
0
        c == 0xb7 ||
826
0
        c == 0x2d0 ||
827
0
        c == 0x2d1 ||
828
0
        c == 0x387 ||
829
0
        c == 0x640 ||
830
0
        c == 0xe46 ||
831
0
        c == 0xec6 ||
832
0
        c == 0x3005 ||
833
0
        (c >= 0x3031 && c <= 0x3035) ||
834
0
        (c >= 0x309d && c <= 0x309e) ||
835
0
        (c >= 0x30fc && c <= 0x30fe));
836
0
}
837
838
Bool TY_(IsUpper)(uint c)
839
0
{
840
0
    uint map = MAP(c);
841
842
0
    return (map & uppercase)!=0;
843
0
}
844
845
uint TY_(ToLower)(uint c)
846
0
{
847
0
    uint map = MAP(c);
848
849
0
    if (map & uppercase)
850
0
        c += 'a' - 'A';
851
852
0
    return c;
853
0
}
854
855
uint TY_(ToUpper)(uint c)
856
0
{
857
0
    uint map = MAP(c);
858
859
0
    if (map & lowercase)
860
0
        c += (uint) ('A' - 'a' );
861
862
0
    return c;
863
0
}
864
865
/*
866
 return last character in string
867
 this is useful when trailing quotemark
868
 is missing on an attribute
869
*/
870
static tmbchar LastChar( tmbstr str )
871
0
{
872
0
    if ( str && *str )
873
0
    {
874
0
        int n = TY_(tmbstrlen)(str);
875
0
        return str[n-1];
876
0
    }
877
0
    return 0;
878
0
}
879
880
Lexer* TY_(NewLexer)( TidyDocImpl* doc )
881
0
{
882
0
    Lexer* lexer = (Lexer*) TidyDocAlloc( doc, sizeof(Lexer) );
883
884
0
    if ( lexer != NULL )
885
0
    {
886
0
        TidyClearMemory( lexer, sizeof(Lexer) );
887
888
0
        lexer->allocator = doc->allocator;
889
0
        lexer->lines = 1;
890
0
        lexer->columns = 1;
891
0
        lexer->state = LEX_CONTENT;
892
893
0
        lexer->versions = (VERS_ALL|VERS_PROPRIETARY);
894
0
        lexer->doctype = VERS_UNKNOWN;
895
0
        lexer->root = &doc->root;
896
0
    }
897
0
    return lexer;
898
0
}
899
900
static Bool EndOfInput( TidyDocImpl* doc )
901
0
{
902
0
    assert( doc->docIn != NULL );
903
0
    return ( !doc->docIn->pushed && TY_(IsEOF)(doc->docIn) );
904
0
}
905
906
void TY_(FreeLexer)( TidyDocImpl* doc )
907
0
{
908
0
    Lexer *lexer = doc->lexer;
909
0
    if ( lexer )
910
0
    {
911
0
        TY_(FreeStyles)( doc );
912
913
        /* See GetToken() */
914
0
        if ( lexer->pushed || lexer->itoken )
915
0
        {
916
0
            if (lexer->pushed)
917
0
                TY_(FreeNode)( doc, lexer->itoken );
918
0
            TY_(FreeNode)( doc, lexer->token );
919
0
        }
920
921
0
        while ( lexer->istacksize > 0 )
922
0
            TY_(PopInline)( doc, NULL );
923
924
0
        TidyDocFree( doc, lexer->istack );
925
0
        TidyDocFree( doc, lexer->lexbuf );
926
0
        TidyDocFree( doc, lexer );
927
0
        doc->lexer = NULL;
928
0
    }
929
0
}
930
931
/* Lexer uses bigger memory chunks than pprint as
932
** it must hold the entire input document. not just
933
** the last line or three.
934
*/
935
static void AddByte( Lexer *lexer, tmbchar ch )
936
0
{
937
0
    if ( lexer->lexsize + 2 >= lexer->lexlength )
938
0
    {
939
0
        tmbstr buf = NULL;
940
0
        uint allocAmt = lexer->lexlength;
941
0
        uint prev = allocAmt; /* Is. #761 */
942
0
        while ( lexer->lexsize + 2 >= allocAmt )
943
0
        {
944
0
            if ( allocAmt == 0 )
945
0
                allocAmt = 8192;
946
0
            else
947
0
                allocAmt *= 2;
948
0
            if (allocAmt < prev) /* Is. #761 - watch for wrap - and */
949
0
                TidyPanic(lexer->allocator, "\nPanic: out of internal memory!\nDocument input too big!\n");
950
0
        }
951
0
        buf = (tmbstr) TidyRealloc( lexer->allocator, lexer->lexbuf, allocAmt );
952
0
        if ( buf )
953
0
        {
954
0
          TidyClearMemory( buf + lexer->lexlength, 
955
0
                           allocAmt - lexer->lexlength );
956
0
          lexer->lexbuf = buf;
957
0
          lexer->lexlength = allocAmt;
958
0
        }
959
0
    }
960
961
0
    lexer->lexbuf[ lexer->lexsize++ ] = ch;
962
0
    lexer->lexbuf[ lexer->lexsize ]   = '\0';  /* debug */
963
0
}
964
965
static void ChangeChar( Lexer *lexer, tmbchar c )
966
0
{
967
0
    if ( lexer->lexsize > 0 )
968
0
    {
969
0
        lexer->lexbuf[ lexer->lexsize-1 ] = c;
970
0
    }
971
0
}
972
973
/* store character c as UTF-8 encoded byte stream */
974
void TY_(AddCharToLexer)( Lexer *lexer, uint c )
975
0
{
976
0
    int i, err, count = 0;
977
0
    tmbchar buf[10] = {0};
978
    
979
0
    err = TY_(EncodeCharToUTF8Bytes)( c, buf, NULL, &count );
980
0
    if (err)
981
0
    {
982
        /* replacement character 0xFFFD encoded as UTF-8 */
983
0
        buf[0] = (byte) 0xEF;
984
0
        buf[1] = (byte) 0xBF;
985
0
        buf[2] = (byte) 0xBD;
986
0
        count = 3;
987
0
    }
988
    
989
0
    for ( i = 0; i < count; ++i )
990
0
        AddByte( lexer, buf[i] );
991
0
}
992
993
static void AddStringToLexer( Lexer *lexer, ctmbstr str )
994
0
{
995
0
    uint c;
996
997
    /*  Many (all?) compilers will sign-extend signed chars (the default) when
998
    **  converting them to unsigned integer values.  We must cast our char to
999
    **  unsigned char before assigning it to prevent this from happening.
1000
    */
1001
0
    while( 0 != (c = (unsigned char) *str++ ))
1002
0
        TY_(AddCharToLexer)( lexer, c );
1003
0
}
1004
1005
1006
static void SetLexerLocus( TidyDocImpl* doc, Lexer *lexer )
1007
0
{
1008
0
    lexer->lines = doc->docIn->curline;
1009
0
    lexer->columns = doc->docIn->curcol;
1010
0
}
1011
1012
/*
1013
    Issue #483
1014
    Have detected the first of a surrogate pair...
1015
    Try to find, decode the second...
1016
    Already have '&' start...
1017
*/
1018
1019
typedef enum {
1020
    SP_ok,
1021
    SP_failed,
1022
    SP_error
1023
}SPStatus;
1024
1025
static SPStatus GetSurrogatePair(TidyDocImpl* doc, Bool isXml, uint *pch)
1026
0
{
1027
0
    Lexer* lexer = doc->lexer;
1028
0
    uint bufSize = 32;
1029
0
    uint c, ch = 0, offset = 0;
1030
0
    tmbstr buf = 0;
1031
0
    SPStatus status = SP_error;  /* assume failed */
1032
0
    int type = 0;   /* assume numeric */
1033
0
    uint fch = *pch;
1034
0
    int i;  /* has to be signed due to for i >= 0 */
1035
0
    if (!lexer)
1036
0
        return status;
1037
0
    buf = (tmbstr)TidyRealloc(lexer->allocator, buf, bufSize);
1038
0
    if (!buf)
1039
0
        return status;
1040
0
    while ((c = TY_(ReadChar)(doc->docIn)) != EndOfStream )
1041
0
    {
1042
0
        if (c == ';')
1043
0
        {
1044
0
            break;  /* reached end of entity */
1045
0
        }
1046
0
        if ((offset + 2) > bufSize)
1047
0
        {
1048
0
            bufSize *= 2;
1049
0
            buf = (tmbstr)TidyRealloc(lexer->allocator, buf, bufSize);
1050
0
            if (!buf)
1051
0
            {
1052
0
                break;
1053
0
            }
1054
0
        }
1055
0
        buf[offset++] = c;  /* add char to buffer */
1056
0
        if (offset == 1)
1057
0
        {
1058
0
            if (c != '#')   /* is a numeric entity */
1059
0
                break;
1060
0
        }
1061
0
        else if (offset == 2 && ((c == 'x') || (!isXml && c == 'X')))
1062
0
        {
1063
0
            type = 1;   /* set hex digits */
1064
0
        }
1065
0
        else
1066
0
        {
1067
0
            if (type)   /* if hex digits */
1068
0
            {
1069
0
                if (!IsDigitHex(c))
1070
0
                    break;
1071
0
            }
1072
0
            else    /* if numeric */
1073
0
            {
1074
0
                if (!TY_(IsDigit)(c))
1075
0
                    break;
1076
0
            }
1077
0
        }
1078
0
    }
1079
1080
0
    if (c == ';')
1081
0
    {
1082
0
        int scanned;
1083
1084
0
        buf[offset] = 0;
1085
0
        if (type)
1086
0
            scanned = sscanf(buf + 2, "%x", &ch);
1087
0
        else
1088
0
            scanned = sscanf(buf + 1, "%d", &ch);
1089
1090
0
        if (scanned == 1 && TY_(IsHighSurrogate)(ch))
1091
0
        {
1092
0
            ch = TY_(CombineSurrogatePair)(ch, fch);
1093
0
            if (TY_(IsValidCombinedChar)(ch))
1094
0
            {
1095
0
                *pch = ch;  /* return combined pair value */
1096
0
                status = SP_ok; /* full success - pair used */
1097
0
            }
1098
0
            else
1099
0
            {
1100
0
                status = SP_failed; /* is one of the 32 out-of-range pairs */
1101
0
                *pch = 0xFFFD;  /* return substitute character */
1102
0
                TY_(ReportSurrogateError)(doc, BAD_SURROGATE_PAIR, fch, ch); /* SP WARNING: -  */
1103
0
            }
1104
0
        }
1105
0
    }
1106
1107
0
    if (status == SP_error)
1108
0
    {
1109
        /* Error condition - can only put back all the chars */
1110
0
        if (c == ';') /* if last, not added to buffer */
1111
0
            TY_(UngetChar)(c, doc->docIn);
1112
0
        if (buf && offset)
1113
0
        {
1114
            /* correct the order for unget - last first */
1115
0
            for (i = offset - 1; i >= 0; i--)
1116
0
            {
1117
0
                c = buf[i];
1118
0
                TY_(UngetChar)(c, doc->docIn);
1119
0
            }
1120
0
        }
1121
0
    }
1122
1123
0
    if (buf)
1124
0
        TidyFree(lexer->allocator, buf);
1125
1126
0
    return status;
1127
0
}
1128
1129
/*
1130
  No longer attempts to insert missing ';' for unknown
1131
 entities unless one was present already, since this
1132
  gives unexpected results.
1133
1134
  For example:   <a href="something.htm?foo&bar&fred">
1135
  was tidied to: <a href="something.htm?foo&amp;bar;&amp;fred;">
1136
  rather than:   <a href="something.htm?foo&amp;bar&amp;fred">
1137
1138
  My thanks for Maurice Buxton for spotting this.
1139
1140
  Also Randy Waki pointed out the following case for the
1141
  04 Aug 00 version (bug #433012):
1142
  
1143
  For example:   <a href="something.htm?id=1&lang=en">
1144
  was tidied to: <a href="something.htm?id=1&lang;=en">
1145
  rather than:   <a href="something.htm?id=1&amp;lang=en">
1146
  
1147
  where "lang" is a known entity (#9001), but browsers would
1148
  misinterpret "&lang;" because it had a value > 256.
1149
  
1150
  So the case of an apparently known entity with a value > 256 and
1151
  missing a semicolon is handled specially.
1152
  
1153
  "ParseEntity" is also a bit of a misnomer - it handles entities and
1154
  numeric character references. Invalid NCR's are now reported.
1155
*/
1156
static void ParseEntity( TidyDocImpl* doc, GetTokenMode mode )
1157
0
{
1158
0
    typedef enum
1159
0
    {
1160
0
        ENT_default,
1161
0
        ENT_numdec,
1162
0
        ENT_numhex
1163
0
    } ENTState;
1164
    
1165
0
    typedef Bool (*ENTfn)(uint);
1166
0
    const ENTfn entFn[] = {
1167
0
        TY_(IsNamechar),
1168
0
        TY_(IsDigit),
1169
0
        IsDigitHex
1170
0
    };
1171
0
    uint start;
1172
0
    ENTState entState = ENT_default;
1173
0
    uint charRead = 0;
1174
0
    Bool semicolon = no, found = no;
1175
0
    Bool isXml = cfgBool( doc, TidyXmlTags );
1176
0
    Bool preserveEntities = cfgBool( doc, TidyPreserveEntities );
1177
0
    uint c, ch, startcol, entver = 0;
1178
0
    Lexer* lexer = doc->lexer;
1179
1180
0
    start = lexer->lexsize - 1;  /* to start at "&" */
1181
0
    startcol = doc->docIn->curcol - 1;
1182
1183
0
    while ( (c = TY_(ReadChar)(doc->docIn)) != EndOfStream )
1184
0
    {
1185
0
        if ( c == ';' )
1186
0
        {
1187
0
            semicolon = yes;
1188
0
            break;
1189
0
        }
1190
0
        ++charRead;
1191
1192
0
        if (charRead == 1 && c == '#')
1193
0
        {
1194
0
            if ( !cfgBool(doc, TidyNCR) ||
1195
0
                 cfg(doc, TidyInCharEncoding) == BIG5 ||
1196
0
                 cfg(doc, TidyInCharEncoding) == SHIFTJIS )
1197
0
            {
1198
0
                TY_(UngetChar)('#', doc->docIn);
1199
0
                return;
1200
0
            }
1201
1202
0
            TY_(AddCharToLexer)( lexer, c );
1203
0
            entState = ENT_numdec;
1204
0
            continue;
1205
0
        }
1206
0
        else if (charRead == 2 && entState == ENT_numdec
1207
0
                 && (c == 'x' || (!isXml && c == 'X')) )
1208
0
        {
1209
0
            TY_(AddCharToLexer)( lexer, c );
1210
0
            entState = ENT_numhex;
1211
0
            continue;
1212
0
        }
1213
1214
0
        if ( entFn[entState](c) )
1215
0
        {
1216
0
            TY_(AddCharToLexer)( lexer, c );
1217
0
            continue;
1218
0
        }
1219
1220
        /* otherwise put it back */
1221
0
        TY_(UngetChar)( c, doc->docIn );
1222
0
        break;
1223
0
    }
1224
1225
    /* make sure entity is NULL terminated */
1226
0
    lexer->lexbuf[lexer->lexsize] = '\0';
1227
1228
    /* Should contrain version to XML/XHTML if &apos; 
1229
    ** is encountered.  But this is not possible with
1230
    ** Tidy's content model bit mask.
1231
    */
1232
0
    if ( TY_(tmbstrcmp)(lexer->lexbuf+start, "&apos") == 0
1233
0
         && !cfgBool(doc, TidyXmlOut)
1234
0
         && !lexer->isvoyager
1235
0
         && !cfgBool(doc, TidyXhtmlOut)
1236
0
         && !(TY_(HTMLVersion)(doc) == HT50) ) /* Issue #239 - no warning if in HTML5++ mode */
1237
0
        TY_(ReportEntityError)( doc, APOS_UNDEFINED, lexer->lexbuf+start, 39 );
1238
1239
0
    if (( mode == OtherNamespace ) && ( c == ';' ))
1240
0
    {
1241
        /* #130 MathML attr and entity fix! */
1242
0
        found = yes;
1243
0
        ch = 255;
1244
0
        entver = XH50|HT50;
1245
0
        preserveEntities = yes;
1246
0
    }
1247
0
    else
1248
0
    {
1249
        /* Lookup entity code and version
1250
        */
1251
0
        found = TY_(EntityInfo)( lexer->lexbuf+start, isXml, &ch, &entver );
1252
0
    }
1253
1254
    /* Issue #483 - Deal with 'surrogate pairs' */
1255
    /* TODO: Maybe warning/error, like found a leading surrogate
1256
       but no following surrogate! Maybe should avoid outputting
1257
       invalid utf-8 for this entity - maybe substitute?  */
1258
0
    if (!preserveEntities && found && TY_(IsLowSurrogate)(ch))
1259
0
    {
1260
0
        uint c1;
1261
0
        if ((c1 = TY_(ReadChar)(doc->docIn)) == '&')
1262
0
        {
1263
0
            SPStatus status;
1264
            /* Have a following entity, 
1265
               so there is a chance of having a valid surrogate pair */
1266
0
            c1 = ch;    /* keep first value, in case of error */
1267
0
            status = GetSurrogatePair(doc, isXml, &ch);
1268
0
            if (status == SP_error)
1269
0
            {
1270
0
                TY_(ReportSurrogateError)(doc, BAD_SURROGATE_TAIL, c1, 0); /* SP WARNING: - using substitute character */
1271
0
                TY_(UngetChar)('&', doc->docIn);  /* otherwise put it back */
1272
0
            }
1273
0
        }
1274
0
        else
1275
0
        {
1276
            /* put this non-entity lead char back */
1277
0
            TY_(UngetChar)(c1, doc->docIn);
1278
            /* Have leading surrogate pair, with no tail */
1279
0
            TY_(ReportSurrogateError)(doc, BAD_SURROGATE_TAIL, ch, 0); /* SP WARNING: - using substitute character */
1280
0
            ch = 0xFFFD;
1281
0
        }
1282
0
    } 
1283
0
    else if (!preserveEntities && found && TY_(IsHighSurrogate)(ch))
1284
0
    {
1285
        /* Have trailing surrogate pair, with no lead */
1286
0
        TY_(ReportSurrogateError)(doc, BAD_SURROGATE_LEAD, ch, 0); /* SP WARNING: - using substitute character */
1287
0
        ch = 0xFFFD;
1288
0
    }
1289
1290
    /* deal with unrecognized or invalid entities */
1291
    /* #433012 - fix by Randy Waki 17 Feb 01 */
1292
    /* report invalid NCR's - Terry Teague 01 Sep 01 */
1293
0
    if ( !found || (ch >= 128 && ch <= 159) || (ch >= 256 && c != ';') )
1294
0
    {
1295
        /* set error position just before offending character */
1296
0
        SetLexerLocus( doc, lexer );
1297
0
        lexer->columns = startcol;
1298
1299
0
        if (lexer->lexsize > start + 1)
1300
0
        {
1301
0
            if (ch >= 128 && ch <= 159)
1302
0
            {
1303
                /* invalid numeric character reference */
1304
                
1305
0
                uint c1 = 0;
1306
0
                int replaceMode = DISCARDED_CHAR;
1307
            
1308
                /* Always assume Win1252 in this circumstance. */
1309
0
                c1 = TY_(DecodeWin1252)( ch );
1310
1311
0
                if ( c1 )
1312
0
                    replaceMode = REPLACED_CHAR;
1313
                
1314
0
                if ( c != ';' )  /* issue warning if not terminated by ';' */
1315
0
                    TY_(ReportEntityError)( doc, MISSING_SEMICOLON_NCR,
1316
0
                                            lexer->lexbuf+start, c );
1317
 
1318
0
                TY_(ReportEncodingError)(doc, INVALID_NCR, ch, replaceMode == DISCARDED_CHAR);
1319
                
1320
0
                if ( c1 )
1321
0
                {
1322
                    /* make the replacement */
1323
0
                    lexer->lexsize = start;
1324
0
                    TY_(AddCharToLexer)( lexer, c1 );
1325
0
                    semicolon = no;
1326
0
                }
1327
0
                else
1328
0
                {
1329
                    /* discard */
1330
0
                    lexer->lexsize = start;
1331
0
                    semicolon = no;
1332
0
               }
1333
               
1334
0
            }
1335
0
            else
1336
0
                TY_(ReportEntityError)( doc, UNKNOWN_ENTITY,
1337
0
                                        lexer->lexbuf+start, ch );
1338
1339
0
            if (semicolon)
1340
0
                TY_(AddCharToLexer)( lexer, ';' );
1341
0
        }
1342
0
        else
1343
0
        {
1344
            /*\ 
1345
             *  Issue #207 - A naked & is allowed in HTML5, as an unambiguous ampersand!
1346
            \*/
1347
0
            if (TY_(HTMLVersion)(doc) != HT50) 
1348
0
            {
1349
0
                TY_(ReportEntityError)( doc, UNESCAPED_AMPERSAND,
1350
0
                                    lexer->lexbuf+start, ch );
1351
0
            }
1352
0
        }
1353
0
    }
1354
0
    else
1355
0
    {
1356
0
        if ( c != ';' )    /* issue warning if not terminated by ';' */
1357
0
        {
1358
            /* set error position just before offending character */
1359
0
            SetLexerLocus( doc, lexer );
1360
0
            lexer->columns = startcol;
1361
0
            TY_(ReportEntityError)( doc, MISSING_SEMICOLON, lexer->lexbuf+start, c );
1362
0
        }
1363
1364
0
        if (preserveEntities)
1365
0
            TY_(AddCharToLexer)( lexer, ';' );
1366
0
        else
1367
0
        {
1368
0
            lexer->lexsize = start;
1369
0
            if ( ch == 160 && (mode == Preformatted) )
1370
0
                ch = ' ';
1371
0
            TY_(AddCharToLexer)( lexer, ch );
1372
1373
0
            if ( ch == '&' && !cfgBool(doc, TidyQuoteAmpersand) )
1374
0
                AddStringToLexer( lexer, "amp;" );
1375
0
        }
1376
1377
        /* Detect extended vs. basic entities */
1378
0
        TY_(ConstrainVersion)( doc, entver );
1379
0
    }
1380
0
}
1381
1382
static tmbchar ParseTagName( TidyDocImpl* doc )
1383
0
{
1384
0
    Lexer *lexer = doc->lexer;
1385
0
    uint c = lexer->lexbuf[ lexer->txtstart ];
1386
0
    Bool xml = cfgBool(doc, TidyXmlTags);
1387
1388
    /* fold case of first character in buffer */
1389
0
    if (!xml && TY_(IsUpper)(c))
1390
0
        lexer->lexbuf[lexer->txtstart] = (tmbchar) TY_(ToLower)(c);
1391
1392
0
    while ((c = TY_(ReadChar)(doc->docIn)) != EndOfStream)
1393
0
    {
1394
0
        if ((!xml && !TY_(IsNamechar)(c)) ||
1395
0
            (xml && !TY_(IsXMLNamechar)(c)))
1396
0
            break;
1397
1398
        /* fold case of subsequent characters */
1399
0
        if (!xml && TY_(IsUpper)(c))
1400
0
             c = TY_(ToLower)(c);
1401
1402
0
        TY_(AddCharToLexer)(lexer, c);
1403
0
    }
1404
1405
0
    lexer->txtend = lexer->lexsize;
1406
0
    return (tmbchar) c;
1407
0
}
1408
1409
/*
1410
  Used for elements and text nodes
1411
  element name is NULL for text nodes
1412
  start and end are offsets into lexbuf
1413
  which contains the textual content of
1414
  all elements in the parse tree.
1415
1416
  parent and content allow traversal
1417
  of the parse tree in any direction.
1418
  attributes are represented as a linked
1419
  list of AttVal nodes which hold the
1420
  strings for attribute/value pairs.
1421
*/
1422
1423
1424
Node *TY_(NewNode)(TidyAllocator* allocator, Lexer *lexer)
1425
0
{
1426
0
    Node* node = (Node*) TidyAlloc( allocator, sizeof(Node) );
1427
0
    TidyClearMemory( node, sizeof(Node) );
1428
0
    if ( lexer )
1429
0
    {
1430
0
        node->line = lexer->lines;
1431
0
        node->column = lexer->columns;
1432
0
    }
1433
0
    node->type = TextNode;
1434
#if defined(ENABLE_DEBUG_LOG) && defined(DEBUG_ALLOCATION)
1435
    SPRTF("Allocated node %p\n", node );
1436
#endif
1437
0
    return node;
1438
0
}
1439
1440
/* used to clone heading nodes when split by an <HR> */
1441
Node *TY_(CloneNode)( TidyDocImpl* doc, Node *element )
1442
0
{
1443
0
    Lexer* lexer = doc->lexer;
1444
0
    Node *node = TY_(NewNode)( lexer->allocator, lexer );
1445
1446
0
    node->start = lexer->lexsize;
1447
0
    node->end   = lexer->lexsize;
1448
1449
0
    if ( element )
1450
0
    {
1451
0
        node->parent     = element->parent;
1452
0
        node->type       = element->type;
1453
0
        node->closed     = element->closed;
1454
0
        node->implicit   = element->implicit;
1455
0
        node->tag        = element->tag;
1456
0
        node->element    = TY_(tmbstrdup)( doc->allocator, element->element );
1457
0
        node->attributes = TY_(DupAttrs)( doc, element->attributes );
1458
0
    }
1459
0
    return node;
1460
0
}
1461
1462
/* free node's attributes */
1463
void TY_(FreeAttrs)( TidyDocImpl* doc, Node *node )
1464
0
{
1465
0
    while ( node->attributes )
1466
0
    {
1467
0
        AttVal *av = node->attributes;
1468
1469
0
        if ( av->attribute )
1470
0
        {
1471
0
            if ( (attrIsID(av) || attrIsNAME(av)) &&
1472
0
                 TY_(IsAnchorElement)(doc, node) )
1473
0
            {
1474
0
                TY_(RemoveAnchorByNode)( doc, av->value, node );
1475
0
            }
1476
0
        }
1477
1478
0
        node->attributes = av->next;
1479
0
        TY_(FreeAttribute)( doc, av );
1480
0
    }
1481
0
}
1482
1483
/* doesn't repair attribute list linkage */
1484
void TY_(FreeAttribute)( TidyDocImpl* doc, AttVal *av )
1485
0
{
1486
0
    TY_(FreeNode)( doc, av->asp );
1487
0
    TY_(FreeNode)( doc, av->php );
1488
0
    TidyDocFree( doc, av->attribute );
1489
0
    TidyDocFree( doc, av->value );
1490
0
    TidyDocFree( doc, av );
1491
0
}
1492
1493
/* detach attribute from node
1494
*/
1495
void TY_(DetachAttribute)( Node *node, AttVal *attr )
1496
0
{
1497
0
    AttVal *av, *prev = NULL;
1498
1499
0
    for ( av = node->attributes; av; av = av->next )
1500
0
    {
1501
0
        if ( av == attr )
1502
0
        {
1503
0
            if ( prev )
1504
0
                prev->next = attr->next;
1505
0
            else
1506
0
                node->attributes = attr->next;
1507
0
            break;
1508
0
        }
1509
0
        prev = av;
1510
0
    }
1511
0
}
1512
1513
/* detach attribute from node then free it
1514
*/
1515
void TY_(RemoveAttribute)( TidyDocImpl* doc, Node *node, AttVal *attr )
1516
0
{
1517
0
    TY_(DetachAttribute)( node, attr );
1518
0
    TY_(FreeAttribute)( doc, attr );
1519
0
}
1520
1521
/*
1522
  Free document nodes by iterating through peers and recursing
1523
  through children. Set next to NULL before calling TY_(FreeNode)()
1524
  to avoid freeing peer nodes. Doesn't patch up prev/next links.
1525
 */
1526
void TY_(FreeNode)( TidyDocImpl* doc, Node *node )
1527
0
{
1528
#if defined(ENABLE_DEBUG_LOG) && defined(DEBUG_ALLOCATION)
1529
    /* avoid showing free of root node! */
1530
    if (node) {
1531
        if (RootNode != node->type) {
1532
            SPRTF("Free node %p\n", node);
1533
        }
1534
        else {
1535
            SPRTF("Root node %p\n", node);
1536
        }
1537
    }
1538
#endif
1539
1540
0
    while ( node )
1541
0
    {
1542
0
        Node* next = node->next;
1543
1544
0
        TY_(FreeAttrs)( doc, node );
1545
0
        TY_(FreeNode)( doc, node->content );
1546
0
        TidyDocFree( doc, node->element );
1547
0
        if (RootNode != node->type)
1548
0
            TidyDocFree( doc, node );
1549
0
        else
1550
0
            node->content = NULL;
1551
1552
0
        node = next;
1553
0
    }
1554
0
}
1555
1556
Node* TY_(TextToken)( Lexer *lexer )
1557
0
{
1558
0
    Node *node = TY_(NewNode)( lexer->allocator, lexer );
1559
0
    node->start = lexer->txtstart;
1560
0
    node->end = lexer->txtend;
1561
0
    return node;
1562
0
}
1563
1564
/* used for creating preformatted text from Word2000 */
1565
Node *TY_(NewLineNode)( Lexer *lexer )
1566
0
{
1567
0
    Node *node = TY_(NewNode)( lexer->allocator, lexer );
1568
0
    node->start = lexer->lexsize;
1569
0
    TY_(AddCharToLexer)( lexer, (uint)'\n' );
1570
0
    node->end = lexer->lexsize;
1571
0
    return node;
1572
0
}
1573
1574
/* used for adding a &nbsp; for Word2000 */
1575
Node* TY_(NewLiteralTextNode)( Lexer *lexer, ctmbstr txt )
1576
0
{
1577
0
    Node *node = TY_(NewNode)( lexer->allocator, lexer );
1578
0
    node->start = lexer->lexsize;
1579
0
    AddStringToLexer( lexer, txt );
1580
0
    node->end = lexer->lexsize;
1581
0
    return node;
1582
0
}
1583
1584
static Node* TagToken( TidyDocImpl* doc, NodeType type )
1585
0
{
1586
0
    Lexer* lexer = doc->lexer;
1587
0
    Node* node = TY_(NewNode)( lexer->allocator, lexer );
1588
0
    node->type = type;
1589
0
    node->element = TY_(tmbstrndup)( doc->allocator,
1590
0
                                     lexer->lexbuf + lexer->txtstart,
1591
0
                                     lexer->txtend - lexer->txtstart );
1592
0
    node->start = lexer->txtstart;
1593
0
    node->end = lexer->txtstart;
1594
1595
0
    if ( type == StartTag || type == StartEndTag || type == EndTag )
1596
0
        TY_(FindTag)(doc, node);
1597
1598
0
    return node;
1599
0
}
1600
1601
static Node* NewToken(TidyDocImpl* doc, NodeType type)
1602
0
{
1603
0
    Lexer* lexer = doc->lexer;
1604
0
    Node* node = TY_(NewNode)(lexer->allocator, lexer);
1605
0
    node->type = type;
1606
0
    node->start = lexer->txtstart;
1607
0
    node->end = lexer->txtend;
1608
0
    return node;
1609
0
}
1610
1611
0
#define CommentToken(doc) NewToken(doc, CommentTag)
1612
#define DocTypeToken(doc) NewToken(doc, DocTypeTag)
1613
0
#define PIToken(doc)      NewToken(doc, ProcInsTag)
1614
0
#define AspToken(doc)     NewToken(doc, AspTag)
1615
0
#define JsteToken(doc)    NewToken(doc, JsteTag)
1616
0
#define PhpToken(doc)     NewToken(doc, PhpTag)
1617
0
#define XmlDeclToken(doc) NewToken(doc, XmlDecl)
1618
0
#define SectionToken(doc) NewToken(doc, SectionTag)
1619
0
#define CDATAToken(doc)   NewToken(doc, CDATATag)
1620
1621
void TY_(AddStringLiteral)( Lexer* lexer, ctmbstr str )
1622
0
{
1623
0
    byte c;
1624
0
    while(0 != (c = *str++) ) {
1625
        /*\
1626
         *  Issue #286
1627
         *  Previously this used TY_(AddCharToLexer)( lexer, c );
1628
         *  which uses err = TY_(EncodeCharToUTF8Bytes)( c, buf, NULL, &count );
1629
         *  But this is transferring already 'translated' data from an
1630
         *  internal location to the lexer, so should use AddByte()
1631
        \*/
1632
0
        AddByte( lexer, c );
1633
0
    }
1634
0
}
1635
1636
/*
1637
void AddStringLiteralLen( Lexer* lexer, ctmbstr str, int len )
1638
{
1639
    byte c;
1640
    int ix;
1641
1642
    for ( ix=0; ix < len && (c = *str++); ++ix )
1643
        TY_(AddCharToLexer)(lexer, c);
1644
}
1645
*/
1646
1647
/* find doctype element */
1648
Node *TY_(FindDocType)( TidyDocImpl* doc )
1649
0
{
1650
0
    Node* node;
1651
0
    for ( node = (doc ? doc->root.content : NULL);
1652
0
          node && node->type != DocTypeTag; 
1653
0
          node = node->next )
1654
0
        /**/;
1655
0
    return node;
1656
0
}
1657
1658
/* find parent container element */
1659
Node* TY_(FindContainer)( Node* node )
1660
0
{
1661
0
    for ( node = (node ? node->parent : NULL);
1662
0
          node && TY_(nodeHasCM)(node, CM_INLINE);
1663
0
          node = node->parent )
1664
0
        /**/;
1665
1666
0
    return node;
1667
0
}
1668
1669
1670
/* find html element */
1671
Node *TY_(FindHTML)( TidyDocImpl* doc )
1672
0
{
1673
0
    Node *node;
1674
0
    for ( node = (doc ? doc->root.content : NULL);
1675
0
          node && !nodeIsHTML(node); 
1676
0
          node = node->next )
1677
0
        /**/;
1678
1679
0
    return node;
1680
0
}
1681
1682
/* find XML Declaration */
1683
Node *TY_(FindXmlDecl)(TidyDocImpl* doc)
1684
0
{
1685
0
    Node *node;
1686
0
    for ( node = (doc ? doc->root.content : NULL);
1687
0
          node && !(node->type == XmlDecl);
1688
0
          node = node->next )
1689
0
        /**/;
1690
1691
0
    return node;
1692
0
}
1693
1694
1695
Node *TY_(FindHEAD)( TidyDocImpl* doc )
1696
0
{
1697
0
    Node *node = TY_(FindHTML)( doc );
1698
1699
0
    if ( node )
1700
0
    {
1701
0
        for ( node = node->content;
1702
0
              node && !nodeIsHEAD(node); 
1703
0
              node = node->next )
1704
0
            /**/;
1705
0
    }
1706
1707
0
    return node;
1708
0
}
1709
1710
Node *TY_(FindTITLE)(TidyDocImpl* doc)
1711
0
{
1712
0
    Node *node = TY_(FindHEAD)(doc);
1713
1714
0
    if (node)
1715
0
        for (node = node->content;
1716
0
             node && !nodeIsTITLE(node);
1717
0
             node = node->next) {}
1718
1719
0
    return node;
1720
0
}
1721
1722
Node *TY_(FindBody)( TidyDocImpl* doc )
1723
0
{
1724
0
    Node *node = ( doc ? doc->root.content : NULL );
1725
1726
0
    while ( node && !nodeIsHTML(node) )
1727
0
        node = node->next;
1728
1729
0
    if (node == NULL)
1730
0
        return NULL;
1731
1732
0
    node = node->content;
1733
0
    while ( node && !nodeIsBODY(node) && !nodeIsFRAMESET(node) )
1734
0
        node = node->next;
1735
1736
0
    if ( node && nodeIsFRAMESET(node) )
1737
0
    {
1738
0
        node = node->content;
1739
0
        while ( node && !nodeIsNOFRAMES(node) )
1740
0
            node = node->next;
1741
1742
0
        if ( node )
1743
0
        {
1744
0
            node = node->content;
1745
0
            while ( node && !nodeIsBODY(node) )
1746
0
                node = node->next;
1747
0
        }
1748
0
    }
1749
1750
0
    return node;
1751
0
}
1752
1753
/* add meta element for Tidy */
1754
Bool TY_(AddGenerator)( TidyDocImpl* doc )
1755
0
{
1756
0
    AttVal *attval;
1757
0
    Node *node;
1758
0
    Node *head = TY_(FindHEAD)( doc );
1759
0
    tmbchar buf[256];
1760
    
1761
0
    if (head)
1762
0
    {
1763
0
#ifdef PLATFORM_NAME
1764
0
        TY_(tmbsnprintf)(buf, sizeof(buf), "HTML Tidy for HTML5 for "PLATFORM_NAME" version %s",
1765
0
                         tidyLibraryVersion());
1766
#else
1767
        TY_(tmbsnprintf)(buf, sizeof(buf), "HTML Tidy for HTML5 version %s", tidyLibraryVersion());
1768
#endif
1769
1770
0
        for ( node = head->content; node; node = node->next )
1771
0
        {
1772
0
            if ( nodeIsMETA(node) )
1773
0
            {
1774
0
                attval = TY_(AttrGetById)(node, TidyAttr_NAME);
1775
1776
0
                if (AttrValueIs(attval, "generator"))
1777
0
                {
1778
0
                    attval = TY_(AttrGetById)(node, TidyAttr_CONTENT);
1779
1780
0
                    if (AttrHasValue(attval) &&
1781
0
                        TY_(tmbstrncasecmp)(attval->value, "HTML Tidy", 9) == 0)
1782
0
                    {
1783
                        /* update the existing content to reflect the */
1784
                        /* actual version of Tidy currently being used */
1785
                        
1786
0
                        TidyDocFree(doc, attval->value);
1787
0
                        attval->value = TY_(tmbstrdup)(doc->allocator, buf);
1788
0
                        return no;
1789
0
                    }
1790
0
                }
1791
0
            }
1792
0
        }
1793
1794
0
        if ( cfg(doc, TidyAccessibilityCheckLevel) == 0 )
1795
0
        {
1796
0
            node = TY_(InferredTag)(doc, TidyTag_META);
1797
0
            TY_(AddAttribute)( doc, node, "name", "generator" );
1798
0
            TY_(AddAttribute)( doc, node, "content", buf );
1799
0
            TY_(InsertNodeAtStart)( head, node );
1800
0
            return yes;
1801
0
        }
1802
0
    }
1803
1804
0
    return no;
1805
0
}
1806
1807
/*\ examine <!DOCTYPE ...> to identify version 
1808
 *  Issue #167 and #169
1809
 *   If HTML5
1810
 *        <!DOCTYPE html>
1811
 *       <!DOCTYPE html SYSTEM "about:legacy-compat">
1812
 *   else others
1813
\*/
1814
static uint FindGivenVersion( TidyDocImpl* doc, Node* doctype )
1815
0
{
1816
0
    AttVal * fpi = TY_(GetAttrByName)(doctype, "PUBLIC");
1817
0
    uint vers;
1818
1819
0
    if (!fpi || !fpi->value) 
1820
0
    {
1821
        /*\
1822
         * Is. #815 - change to case-insensitive test
1823
         * See REC: https://www.w3.org/TR/html5/syntax.html#the-doctype
1824
        \*/
1825
0
        if (doctype->element && (TY_(tmbstrcasecmp)(doctype->element,"html") == 0))
1826
0
        {
1827
0
            return VERS_HTML5;  /* TODO: do we need to check MORE? */
1828
0
        }
1829
        /* TODO: Consider warning, error message */
1830
0
        return VERS_UNKNOWN;
1831
0
    }
1832
0
    vers = GetVersFromFPI(fpi->value);
1833
1834
0
    if (VERS_XHTML & vers)
1835
0
    {
1836
0
        TY_(SetOptionBool)(doc, TidyXmlOut, yes);
1837
0
        TY_(SetOptionBool)(doc, TidyXhtmlOut, yes);
1838
0
        doc->lexer->isvoyager = yes;
1839
0
    }
1840
1841
    /* todo: add a warning if case does not match? */
1842
0
    TidyDocFree(doc, fpi->value);
1843
0
    fpi->value = TY_(tmbstrdup)(doc->allocator, GetFPIFromVers(vers));
1844
1845
0
    return vers;
1846
0
}
1847
1848
/* return guessed version */
1849
uint TY_(ApparentVersion)( TidyDocImpl* doc )
1850
0
{
1851
0
    if ((doc->lexer->doctype == XH11 ||
1852
0
         doc->lexer->doctype == XB10) &&
1853
0
        (doc->lexer->versions & doc->lexer->doctype))
1854
0
        return doc->lexer->doctype;
1855
0
    else
1856
0
        return TY_(HTMLVersion)(doc);
1857
0
}
1858
1859
ctmbstr TY_(HTMLVersionNameFromCode)( uint vers, Bool ARG_UNUSED(isXhtml) )
1860
0
{
1861
0
    ctmbstr name = GetNameFromVers(vers);
1862
0
    return name;
1863
0
}
1864
1865
uint TY_(HTMLVersionNumberFromCode)( uint vers )
1866
0
{
1867
0
    uint i;
1868
1869
0
    for (i = 0; W3C_Doctypes[i].name; ++i)
1870
0
        if (W3C_Doctypes[i].vers == vers)
1871
0
            return W3C_Doctypes[i].vers_out;
1872
1873
0
    return VERS_UNKNOWN;
1874
0
}
1875
1876
Bool TY_(WarnMissingSIInEmittedDocType)( TidyDocImpl* doc )
1877
0
{
1878
0
    Bool isXhtml = doc->lexer->isvoyager;
1879
0
    Node* doctype;
1880
    
1881
    /* Do not warn in XHTML mode */
1882
0
    if ( isXhtml )
1883
0
        return no;
1884
1885
    /* Do not warn if emitted doctype is proprietary */
1886
0
    if ( TY_(HTMLVersionNameFromCode)(doc->lexer->versionEmitted, isXhtml ) == NULL )
1887
0
        return no;
1888
1889
    /* Do not warn if no SI is possible */
1890
0
    if ( GetSIFromVers(doc->lexer->versionEmitted) == NULL )
1891
0
        return no;
1892
1893
0
    if ( (doctype = TY_(FindDocType)( doc )) != NULL
1894
0
         && TY_(GetAttrByName)(doctype, "SYSTEM") == NULL )
1895
0
        return yes;
1896
1897
0
    return no;
1898
0
}
1899
1900
1901
/* Put DOCTYPE declaration between the
1902
** <?xml version "1.0" ... ?> declaration, if any,
1903
** and the <html> tag.  Should also work for any comments, 
1904
** etc. that may precede the <html> tag.
1905
*/
1906
1907
static Node* NewDocTypeNode( TidyDocImpl* doc )
1908
0
{
1909
0
    Node* doctype = NULL;
1910
0
    Node* html = TY_(FindHTML)( doc );
1911
1912
0
    if ( !html )
1913
0
        return NULL;
1914
1915
0
    doctype = TY_(NewNode)( doc->allocator, NULL );
1916
0
    doctype->type = DocTypeTag;
1917
0
    TY_(InsertNodeBeforeElement)(html, doctype);
1918
0
    return doctype;
1919
0
}
1920
1921
Bool TY_(SetXHTMLDocType)( TidyDocImpl* doc )
1922
0
{
1923
0
    Lexer *lexer = doc->lexer;
1924
0
    Node *doctype = TY_(FindDocType)( doc );
1925
0
    TidyDoctypeModes dtmode = (TidyDoctypeModes)cfg(doc, TidyDoctypeMode);
1926
0
    ctmbstr pub = "PUBLIC";
1927
0
    ctmbstr sys = "SYSTEM";
1928
1929
0
    lexer->versionEmitted = TY_(ApparentVersion)( doc );
1930
1931
0
    if (dtmode == TidyDoctypeOmit)
1932
0
    {
1933
0
        if (doctype)
1934
0
            TY_(DiscardElement)(doc, doctype);
1935
0
        return yes;
1936
0
    }
1937
1938
0
    if (dtmode == TidyDoctypeUser && !cfgStr(doc, TidyDoctype))
1939
0
        return no;
1940
1941
0
    if (!doctype)
1942
0
    {
1943
0
        doctype = NewDocTypeNode(doc);
1944
0
        doctype->element = TY_(tmbstrdup)(doc->allocator, "html");
1945
0
    }
1946
0
    else
1947
0
    {
1948
0
        doctype->element = TY_(tmbstrtolower)(doctype->element);
1949
0
    }
1950
1951
0
    switch(dtmode)
1952
0
    {
1953
0
    case TidyDoctypeHtml5:
1954
        /* HTML5 */
1955
0
        TY_(RepairAttrValue)(doc, doctype, pub, NULL);
1956
0
        TY_(RepairAttrValue)(doc, doctype, sys, NULL);
1957
0
        lexer->versionEmitted = XH50;
1958
0
        break;
1959
0
    case TidyDoctypeStrict:
1960
        /* XHTML 1.0 Strict */
1961
0
        TY_(RepairAttrValue)(doc, doctype, pub, GetFPIFromVers(X10S));
1962
0
        TY_(RepairAttrValue)(doc, doctype, sys, GetSIFromVers(X10S));
1963
0
        lexer->versionEmitted = X10S;
1964
0
        break;
1965
0
    case TidyDoctypeLoose:
1966
        /* XHTML 1.0 Transitional */
1967
0
        TY_(RepairAttrValue)(doc, doctype, pub, GetFPIFromVers(X10T));
1968
0
        TY_(RepairAttrValue)(doc, doctype, sys, GetSIFromVers(X10T));
1969
0
        lexer->versionEmitted = X10T;
1970
0
        break;
1971
0
    case TidyDoctypeUser:
1972
        /* user defined document type declaration */
1973
0
        TY_(RepairAttrValue)(doc, doctype, pub, cfgStr(doc, TidyDoctype));
1974
0
        TY_(RepairAttrValue)(doc, doctype, sys, "");
1975
0
        break;
1976
0
    case TidyDoctypeAuto:
1977
0
        if (lexer->doctype == VERS_UNKNOWN || lexer->doctype == VERS_HTML5) {
1978
0
          lexer->versionEmitted = XH50;
1979
0
          return yes;
1980
0
        }
1981
0
        else if (lexer->versions & XH11 && lexer->doctype == XH11)
1982
0
        {
1983
0
            if (!TY_(GetAttrByName)(doctype, sys))
1984
0
                TY_(RepairAttrValue)(doc, doctype, sys, GetSIFromVers(XH11));
1985
0
            lexer->versionEmitted = XH11;
1986
0
            return yes;
1987
0
        }
1988
0
        else if (lexer->versions & XH11 && !(lexer->versions & VERS_HTML40))
1989
0
        {
1990
0
            TY_(RepairAttrValue)(doc, doctype, pub, GetFPIFromVers(XH11));
1991
0
            TY_(RepairAttrValue)(doc, doctype, sys, GetSIFromVers(XH11));
1992
0
            lexer->versionEmitted = XH11;
1993
0
        }
1994
0
        else if (lexer->versions & XB10 && lexer->doctype == XB10)
1995
0
        {
1996
0
            if (!TY_(GetAttrByName)(doctype, sys))
1997
0
                TY_(RepairAttrValue)(doc, doctype, sys, GetSIFromVers(XB10));
1998
0
            lexer->versionEmitted = XB10;
1999
0
            return yes;
2000
0
        }
2001
0
        else if (lexer->versions & VERS_HTML40_STRICT)
2002
0
        {
2003
0
            TY_(RepairAttrValue)(doc, doctype, pub, GetFPIFromVers(X10S));
2004
0
            TY_(RepairAttrValue)(doc, doctype, sys, GetSIFromVers(X10S));
2005
0
            lexer->versionEmitted = X10S;
2006
0
        }
2007
0
        else if (lexer->versions & VERS_FRAMESET)
2008
0
        {
2009
0
            TY_(RepairAttrValue)(doc, doctype, pub, GetFPIFromVers(X10F));
2010
0
            TY_(RepairAttrValue)(doc, doctype, sys, GetSIFromVers(X10F));
2011
0
            lexer->versionEmitted = X10F;
2012
0
        }
2013
0
        else if (lexer->versions & VERS_LOOSE)
2014
0
        {
2015
0
            TY_(RepairAttrValue)(doc, doctype, pub, GetFPIFromVers(X10T));
2016
0
            TY_(RepairAttrValue)(doc, doctype, sys, GetSIFromVers(X10T));
2017
0
            lexer->versionEmitted = X10T;
2018
0
        }
2019
0
        else if (lexer->versions & VERS_HTML5)
2020
0
        {
2021
            /*\
2022
             *  Issue #273 - If still a html5/xhtml5 bit
2023
             *  existing, that is the 'ConstrainVersion' has
2024
             *  not eliminated all HTML5, then nothing to do here.
2025
             *  Certainly do **not** delete the DocType node!
2026
             *  see: http://www.w3.org/QA/Tips/Doctype
2027
            \*/
2028
0
        }
2029
0
        else
2030
0
        {
2031
0
            if (doctype)
2032
0
                TY_(DiscardElement)(doc, doctype);
2033
0
            return no;
2034
0
        }
2035
0
        break;
2036
0
    case TidyDoctypeOmit:
2037
0
        assert(0);
2038
0
        break;
2039
0
    }
2040
2041
0
    return no;
2042
0
}
2043
2044
/* fixup doctype if missing */
2045
Bool TY_(FixDocType)( TidyDocImpl* doc )
2046
0
{
2047
0
    Lexer* lexer = doc->lexer;
2048
0
    Node* doctype = TY_(FindDocType)( doc );
2049
0
    uint dtmode = cfg( doc, TidyDoctypeMode );
2050
0
    uint guessed = VERS_UNKNOWN;
2051
0
    Bool hadSI = no;
2052
2053
    /* Issue #167 - found doctype, and doctype is default VERS_HTML5, set VERS_HTML5 and return yes */
2054
0
    if (doctype && (dtmode == TidyDoctypeAuto) &&
2055
0
        (lexer->doctype == VERS_HTML5) )
2056
0
    {
2057
        /* The version emitted cannot be a composite value! */
2058
0
        lexer->versionEmitted = HT50;
2059
0
        return yes;
2060
0
    }
2061
0
    if (dtmode == TidyDoctypeAuto &&
2062
0
        lexer->versions & lexer->doctype &&
2063
0
        !(VERS_XHTML & lexer->doctype && !lexer->isvoyager)
2064
0
        && TY_(FindDocType)(doc))
2065
0
    {
2066
0
        lexer->versionEmitted = lexer->doctype;
2067
0
        return yes;
2068
0
    }
2069
2070
0
    if (dtmode == TidyDoctypeOmit)
2071
0
    {
2072
0
        if (doctype)
2073
0
            TY_(DiscardElement)( doc, doctype );
2074
0
        lexer->versionEmitted = TY_(ApparentVersion)( doc );
2075
0
        return yes;
2076
0
    }
2077
2078
0
    if (cfgBool(doc, TidyXmlOut))
2079
0
        return yes;
2080
2081
0
    if (doctype)
2082
0
        hadSI = TY_(GetAttrByName)(doctype, "SYSTEM") != NULL;
2083
2084
0
    if ((dtmode == TidyDoctypeStrict ||
2085
0
         dtmode == TidyDoctypeLoose) && doctype)
2086
0
    {
2087
0
        TY_(DiscardElement)(doc, doctype);
2088
0
        doctype = NULL;
2089
0
    }
2090
2091
0
    switch (dtmode)
2092
0
    {
2093
0
    case TidyDoctypeHtml5:
2094
0
        guessed = HT50;
2095
0
        break;
2096
0
    case TidyDoctypeStrict:
2097
0
        guessed = H41S;
2098
0
        break;
2099
0
    case TidyDoctypeLoose:
2100
0
        guessed = H41T;
2101
0
        break;
2102
0
    case TidyDoctypeAuto:
2103
0
        guessed = TY_(HTMLVersion)(doc);
2104
0
        break;
2105
0
    }
2106
2107
0
    lexer->versionEmitted = guessed;
2108
0
    if (guessed == VERS_UNKNOWN)
2109
0
        return no;
2110
2111
0
    if (doctype)
2112
0
    {
2113
0
        doctype->element = TY_(tmbstrtolower)(doctype->element);
2114
0
    }
2115
0
    else
2116
0
    {
2117
0
        doctype = NewDocTypeNode(doc);
2118
0
        doctype->element = TY_(tmbstrdup)(doc->allocator, "html");
2119
0
    }
2120
2121
0
    TY_(RepairAttrValue)(doc, doctype, "PUBLIC", GetFPIFromVers(guessed));
2122
2123
0
    if (hadSI)
2124
0
        TY_(RepairAttrValue)(doc, doctype, "SYSTEM", GetSIFromVers(guessed));
2125
2126
0
    return yes;
2127
0
}
2128
2129
/* ensure XML document starts with <?xml version="1.0"?> */
2130
/* add encoding attribute if not using ASCII or UTF-8 output */
2131
Bool TY_(FixXmlDecl)( TidyDocImpl* doc )
2132
0
{
2133
0
    Node* xml;
2134
0
    AttVal *version, *encoding;
2135
0
    Lexer*lexer = doc->lexer;
2136
0
    Node* root = &doc->root;
2137
2138
0
    if ( root->content && root->content->type == XmlDecl )
2139
0
    {
2140
0
        xml = root->content;
2141
0
    }
2142
0
    else
2143
0
    {
2144
0
        xml = TY_(NewNode)(lexer->allocator, lexer);
2145
0
        xml->type = XmlDecl;
2146
0
        if ( root->content )
2147
0
            TY_(InsertNodeBeforeElement)(root->content, xml);
2148
0
        else
2149
0
            root->content = xml;
2150
0
    }
2151
2152
0
    version = TY_(GetAttrByName)(xml, "version");
2153
0
    encoding = TY_(GetAttrByName)(xml, "encoding");
2154
2155
    /*
2156
      We need to insert a check if declared encoding 
2157
      and output encoding mismatch and fix the XML
2158
      declaration accordingly!!!
2159
    */
2160
2161
0
    if ( encoding == NULL && cfg(doc, TidyOutCharEncoding) != UTF8 )
2162
0
    {
2163
0
        ctmbstr enc = TY_(GetEncodingNameFromTidyId)(cfg(doc, TidyOutCharEncoding));
2164
0
        if ( enc )
2165
0
            TY_(AddAttribute)( doc, xml, "encoding", enc );
2166
0
    }
2167
2168
0
    if ( version == NULL )
2169
0
        TY_(AddAttribute)( doc, xml, "version", "1.0" );
2170
0
    return yes;
2171
0
}
2172
2173
Node* TY_(InferredTag)(TidyDocImpl* doc, TidyTagId id)
2174
0
{
2175
0
    Lexer *lexer = doc->lexer;
2176
0
    Node *node = TY_(NewNode)( lexer->allocator, lexer );
2177
0
    const Dict* dict = TY_(LookupTagDef)(id);
2178
2179
0
    assert( dict != NULL );
2180
2181
0
    node->type = StartTag;
2182
0
    node->implicit = yes;
2183
0
    node->element = TY_(tmbstrdup)(doc->allocator, dict->name);
2184
0
    node->tag = dict;
2185
0
    node->start = lexer->txtstart;
2186
0
    node->end = lexer->txtend;
2187
2188
0
    return node;
2189
0
}
2190
2191
static Bool ExpectsContent(Node *node)
2192
0
{
2193
0
    if (node->type != StartTag)
2194
0
        return no;
2195
2196
    /* unknown element? */
2197
0
    if (node->tag == NULL)
2198
0
        return yes;
2199
2200
0
    if (node->tag->model & CM_EMPTY)
2201
0
        return no;
2202
2203
0
    return yes;
2204
0
}
2205
2206
/*
2207
  create a text node for the contents of
2208
  a CDATA element like style or script
2209
  which ends with </foo> for some foo.
2210
*/
2211
2212
typedef enum
2213
{
2214
    CDATA_INTERMEDIATE,
2215
    CDATA_STARTTAG,
2216
    CDATA_ENDTAG
2217
} CDATAState;
2218
2219
static Node *GetCDATA( TidyDocImpl* doc, Node *container )
2220
0
{
2221
0
    Lexer* lexer = doc->lexer;
2222
0
    uint start = 0;
2223
0
    int nested = 0;
2224
0
    CDATAState state = CDATA_INTERMEDIATE;
2225
0
    uint i;
2226
0
    Bool isEmpty = yes;
2227
0
    Bool matches = no;
2228
0
    uint c;
2229
0
    Bool hasSrc = (TY_(AttrGetById)(container, TidyAttr_SRC) != NULL) ? yes : no;
2230
    /*\ Issue #65 (1642186) and #280 - is script or style, and the option on
2231
     *  If yes, then avoid incrementing nested...
2232
    \*/
2233
0
    Bool nonested = ((nodeIsSCRIPT(container) || (nodeIsSTYLE(container))) && 
2234
0
        cfgBool(doc, TidySkipNested)) ? yes : no;
2235
2236
0
    SetLexerLocus( doc, lexer );
2237
0
    lexer->waswhite = no;
2238
0
    lexer->txtstart = lexer->txtend = lexer->lexsize;
2239
2240
    /* seen start tag, look for matching end tag */
2241
0
    while ((c = TY_(ReadChar)(doc->docIn)) != EndOfStream)
2242
0
    {
2243
0
        TY_(AddCharToLexer)(lexer, c);
2244
0
        lexer->txtend = lexer->lexsize;
2245
2246
0
        if (state == CDATA_INTERMEDIATE)
2247
0
        {
2248
0
            if (c != '<')
2249
0
            {
2250
0
                if (isEmpty && !TY_(IsWhite)(c))
2251
0
                    isEmpty = no;
2252
0
                continue;
2253
0
            }
2254
2255
0
            c = TY_(ReadChar)(doc->docIn);
2256
2257
0
            if (TY_(IsLetter)(c))
2258
0
            {
2259
                /* <head><script src=foo><meta name=foo content=bar>*/
2260
0
                if (hasSrc && isEmpty && nodeIsSCRIPT(container))
2261
0
                {
2262
                    /* ReportError(doc, container, NULL, MISSING_ENDTAG_FOR); */
2263
0
                    lexer->lexsize = lexer->txtstart;
2264
0
                    TY_(UngetChar)(c, doc->docIn);
2265
0
                    TY_(UngetChar)('<', doc->docIn);
2266
0
                    return NULL;
2267
0
                }
2268
0
                TY_(AddCharToLexer)(lexer, c);
2269
0
                start = lexer->lexsize - 1;
2270
0
                state = CDATA_STARTTAG;
2271
0
            }
2272
0
            else if (c == '/')
2273
0
            {
2274
0
                TY_(AddCharToLexer)(lexer, c);
2275
2276
0
                c = TY_(ReadChar)(doc->docIn);
2277
                
2278
0
                if (!TY_(IsLetter)(c))
2279
0
                {
2280
0
                    TY_(UngetChar)(c, doc->docIn);
2281
0
                    continue;
2282
0
                }
2283
0
                TY_(UngetChar)(c, doc->docIn);
2284
2285
0
                start = lexer->lexsize;
2286
0
                state = CDATA_ENDTAG;
2287
0
            }
2288
0
            else if (c == '\\')
2289
0
            {
2290
                /* recognize document.write("<script><\/script>") */
2291
0
                TY_(AddCharToLexer)(lexer, c);
2292
2293
0
                c = TY_(ReadChar)(doc->docIn);
2294
2295
0
                if (c != '/')
2296
0
                {
2297
0
                    TY_(UngetChar)(c, doc->docIn);
2298
0
                    continue;
2299
0
                }
2300
2301
0
                TY_(AddCharToLexer)(lexer, c);
2302
2303
0
                if (nonested) {
2304
                    /*\ 
2305
                     *  Issue #65 - for version 5.1.14.EXP2
2306
                     *  If the nonested option is ON then the <script> 
2307
                     *  tag did not bump nested, so no need to treat this as 
2308
                     *  an end tag just to decrease nested, just continue!
2309
                    \*/
2310
0
                    continue;
2311
0
                }
2312
2313
0
                c = TY_(ReadChar)(doc->docIn);
2314
                
2315
0
                if (!TY_(IsLetter)(c))
2316
0
                {
2317
0
                    TY_(UngetChar)(c, doc->docIn);
2318
0
                    continue;
2319
0
                }
2320
0
                TY_(UngetChar)(c, doc->docIn);
2321
2322
0
                start = lexer->lexsize;
2323
0
                state = CDATA_ENDTAG;
2324
0
            }
2325
0
            else
2326
0
            {
2327
0
                TY_(UngetChar)(c, doc->docIn);
2328
0
            }
2329
0
        }
2330
        /* '<' + Letter found */
2331
0
        else if (state == CDATA_STARTTAG)
2332
0
        {
2333
0
            if (TY_(IsLetter)(c))
2334
0
                continue;
2335
2336
0
            matches = TY_(tmbstrncasecmp)(container->element, lexer->lexbuf + start,
2337
0
                                          TY_(tmbstrlen)(container->element)) == 0;
2338
0
            if (matches && !nonested)
2339
0
                nested++;
2340
2341
0
            state = CDATA_INTERMEDIATE;
2342
0
        }
2343
        /* '<' + '/' + Letter found */
2344
0
        else if (state == CDATA_ENDTAG)
2345
0
        {
2346
0
            if (TY_(IsLetter)(c))
2347
0
                continue;
2348
2349
0
            matches = TY_(tmbstrncasecmp)(container->element, lexer->lexbuf + start,
2350
0
                                          TY_(tmbstrlen)(container->element)) == 0;
2351
2352
0
            if (isEmpty && !matches)
2353
0
            {
2354
                /* ReportError(doc, container, NULL, MISSING_ENDTAG_FOR); */
2355
2356
0
                for (i = lexer->lexsize - 1; i >= start; --i)
2357
0
                    TY_(UngetChar)((uint)lexer->lexbuf[i], doc->docIn);
2358
0
                TY_(UngetChar)('/', doc->docIn);
2359
0
                TY_(UngetChar)('<', doc->docIn);
2360
0
                break;
2361
0
            }
2362
2363
0
            if (matches && nested-- <= 0)
2364
0
            {
2365
0
                for (i = lexer->lexsize - 1; i >= start; --i)
2366
0
                    TY_(UngetChar)((uint)lexer->lexbuf[i], doc->docIn);
2367
0
                TY_(UngetChar)('/', doc->docIn);
2368
0
                TY_(UngetChar)('<', doc->docIn);
2369
0
                lexer->lexsize -= (lexer->lexsize - start) + 2;
2370
0
                break;
2371
0
            }
2372
0
            else if (lexer->lexbuf[start - 2] != '\\')
2373
0
            {
2374
                /* if the end tag is not already escaped using backslash */
2375
0
                SetLexerLocus( doc, lexer );
2376
0
                lexer->columns -= 3;
2377
2378
                /*\ if javascript insert backslash before / 
2379
                 *  Issue #348 - Add option, escape-scripts, to skip
2380
                \*/
2381
0
                if ((TY_(IsJavaScript)(container)) && cfgBool(doc, TidyEscapeScripts) &&
2382
0
                    !TY_(IsHTML5Mode)(doc) )    /* Is #700 - This only applies to legacy html4 mode */
2383
0
                {
2384
                    /* Issue #281 - only warn if adding the escape! */
2385
0
                    TY_(Report)(doc, NULL, NULL, BAD_CDATA_CONTENT);
2386
2387
0
                    for (i = lexer->lexsize; i > start-1; --i)
2388
0
                        lexer->lexbuf[i] = lexer->lexbuf[i-1];
2389
2390
0
                    lexer->lexbuf[start-1] = '\\';
2391
0
                    lexer->lexsize++;
2392
0
                }
2393
0
            }
2394
0
            state = CDATA_INTERMEDIATE;
2395
0
        }
2396
0
    }
2397
0
    if (isEmpty)
2398
0
        lexer->lexsize = lexer->txtstart = lexer->txtend;
2399
0
    else
2400
0
        lexer->txtend = lexer->lexsize;
2401
2402
0
    if (c == EndOfStream)
2403
0
        TY_(Report)(doc, container, NULL, MISSING_ENDTAG_FOR );
2404
2405
0
    return TY_(TextToken)(lexer);
2406
0
}
2407
2408
void TY_(UngetToken)( TidyDocImpl* doc )
2409
0
{
2410
0
    doc->lexer->pushed = yes;
2411
0
}
2412
2413
#if defined(ENABLE_DEBUG_LOG)
2414
#  define CondReturnTextNode(doc, skip) \
2415
            if (lexer->txtend > lexer->txtstart) { \
2416
                Node *_node = TY_(TextToken)(lexer); \
2417
                lexer->token = _node; \
2418
                GTDBG(doc,"text_node",_node); \
2419
                return _node; \
2420
            }
2421
2422
#else
2423
#  define CondReturnTextNode(doc, skip) \
2424
0
            if (lexer->txtend > lexer->txtstart) \
2425
0
            { \
2426
0
                lexer->token = TY_(TextToken)(lexer); \
2427
0
                return lexer->token; \
2428
0
            }
2429
#endif
2430
2431
/*
2432
  modes for GetToken()
2433
2434
  MixedContent   -- for elements which don't accept PCDATA
2435
  Preformatted   -- white space preserved as is
2436
  IgnoreMarkup   -- for CDATA elements such as script, style
2437
*/
2438
static Node* GetTokenFromStream( TidyDocImpl* doc, GetTokenMode mode );
2439
2440
Node* TY_(GetToken)( TidyDocImpl* doc, GetTokenMode mode )
2441
0
{
2442
0
    Node *node;
2443
0
    Lexer* lexer = doc->lexer;
2444
2445
0
    if (lexer->pushed || lexer->itoken)
2446
0
    {
2447
        /* Deal with previously returned duplicate inline token */
2448
0
        if (lexer->itoken)
2449
0
        {
2450
            /* itoken rejected */
2451
0
            if (lexer->pushed)
2452
0
            {
2453
0
                lexer->pushed = no;
2454
0
                node = lexer->itoken;
2455
0
                GTDBG(doc,"lex-itoken", node);
2456
0
                return node;
2457
0
            }
2458
            /* itoken has been accepted */
2459
0
            lexer->itoken = NULL;
2460
0
        }
2461
            
2462
        /* duplicate inlines in preference to pushed text nodes when appropriate */
2463
0
        lexer->pushed = no;
2464
0
        if (lexer->token->type != TextNode
2465
0
            || !(lexer->insert || lexer->inode)) {
2466
0
            node = lexer->token;
2467
0
            GTDBG(doc,"lex-token", node);
2468
0
            return node;
2469
0
        }
2470
0
        lexer->itoken = TY_(InsertedToken)( doc );
2471
0
        node = lexer->itoken;
2472
0
        GTDBG(doc,"lex-inserted", node);
2473
0
        return node;
2474
0
    }
2475
2476
0
    assert( !(lexer->pushed || lexer->itoken) );
2477
2478
    /* at start of block elements, unclosed inline
2479
       elements are inserted into the token stream 
2480
       Issue #341 - Can NOT insert a token if NO istacksize  
2481
     */
2482
0
    if ((lexer->insert || lexer->inode) && lexer->istacksize)
2483
0
    {
2484
        /*\ Issue #92: could fix by the following, but instead chose not to stack these 2
2485
         *  if ( !(lexer->insert && (nodeIsINS(lexer->insert) || nodeIsDEL(lexer->insert))) ) {
2486
        \*/
2487
0
        lexer->token = TY_(InsertedToken)( doc );
2488
0
        node = lexer->token;
2489
0
        GTDBG(doc,"lex-inserted2", node);
2490
0
        return node;
2491
0
    }
2492
2493
0
    if (mode == CdataContent)
2494
0
    {
2495
0
        assert( lexer->parent != NULL );
2496
0
        node = GetCDATA(doc, lexer->parent);
2497
0
        GTDBG(doc,"lex-cdata", node);
2498
0
        return node;
2499
0
    }
2500
2501
0
    return GetTokenFromStream( doc, mode );
2502
0
}
2503
2504
#if defined(ENABLE_DEBUG_LOG)
2505
static void check_me(char *name)
2506
{
2507
    SPRTF("Have node %s\n", name);
2508
}
2509
#endif
2510
2511
static Node* GetTokenFromStream( TidyDocImpl* doc, GetTokenMode mode )
2512
0
{
2513
0
    Lexer* lexer = doc->lexer;
2514
0
    uint c, lexdump, badcomment = 0;
2515
0
    Bool isempty = no;
2516
0
    AttVal *attributes = NULL;
2517
0
    Node *node;
2518
0
    Bool fixComments;
2519
    
2520
0
    switch ( cfgAutoBool(doc, TidyFixComments) )
2521
0
    {
2522
0
        case TidyYesState:
2523
0
            fixComments = yes;
2524
0
            break;
2525
2526
0
        case TidyNoState:
2527
0
            fixComments = no;
2528
0
            break;
2529
2530
0
        default:
2531
0
            fixComments = (TY_(HTMLVersion)(doc) & HT50) == 0;
2532
0
            break;
2533
0
    }
2534
2535
    /* Lexer->token must be set on return. Nullify it for safety. */
2536
0
    lexer->token = NULL;
2537
2538
0
    SetLexerLocus( doc, lexer );
2539
0
    lexer->waswhite = no;
2540
2541
0
    lexer->txtstart = lexer->txtend = lexer->lexsize;
2542
2543
0
    while ((c = TY_(ReadChar)(doc->docIn)) != EndOfStream)
2544
0
    {
2545
0
        if (lexer->insertspace)
2546
0
        {
2547
0
            TY_(AddCharToLexer)(lexer, ' ');
2548
0
            lexer->waswhite = yes;
2549
0
            lexer->insertspace = no;
2550
0
        }
2551
2552
0
        if (c == 160 && (mode == Preformatted))
2553
0
            c = ' ';
2554
2555
0
        TY_(AddCharToLexer)(lexer, c);
2556
2557
0
        switch (lexer->state)
2558
0
        {
2559
0
            case LEX_CONTENT:  /* element content */
2560
2561
                /*
2562
                 Discard white space if appropriate. Its cheaper
2563
                 to do this here rather than in parser methods
2564
                 for elements that don't have mixed content.
2565
                */
2566
0
                if (TY_(IsWhite)(c) && (mode == IgnoreWhitespace) 
2567
0
                      && lexer->lexsize == lexer->txtstart + 1)
2568
0
                {
2569
0
                    --(lexer->lexsize);
2570
0
                    lexer->waswhite = no;
2571
0
                    SetLexerLocus( doc, lexer );
2572
0
                    continue;
2573
0
                }
2574
2575
0
                if (c == '<')
2576
0
                {
2577
0
                    lexer->state = LEX_GT;
2578
0
                    continue;
2579
0
                }
2580
2581
0
                if (TY_(IsWhite)(c))
2582
0
                {
2583
                    /* was previous character white? */
2584
0
                    if (lexer->waswhite)
2585
0
                    {
2586
0
                        if (mode != Preformatted && mode != IgnoreMarkup)
2587
0
                        {
2588
0
                            --(lexer->lexsize);
2589
0
                            SetLexerLocus( doc, lexer );
2590
0
                        }
2591
0
                    }
2592
0
                    else /* prev character wasn't white */
2593
0
                    {
2594
0
                        lexer->waswhite = yes;
2595
2596
0
                        if (mode != Preformatted && mode != IgnoreMarkup && c != ' ')
2597
0
                            ChangeChar(lexer, ' ');
2598
0
                    }
2599
2600
0
                    continue;
2601
0
                }
2602
0
                else if (c == '&' && mode != IgnoreMarkup)
2603
0
                    ParseEntity( doc, mode );
2604
2605
                /* this is needed to avoid trimming trailing whitespace */
2606
0
                if (mode == IgnoreWhitespace)
2607
0
                    mode = MixedContent;
2608
2609
0
                lexer->waswhite = no;
2610
0
                continue;
2611
2612
0
            case LEX_GT:  /* < */
2613
2614
                /* check for endtag */
2615
0
                if (c == '/')
2616
0
                {
2617
0
                    if ((c = TY_(ReadChar)(doc->docIn)) == EndOfStream)
2618
0
                    {
2619
0
                        TY_(UngetChar)(c, doc->docIn);
2620
0
                        continue;
2621
0
                    }
2622
2623
0
                    TY_(AddCharToLexer)(lexer, c);
2624
2625
0
                    if (TY_(IsLetter)(c) || (cfgBool(doc, TidyXmlTags) && TY_(IsXMLNamechar)(c)))
2626
0
                    {
2627
0
                        lexer->lexsize -= 3;
2628
0
                        lexer->txtend = lexer->lexsize;
2629
0
                        TY_(UngetChar)(c, doc->docIn);
2630
0
                        lexer->state = LEX_ENDTAG;
2631
0
                        lexer->lexbuf[lexer->lexsize] = '\0';  /* debug */
2632
0
                        doc->docIn->curcol -= 2;
2633
2634
                        /* if some text before the </ return it now */
2635
0
                        if (lexer->txtend > lexer->txtstart)
2636
0
                        {
2637
                            /* trim space character before end tag */
2638
0
                            if (mode == IgnoreWhitespace && lexer->lexbuf[lexer->lexsize - 1] == ' ')
2639
0
                            {
2640
0
                                lexer->lexsize -= 1;
2641
0
                                lexer->txtend = lexer->lexsize;
2642
0
                            }
2643
0
                            lexer->token = TY_(TextToken)(lexer);
2644
0
                            node = lexer->token;
2645
0
                            GTDBG(doc,"text", node);
2646
0
                            return node;
2647
0
                        }
2648
2649
0
                        continue;       /* no text so keep going */
2650
0
                    }
2651
2652
                    /* otherwise treat as CDATA */
2653
0
                    lexer->waswhite = no;
2654
0
                    lexer->state = LEX_CONTENT;
2655
0
                    continue;
2656
0
                }
2657
2658
0
                if (mode == IgnoreMarkup)
2659
0
                {
2660
                    /* otherwise treat as CDATA */
2661
0
                    lexer->waswhite = no;
2662
0
                    lexer->state = LEX_CONTENT;
2663
0
                    continue;
2664
0
                }
2665
2666
                /*
2667
                   look out for comments, doctype or marked sections
2668
                   this isn't quite right, but its getting there ...
2669
                */
2670
0
                if (c == '!')
2671
0
                {
2672
0
                    c = TY_(ReadChar)(doc->docIn);
2673
2674
0
                    if (c == '-')
2675
0
                    {
2676
0
                        c = TY_(ReadChar)(doc->docIn);
2677
2678
0
                        if (c == '-')
2679
0
                        {
2680
0
                            lexer->state = LEX_COMMENT;  /* comment */
2681
0
                            lexer->lexsize -= 2;
2682
0
                            lexer->txtend = lexer->lexsize;
2683
2684
0
                            CondReturnTextNode(doc, 4)
2685
2686
0
                            lexer->txtstart = lexer->lexsize;
2687
0
                            continue;
2688
0
                        }
2689
2690
                        /*
2691
                           TY_(Report)(doc, NULL, NULL, MALFORMED_COMMENT_DROPPING );
2692
                           Warning now done later - see issue #487
2693
                         */
2694
0
                    }
2695
0
                    else if (c == 'd' || c == 'D')
2696
0
                    {
2697
                        /* todo: check for complete "<!DOCTYPE" not just <!D */
2698
2699
0
                        uint skip = 0;
2700
2701
0
                        lexer->state = LEX_DOCTYPE; /* doctype */
2702
0
                        lexer->lexsize -= 2;
2703
0
                        lexer->txtend = lexer->lexsize;
2704
0
                        mode = IgnoreWhitespace;
2705
2706
                        /* skip until white space or '>' */
2707
2708
0
                        for (;;)
2709
0
                        {
2710
0
                            c = TY_(ReadChar)(doc->docIn);
2711
0
                            ++skip;
2712
2713
0
                            if (c == EndOfStream || c == '>')
2714
0
                            {
2715
0
                                TY_(UngetChar)(c, doc->docIn);
2716
0
                                break;
2717
0
                            }
2718
2719
2720
0
                            if (!TY_(IsWhite)(c))
2721
0
                                continue;
2722
2723
                            /* and skip to end of whitespace */
2724
2725
0
                            for (;;)
2726
0
                            {
2727
0
                                c = TY_(ReadChar)(doc->docIn);
2728
0
                                ++skip;
2729
2730
0
                                if (c == EndOfStream || c == '>')
2731
0
                                {
2732
0
                                    TY_(UngetChar)(c, doc->docIn);
2733
0
                                    break;
2734
0
                                }
2735
2736
2737
0
                                if (TY_(IsWhite)(c))
2738
0
                                    continue;
2739
2740
0
                                TY_(UngetChar)(c, doc->docIn);
2741
0
                                break;
2742
0
                            }
2743
2744
0
                            break;
2745
0
                        }
2746
2747
0
                        CondReturnTextNode(doc, (skip + 3))
2748
2749
0
                        lexer->txtstart = lexer->lexsize;
2750
0
                        continue;
2751
0
                    }
2752
0
                    else if (c == '[')
2753
0
                    {
2754
                        /* Word 2000 embeds <![if ...]> ... <![endif]> sequences */
2755
0
                        lexer->lexsize -= 2;
2756
0
                        lexer->state = LEX_SECTION;
2757
0
                        lexer->txtend = lexer->lexsize;
2758
2759
0
                        CondReturnTextNode(doc, 2)
2760
2761
0
                        lexer->txtstart = lexer->lexsize;
2762
0
                        continue;
2763
0
                    }
2764
2765
2766
                    /*
2767
                       We only print this message if there's a missing
2768
                       starting hyphen; this comment will be dropped.
2769
                     */
2770
0
                    TY_(Report)(doc, NULL, NULL, MALFORMED_COMMENT_DROPPING ); /* Is. #487 */
2771
2772
                    /* else swallow characters up to and including next '>' */
2773
0
                    while ((c = TY_(ReadChar)(doc->docIn)) != '>')
2774
0
                    {
2775
0
                        if (c == EndOfStream)
2776
0
                        {
2777
0
                            TY_(UngetChar)(c, doc->docIn);
2778
0
                            break;
2779
0
                        }
2780
0
                    }
2781
2782
0
                    lexer->lexsize -= 2;
2783
0
                    lexer->lexbuf[lexer->lexsize] = '\0';
2784
0
                    lexer->state = LEX_CONTENT;
2785
0
                    continue;
2786
0
                }
2787
2788
                /*
2789
                   processing instructions
2790
                */
2791
2792
0
                if (c == '?')
2793
0
                {
2794
0
                    lexer->lexsize -= 2;
2795
0
                    lexer->state = LEX_PROCINSTR;
2796
0
                    lexer->txtend = lexer->lexsize;
2797
2798
0
                    CondReturnTextNode(doc, 2)
2799
2800
0
                    lexer->txtstart = lexer->lexsize;
2801
0
                    continue;
2802
0
                }
2803
2804
                /* Microsoft ASP's e.g. <% ... server-code ... %> */
2805
0
                if (c == '%')
2806
0
                {
2807
0
                    lexer->lexsize -= 2;
2808
0
                    lexer->state = LEX_ASP;
2809
0
                    lexer->txtend = lexer->lexsize;
2810
2811
0
                    CondReturnTextNode(doc, 2)
2812
2813
0
                    lexer->txtstart = lexer->lexsize;
2814
0
                    continue;
2815
0
                }
2816
2817
                /* Netscapes JSTE e.g. <# ... server-code ... #> */
2818
0
                if (c == '#')
2819
0
                {
2820
0
                    lexer->lexsize -= 2;
2821
0
                    lexer->state = LEX_JSTE;
2822
0
                    lexer->txtend = lexer->lexsize;
2823
2824
0
                    CondReturnTextNode(doc, 2)
2825
2826
0
                    lexer->txtstart = lexer->lexsize;
2827
0
                    continue;
2828
0
                }
2829
2830
                /* check for start tag */
2831
0
                if (TY_(IsLetter)(c) || (cfgBool(doc, TidyXmlTags) && TY_(IsXMLNamechar)(c)))
2832
0
                {
2833
0
                    TY_(UngetChar)(c, doc->docIn);     /* push back letter */
2834
0
                    TY_(UngetChar)('<', doc->docIn);
2835
0
                    lexer->lexsize -= 2;      /* discard "<" + letter */
2836
0
                    lexer->txtend = lexer->lexsize;
2837
0
                    lexer->state = LEX_STARTTAG;         /* ready to read tag name */
2838
2839
0
                    CondReturnTextNode(doc, 2)
2840
2841
                    /* lexer->txtstart = lexer->lexsize; missing here? */
2842
0
                    continue;       /* no text so keep going */
2843
0
                }
2844
2845
                /* otherwise treat as CDATA */
2846
                /* fix for bug 762102 (486) */
2847
                /* Issue #384 - Fix skipping parsing character, particularly '<<' */
2848
0
                TY_(UngetChar)(c, doc->docIn);
2849
0
                lexer->lexsize -= 1;
2850
0
                lexer->state = LEX_CONTENT;
2851
0
                lexer->waswhite = no;
2852
0
                continue;
2853
2854
0
            case LEX_ENDTAG:  /* </letter */
2855
0
                lexer->txtstart = lexer->lexsize - 1;
2856
0
                doc->docIn->curcol += 2;
2857
0
                c = ParseTagName( doc );
2858
0
                lexer->token = TagToken( doc, EndTag );  /* create endtag token */
2859
0
                lexer->lexsize = lexer->txtend = lexer->txtstart;
2860
2861
                /* skip to '>' */
2862
0
                while ( c != '>' && c != EndOfStream )
2863
0
                {
2864
0
                    c = TY_(ReadChar)(doc->docIn);
2865
0
                }
2866
2867
0
                if (c == EndOfStream)
2868
0
                {
2869
0
                    TY_(FreeNode)( doc, lexer->token );
2870
0
                    continue;
2871
0
                }
2872
2873
0
                lexer->state = LEX_CONTENT;
2874
0
                lexer->waswhite = no;
2875
0
                node = lexer->token;
2876
0
                GTDBG(doc,"endtag", node);
2877
0
                return node;  /* the endtag token */
2878
2879
0
            case LEX_STARTTAG: /* first letter of tagname */
2880
0
                c = TY_(ReadChar)(doc->docIn);
2881
0
                ChangeChar(lexer, (tmbchar)c);
2882
0
                lexer->txtstart = lexer->lexsize - 1; /* set txtstart to first letter */
2883
0
                c = ParseTagName( doc );
2884
0
                isempty = no;
2885
0
                attributes = NULL;
2886
0
                lexer->token = TagToken( doc, StartTag ); /* [i_a]2 'isempty' is always false, thanks to code 2 lines above */
2887
2888
                /* parse attributes, consuming closing ">" */
2889
0
                if (c != '>')
2890
0
                {
2891
0
                    if (c == '/')
2892
0
                        TY_(UngetChar)(c, doc->docIn);
2893
2894
0
                    attributes = ParseAttrs( doc, &isempty );
2895
0
                }
2896
2897
0
                if (isempty)
2898
0
                    lexer->token->type = StartEndTag;
2899
2900
0
                lexer->token->attributes = attributes;
2901
0
                lexer->lexsize = lexer->txtend = lexer->txtstart;
2902
2903
                /* swallow newline following start tag */
2904
                /* special check needed for CRLF sequence */
2905
                /* this doesn't apply to empty elements */
2906
                /* nor to preformatted content that needs escaping */
2907
                /*\
2908
                 * Issue #230: Need to KEEP this user newline character in certain 
2909
                 * circumstances, certainly for <pre>, <script>, <style>...
2910
                 * Any others?
2911
                 * Issue #238: maybe **ONLY** for <pre>
2912
                \*/
2913
0
                if ( nodeIsPRE(lexer->token) )
2914
0
                {
2915
0
                    mode = Preformatted;
2916
0
                }
2917
2918
0
                if ((mode != Preformatted && ExpectsContent(lexer->token))
2919
0
                    || nodeIsBR(lexer->token) || nodeIsHR(lexer->token))
2920
0
                {
2921
0
                    c = TY_(ReadChar)(doc->docIn);
2922
2923
0
                    if ((c == '\n') && (mode != IgnoreWhitespace)) /* Issue #329 - Can NOT afford to lose this newline */
2924
0
                        TY_(UngetChar)(c, doc->docIn);  /* Issue #329 - make sure the newline is maintained for now */
2925
0
                    else if (c != '\n' && c != '\f')
2926
0
                        TY_(UngetChar)(c, doc->docIn);
2927
2928
0
                    lexer->waswhite = yes;  /* to swallow leading whitespace */
2929
0
                }
2930
0
                else
2931
0
                    lexer->waswhite = no;
2932
2933
0
                lexer->state = LEX_CONTENT;
2934
0
                if (lexer->token->tag == NULL) 
2935
0
                {
2936
0
                    if (mode != OtherNamespace) /* [i_a]2 only issue warning if NOT 'OtherNamespace', and tag null */
2937
0
                    {
2938
                        /* Special case for HTML5 unknown tags: if it looks 
2939
                           like an autonomous custom tag, then emit a variation
2940
                           of the standard message. We don't want to do this
2941
                           for older HTML, because it's not truly supported
2942
                           by the standard, although Tidy will allow it. */
2943
0
                        if ( (doc->lexer->doctype & VERS_HTML5) > 0 && TY_(elementIsAutonomousCustomFormat)( lexer->token->element ) )
2944
0
                            TY_(Report)( doc, NULL, lexer->token, UNKNOWN_ELEMENT_LOOKS_CUSTOM );
2945
0
                        else
2946
0
                            TY_(Report)( doc, NULL, lexer->token, UNKNOWN_ELEMENT );
2947
0
                    }
2948
0
                }
2949
0
                else if ( !cfgBool(doc, TidyXmlTags) )
2950
0
                {
2951
0
                    TY_(ConstrainVersion)( doc, lexer->token->tag->versions );
2952
0
                    TY_(RepairDuplicateAttributes)( doc, lexer->token, no );
2953
0
                } else 
2954
0
                    TY_(RepairDuplicateAttributes)( doc, lexer->token, yes );
2955
0
                node = lexer->token;
2956
0
                GTDBG(doc,"starttag", node);
2957
0
                return node;  /* return start tag */
2958
2959
0
            case LEX_COMMENT:  /* seen <!-- so look for --> */
2960
2961
0
                if (c != '-')
2962
0
                    continue;
2963
2964
0
                c = TY_(ReadChar)(doc->docIn);
2965
2966
                /* Fix hyphens at beginning of tag */
2967
0
                if ( c != '-' && fixComments && lexer->lexsize - lexer->txtstart == 1 )
2968
0
                {
2969
0
                    lexer->lexbuf[lexer->lexsize - 1] = '=';
2970
0
                }
2971
2972
0
                TY_(AddCharToLexer)(lexer, c);
2973
2974
0
                if (c != '-')
2975
0
                    continue;
2976
2977
0
            end_comment:
2978
0
                c = TY_(ReadChar)(doc->docIn);
2979
2980
0
                if (c == '>')
2981
0
                {
2982
0
                    if (badcomment)
2983
0
                    {
2984
                        /*
2985
                           We've got bad comments that we either fixed or
2986
                           ignored; provide proper user feedback based on
2987
                           doctype and whether or not we fixed them.
2988
                         */
2989
0
                        if ( (TY_(HTMLVersion)(doc) & HT50) )
2990
0
                        {
2991
0
                            if ( fixComments )
2992
0
                                TY_(Report)(doc, NULL, NULL, MALFORMED_COMMENT );
2993
                            /* Otherwise for HTML5, it's safe to ignore. */
2994
0
                        }
2995
0
                        else
2996
0
                        {
2997
0
                            if ( fixComments )
2998
0
                                TY_(Report)(doc, NULL, NULL, MALFORMED_COMMENT );
2999
0
                            else
3000
0
                                TY_(Report)(doc, NULL, NULL, MALFORMED_COMMENT_WARN );
3001
0
                        }
3002
0
                    }
3003
3004
                    /* do not store closing -- in lexbuf */
3005
0
                    lexer->lexsize -= 2;
3006
0
                    lexer->txtend = lexer->lexsize;
3007
0
                    lexer->lexbuf[lexer->lexsize] = '\0';
3008
0
                    lexer->state = LEX_CONTENT;
3009
0
                    lexer->waswhite = no;
3010
0
                    lexer->token = CommentToken(doc);
3011
3012
                    /* now look for a line break */
3013
3014
0
                    c = TY_(ReadChar)(doc->docIn);
3015
3016
0
                    if (c == '\n')
3017
0
                        lexer->token->linebreak = yes;
3018
0
                    else
3019
0
                        TY_(UngetChar)(c, doc->docIn);
3020
3021
0
                    node = lexer->token;
3022
0
                    GTDBG(doc,"comment", node);
3023
0
                    return node;
3024
0
                }
3025
3026
                /* note position of first such error in the comment */
3027
0
                if (!badcomment)
3028
0
                {
3029
0
                    SetLexerLocus( doc, lexer );
3030
0
                    lexer->columns -= 3;
3031
0
                }
3032
3033
0
                badcomment++;
3034
3035
                /* fix hyphens in the middle */
3036
0
                if ( fixComments )
3037
0
                    lexer->lexbuf[lexer->lexsize - 2] = '=';
3038
3039
                /* if '-' then look for '>' to end the comment */
3040
0
                if (c == '-')
3041
0
                {
3042
0
                    TY_(AddCharToLexer)(lexer, c);
3043
0
                    goto end_comment;
3044
0
                }
3045
3046
                /* fix hyphens end, and continue to look for --> */
3047
0
                if ( fixComments )
3048
0
                    lexer->lexbuf[lexer->lexsize - 1] = '=';
3049
3050
                /* http://tidy.sf.net/bug/1266647 */
3051
0
                TY_(AddCharToLexer)(lexer, c);
3052
3053
0
                continue; 
3054
3055
0
            case LEX_DOCTYPE:  /* seen <!d so look for '>' munging whitespace */
3056
3057
                /* use ParseDocTypeDecl() to tokenize doctype declaration */
3058
0
                TY_(UngetChar)(c, doc->docIn);
3059
0
                lexer->lexsize -= 1;
3060
0
                lexer->token = ParseDocTypeDecl(doc);
3061
3062
0
                lexer->txtend = lexer->lexsize;
3063
0
                lexer->lexbuf[lexer->lexsize] = '\0';
3064
0
                lexer->state = LEX_CONTENT;
3065
0
                lexer->waswhite = no;
3066
3067
                /* make a note of the version named by the 1st doctype */
3068
0
                if (lexer->doctype == VERS_UNKNOWN && lexer->token && !cfgBool(doc, TidyXmlTags))
3069
0
                {
3070
0
                    lexer->doctype = FindGivenVersion(doc, lexer->token);
3071
0
                    if (lexer->doctype != VERS_HTML5)
3072
0
                    {
3073
                        /*\
3074
                         *  Back to legacy HTML4 mode for -
3075
                         *  Issue #167 & #169 - TidyTag_A
3076
                         *  Issue #196        - TidyTag_CAPTION
3077
                         *  others?
3078
                        \*/ 
3079
0
                        TY_(AdjustTags)(doc); /* Dynamically modify the tags table  */
3080
0
                    }
3081
0
                }
3082
0
                node = lexer->token;
3083
0
                GTDBG(doc,"doctype", node);
3084
0
                return node;
3085
3086
0
            case LEX_PROCINSTR:  /* seen <? so look for '>' */
3087
                /* check for PHP preprocessor instructions <?php ... ?> */
3088
3089
0
                if  (lexer->lexsize - lexer->txtstart == 3)
3090
0
                {
3091
0
                    if (TY_(tmbstrncmp)(lexer->lexbuf + lexer->txtstart, "php", 3) == 0)
3092
0
                    {
3093
0
                        lexer->state = LEX_PHP;
3094
0
                        continue;
3095
0
                    }
3096
0
                }
3097
3098
0
                if  (lexer->lexsize - lexer->txtstart == 4)
3099
0
                {
3100
0
                    if (TY_(tmbstrncmp)(lexer->lexbuf + lexer->txtstart, "xml", 3) == 0 &&
3101
0
                        TY_(IsWhite)(lexer->lexbuf[lexer->txtstart + 3]))
3102
0
                    {
3103
0
                        lexer->state = LEX_XMLDECL;
3104
0
                        attributes = NULL;
3105
0
                        continue;
3106
0
                    }
3107
0
                }
3108
3109
0
                if (cfgBool(doc, TidyXmlPIs) || lexer->isvoyager) /* insist on ?> as terminator */
3110
0
                {
3111
0
                    if (c != '?')
3112
0
                        continue;
3113
3114
                    /* now look for '>' */
3115
0
                    c = TY_(ReadChar)(doc->docIn);
3116
3117
0
                    if (c == EndOfStream)
3118
0
                    {
3119
0
                        TY_(Report)(doc, NULL, NULL, UNEXPECTED_END_OF_FILE );
3120
0
                        TY_(UngetChar)(c, doc->docIn);
3121
0
                        continue;
3122
0
                    }
3123
3124
0
                    TY_(AddCharToLexer)(lexer, c);
3125
0
                }
3126
3127
3128
0
                if (c != '>')
3129
0
                    continue;
3130
3131
0
                lexer->lexsize -= 1;
3132
3133
0
                if (lexer->lexsize)
3134
0
                {
3135
0
                    uint i;
3136
0
                    Bool closed;
3137
3138
0
                    for (i = 0; i < lexer->lexsize - lexer->txtstart &&
3139
0
                        !TY_(IsWhite)(lexer->lexbuf[i + lexer->txtstart]); ++i)
3140
0
                        /**/;
3141
3142
0
                    closed = lexer->lexbuf[lexer->lexsize - 1] == '?';
3143
3144
0
                    if (closed)
3145
0
                        lexer->lexsize -= 1;
3146
3147
0
                    lexer->txtstart += i;
3148
0
                    lexer->txtend = lexer->lexsize;
3149
0
                    lexer->lexbuf[lexer->lexsize] = '\0';
3150
3151
0
                    lexer->token = PIToken(doc);
3152
0
                    lexer->token->closed = closed;
3153
0
                    lexer->token->element = TY_(tmbstrndup)(doc->allocator,
3154
0
                                                            lexer->lexbuf +
3155
0
                                                            lexer->txtstart - i, i);
3156
0
                }
3157
0
                else
3158
0
                {
3159
0
                    lexer->txtend = lexer->lexsize;
3160
0
                    lexer->lexbuf[lexer->lexsize] = '\0';
3161
0
                    lexer->token = PIToken(doc);
3162
0
                }
3163
3164
0
                lexer->state = LEX_CONTENT;
3165
0
                lexer->waswhite = no;
3166
0
                node = lexer->token;
3167
0
                GTDBG(doc,"procinstr", node);
3168
0
                return node;
3169
3170
0
            case LEX_ASP:  /* seen <% so look for "%>" */
3171
0
                if (c != '%')
3172
0
                    continue;
3173
3174
                /* now look for '>' */
3175
0
                c = TY_(ReadChar)(doc->docIn);
3176
3177
3178
0
                if (c != '>')
3179
0
                {
3180
0
                    TY_(UngetChar)(c, doc->docIn);
3181
0
                    continue;
3182
0
                }
3183
3184
0
                lexer->lexsize -= 1;
3185
0
                lexer->txtend = lexer->lexsize;
3186
0
                lexer->lexbuf[lexer->lexsize] = '\0';
3187
0
                lexer->state = LEX_CONTENT;
3188
0
                lexer->waswhite = no;
3189
0
                lexer->token = AspToken(doc);
3190
0
                node = lexer->token;
3191
0
                GTDBG(doc,"ASP", node);
3192
0
                return node;  /* the endtag token */
3193
3194
3195
3196
0
            case LEX_JSTE:  /* seen <# so look for "#>" */
3197
0
                if (c != '#')
3198
0
                    continue;
3199
3200
                /* now look for '>' */
3201
0
                c = TY_(ReadChar)(doc->docIn);
3202
3203
3204
0
                if (c != '>')
3205
0
                {
3206
0
                    TY_(UngetChar)(c, doc->docIn);
3207
0
                    continue;
3208
0
                }
3209
3210
0
                lexer->lexsize -= 1;
3211
0
                lexer->txtend = lexer->lexsize;
3212
0
                lexer->lexbuf[lexer->lexsize] = '\0';
3213
0
                lexer->state = LEX_CONTENT;
3214
0
                lexer->waswhite = no;
3215
0
                lexer->token = JsteToken(doc);
3216
0
                node = lexer->token;
3217
0
                GTDBG(doc,"JSTE", node);
3218
0
                return node;  /* the JSTE token */
3219
3220
3221
0
            case LEX_PHP: /* seen "<?php" so look for "?>" */
3222
0
                if (c != '?')
3223
0
                    continue;
3224
3225
                /* now look for '>' */
3226
0
                c = TY_(ReadChar)(doc->docIn);
3227
3228
0
                if (c != '>')
3229
0
                {
3230
0
                    TY_(UngetChar)(c, doc->docIn);
3231
0
                    continue;
3232
0
                }
3233
3234
0
                lexer->lexsize -= 1;
3235
0
                lexer->txtend = lexer->lexsize;
3236
0
                lexer->lexbuf[lexer->lexsize] = '\0';
3237
0
                lexer->state = LEX_CONTENT;
3238
0
                lexer->waswhite = no;
3239
0
                lexer->token = PhpToken(doc);
3240
0
                node = lexer->token;
3241
0
                GTDBG(doc,"PHP", node);
3242
0
                return node;  /* the PHP token */
3243
3244
0
            case LEX_XMLDECL: /* seen "<?xml" so look for "?>" */
3245
3246
0
                if (TY_(IsWhite)(c) && c != '?')
3247
0
                    continue;
3248
3249
                /* get pseudo-attribute */
3250
0
                if (c != '?')
3251
0
                {
3252
0
                    tmbstr name;
3253
0
                    Node *asp, *php;
3254
0
                    AttVal *av = NULL;
3255
0
                    int pdelim = 0;
3256
0
                    isempty = no;
3257
3258
0
                    TY_(UngetChar)(c, doc->docIn);
3259
3260
0
                    name = ParseAttribute( doc, &isempty, &asp, &php );
3261
3262
0
                    if (!name)
3263
0
                    {
3264
                        /* check if attributes are created by ASP markup */
3265
0
                        if (asp)
3266
0
                        {
3267
0
                            av = TY_(NewAttribute)(doc);
3268
0
                            av->asp = asp;
3269
0
                            AddAttrToList( &attributes, av ); 
3270
0
                        }
3271
3272
                        /* check if attributes are created by PHP markup */
3273
0
                        if (php)
3274
0
                        {
3275
0
                            av = TY_(NewAttribute)(doc);
3276
0
                            av->php = php;
3277
0
                            AddAttrToList( &attributes, av ); 
3278
0
                        }
3279
                      
3280
                        /* fix for http://tidy.sf.net/bug/788031 */
3281
0
                        lexer->lexsize -= 1;
3282
0
                        lexer->txtend = lexer->txtstart;
3283
0
                        lexer->lexbuf[lexer->txtend] = '\0';
3284
0
                        lexer->state = LEX_CONTENT;
3285
0
                        lexer->waswhite = no;
3286
0
                        lexer->token = XmlDeclToken(doc);
3287
0
                        lexer->token->attributes = attributes;
3288
0
                        node = lexer->token;
3289
0
                        GTDBG(doc,"xml", node);
3290
0
                        return node;  /* the xml token */
3291
0
                    }
3292
3293
0
                    av = TY_(NewAttribute)(doc);
3294
0
                    av->attribute = name;
3295
0
                    av->value = ParseValue( doc, name, yes, &isempty, &pdelim );
3296
0
                    av->delim = pdelim;
3297
0
                    av->dict = TY_(FindAttribute)( doc, av );
3298
3299
0
                    AddAttrToList( &attributes, av );
3300
                    /* continue; */
3301
0
                }
3302
3303
                /* now look for '>' */
3304
0
                c = TY_(ReadChar)(doc->docIn);
3305
3306
0
                if (c != '>')
3307
0
                {
3308
0
                    TY_(UngetChar)(c, doc->docIn);
3309
0
                    continue;
3310
0
                }
3311
0
                lexer->lexsize -= 1;
3312
0
                lexer->txtend = lexer->txtstart;
3313
0
                lexer->lexbuf[lexer->txtend] = '\0';
3314
0
                lexer->state = LEX_CONTENT;
3315
0
                lexer->waswhite = no;
3316
0
                lexer->token = XmlDeclToken(doc);
3317
0
                lexer->token->attributes = attributes;
3318
0
                node = lexer->token;
3319
0
                GTDBG(doc,"XML", node);
3320
0
                return node;  /* the XML token */
3321
3322
0
            case LEX_SECTION: /* seen "<![" so look for "]>" */
3323
0
                if (c == '[')
3324
0
                {
3325
0
                    if (lexer->lexsize == (lexer->txtstart + 6) &&
3326
0
                        TY_(tmbstrncmp)(lexer->lexbuf+lexer->txtstart, "CDATA[", 6) == 0)
3327
0
                    {
3328
0
                        lexer->state = LEX_CDATA;
3329
0
                        lexer->lexsize -= 6;
3330
0
                        continue;
3331
0
                    }
3332
0
                }
3333
3334
0
                if (c == '>')
3335
0
                {
3336
                    /* Is. #462 - reached '>' before ']' */
3337
0
                    TY_(UngetChar)(c, doc->docIn);
3338
0
                } else if (c != ']')
3339
0
                    continue;
3340
3341
                /* now look for '>' */
3342
0
                c = TY_(ReadChar)(doc->docIn);
3343
3344
0
                lexdump = 1;
3345
0
                if (c != '>')
3346
0
                {
3347
                    /* Issue #153 - can also be ]'-->' */
3348
0
                    if (c == '-') 
3349
0
                    {
3350
0
                        c = TY_(ReadChar)(doc->docIn);
3351
0
                        if (c == '-')
3352
0
                        {
3353
0
                            c = TY_(ReadChar)(doc->docIn);
3354
0
                            if (c != '>')
3355
0
                            {
3356
0
                                TY_(UngetChar)(c, doc->docIn);
3357
0
                                TY_(UngetChar)('-', doc->docIn);
3358
0
                                TY_(UngetChar)('-', doc->docIn);
3359
0
                                continue;
3360
0
                            }
3361
                            /* this failed!
3362
                               TY_(AddCharToLexer)(lexer, '-'); TY_(AddCharToLexer)(lexer, '-'); lexdump = 0; 
3363
                               got output <![endif]--]> - needs further fix in pprint section output
3364
                             */
3365
0
                        }
3366
0
                        else
3367
0
                        {
3368
0
                            TY_(UngetChar)(c, doc->docIn);
3369
0
                            TY_(UngetChar)('-', doc->docIn);
3370
0
                            continue;
3371
0
                        }
3372
0
                    } 
3373
0
                    else 
3374
0
                    {
3375
0
                        TY_(UngetChar)(c, doc->docIn);
3376
0
                        continue;
3377
0
                    }
3378
0
                }
3379
 
3380
0
                lexer->lexsize -= lexdump;
3381
0
                lexer->txtend = lexer->lexsize;
3382
0
                lexer->lexbuf[lexer->lexsize] = '\0';
3383
0
                lexer->state = LEX_CONTENT;
3384
0
                lexer->waswhite = no;
3385
0
                lexer->token = SectionToken(doc);
3386
0
                node = lexer->token;
3387
0
                GTDBG(doc,"SECTION", node);
3388
0
                return node;  /* the SECTION token */
3389
3390
0
            case LEX_CDATA: /* seen "<![CDATA[" so look for "]]>" */
3391
0
                if (c != ']')
3392
0
                    continue;
3393
3394
                /* now look for ']' */
3395
0
                c = TY_(ReadChar)(doc->docIn);
3396
3397
0
                if (c != ']')
3398
0
                {
3399
0
                    TY_(UngetChar)(c, doc->docIn);
3400
0
                    continue;
3401
0
                }
3402
3403
                /* now look for '>' */
3404
0
                c = TY_(ReadChar)(doc->docIn);
3405
3406
0
                if (c != '>')
3407
0
                {
3408
0
                    TY_(UngetChar)(c, doc->docIn);
3409
0
                    TY_(UngetChar)(']', doc->docIn);
3410
0
                    continue;
3411
0
                }
3412
3413
0
                lexer->lexsize -= 1;
3414
0
                lexer->txtend = lexer->lexsize;
3415
0
                lexer->lexbuf[lexer->lexsize] = '\0';
3416
0
                lexer->state = LEX_CONTENT;
3417
0
                lexer->waswhite = no;
3418
0
                lexer->token = CDATAToken(doc);
3419
0
                node = lexer->token;
3420
0
                GTDBG(doc,"CDATA", node);
3421
0
                return node;  /* the CDATA token */
3422
0
        }
3423
0
    }
3424
3425
0
    if (lexer->state == LEX_CONTENT)  /* text string */
3426
0
    {
3427
0
        lexer->txtend = lexer->lexsize;
3428
3429
0
        if (lexer->txtend > lexer->txtstart)
3430
0
        {
3431
0
            TY_(UngetChar)(c, doc->docIn);
3432
3433
0
            if (lexer->lexbuf[lexer->lexsize - 1] == ' ')
3434
0
            {
3435
0
                lexer->lexsize -= 1;
3436
0
                lexer->txtend = lexer->lexsize;
3437
0
            }
3438
0
            lexer->token = TY_(TextToken)(lexer);
3439
0
            node = lexer->token;
3440
0
            GTDBG(doc,"textstring", node);
3441
0
            return node;  /* the textstring token */
3442
0
        }
3443
0
    }
3444
0
    else if (lexer->state == LEX_COMMENT) /* comment */
3445
0
    {
3446
0
        if (c == EndOfStream)
3447
0
        {
3448
            /* We print this if we reached end of the stream mid-comment. */
3449
0
            TY_(Report)(doc, NULL, NULL, MALFORMED_COMMENT_EOS );
3450
0
        }
3451
3452
0
        lexer->txtend = lexer->lexsize;
3453
0
        lexer->lexbuf[lexer->lexsize] = '\0';
3454
0
        lexer->state = LEX_CONTENT;
3455
0
        lexer->waswhite = no;
3456
0
        lexer->token = CommentToken(doc);
3457
0
        node = lexer->token;
3458
0
        GTDBG(doc,"COMMENT", node);
3459
0
        return node;  /* the COMMENT token */
3460
0
    }
3461
3462
    /* check attributes before return NULL */
3463
0
    if (attributes)
3464
0
        TY_(FreeAttribute)( doc, attributes );
3465
3466
0
    DEBUG_LOG(SPRTF("Returning NULL...\n"));
3467
0
    return NULL;
3468
0
}
3469
3470
static void MapStr( ctmbstr str, uint code )
3471
0
{
3472
0
    while ( *str )
3473
0
    {
3474
0
        uint i = (byte) *str++;
3475
0
        lexmap[i] |= code;
3476
0
    }
3477
0
}
3478
3479
void TY_(InitMap)(void)
3480
0
{
3481
0
    MapStr("\r\n\f", newline|white);
3482
0
    MapStr(" \t", white);
3483
0
    MapStr("-.:_", namechar);
3484
0
    MapStr("0123456789", digit|digithex|namechar);
3485
0
    MapStr("abcdefghijklmnopqrstuvwxyz", lowercase|letter|namechar);
3486
0
    MapStr("ABCDEFGHIJKLMNOPQRSTUVWXYZ", uppercase|letter|namechar);
3487
0
    MapStr("abcdefABCDEF", digithex);
3488
0
}
3489
3490
/*
3491
 parser for ASP within start tags
3492
3493
 Some people use ASP for to customize attributes
3494
 Tidy isn't really well suited to dealing with ASP
3495
 This is a workaround for attributes, but won't
3496
 deal with the case where the ASP is used to tailor
3497
 the attribute value. Here is an example of a work
3498
 around for using ASP in attribute values:
3499
3500
  href='<%=rsSchool.Fields("ID").Value%>'
3501
3502
 where the ASP that generates the attribute value
3503
 is masked from Tidy by the quotemarks.
3504
3505
*/
3506
3507
static Node *ParseAsp( TidyDocImpl* doc )
3508
0
{
3509
0
    Lexer* lexer = doc->lexer;
3510
0
    uint c;
3511
0
    Node *asp = NULL;
3512
3513
0
    lexer->txtstart = lexer->lexsize;
3514
3515
0
    for (;;)
3516
0
    {
3517
0
        if ((c = TY_(ReadChar)(doc->docIn)) == EndOfStream)
3518
0
            break;
3519
3520
0
        TY_(AddCharToLexer)(lexer, c);
3521
3522
3523
0
        if (c != '%')
3524
0
            continue;
3525
3526
0
        if ((c = TY_(ReadChar)(doc->docIn)) == EndOfStream)
3527
0
            break;
3528
3529
0
        TY_(AddCharToLexer)(lexer, c);
3530
3531
0
        if (c == '>')
3532
0
        {
3533
0
            lexer->lexsize -= 2;
3534
0
            break;
3535
0
        }
3536
0
    }
3537
3538
0
    lexer->txtend = lexer->lexsize;
3539
0
    if (lexer->txtend > lexer->txtstart)
3540
0
        asp = AspToken(doc);
3541
3542
0
    lexer->txtstart = lexer->txtend;
3543
0
    return asp;
3544
0
}   
3545
 
3546
3547
/*
3548
 PHP is like ASP but is based upon XML
3549
 processing instructions, e.g. <?php ... ?>
3550
*/
3551
static Node *ParsePhp( TidyDocImpl* doc )
3552
0
{
3553
0
    Lexer* lexer = doc->lexer;
3554
0
    uint c;
3555
0
    Node *php = NULL;
3556
3557
0
    lexer->txtstart = lexer->lexsize;
3558
3559
0
    for (;;)
3560
0
    {
3561
0
        if ((c = TY_(ReadChar)(doc->docIn)) == EndOfStream)
3562
0
            break;
3563
3564
0
        TY_(AddCharToLexer)(lexer, c);
3565
3566
3567
0
        if (c != '?')
3568
0
            continue;
3569
3570
0
        if ((c = TY_(ReadChar)(doc->docIn)) == EndOfStream)
3571
0
            break;
3572
3573
0
        TY_(AddCharToLexer)(lexer, c);
3574
3575
0
        if (c == '>')
3576
0
        {
3577
0
            lexer->lexsize -= 2;
3578
0
            break;
3579
0
        }
3580
0
    }
3581
3582
0
    lexer->txtend = lexer->lexsize;
3583
0
    if (lexer->txtend > lexer->txtstart)
3584
0
        php = PhpToken(doc);
3585
3586
0
    lexer->txtstart = lexer->txtend;
3587
0
    return php;
3588
0
}   
3589
3590
/* consumes the '>' terminating start tags */
3591
/* @TODO: float the errors back to the calling method */
3592
static tmbstr  ParseAttribute( TidyDocImpl* doc, Bool *isempty,
3593
                              Node **asp, Node **php )
3594
0
{
3595
0
    Lexer* lexer = doc->lexer;
3596
0
    int start, len = 0;
3597
0
    tmbstr attr = NULL;
3598
0
    uint c, lastc;
3599
3600
0
    *asp = NULL;  /* clear asp pointer */
3601
0
    *php = NULL;  /* clear php pointer */
3602
3603
 /* skip white space before the attribute */
3604
3605
0
    for (;;)
3606
0
    {
3607
0
        c = TY_(ReadChar)( doc->docIn );
3608
3609
3610
0
        if (c == '/')
3611
0
        {
3612
0
            c = TY_(ReadChar)( doc->docIn );
3613
3614
0
            if (c == '>')
3615
0
            {
3616
0
                *isempty = yes;
3617
0
                return NULL;
3618
0
            }
3619
3620
0
            TY_(UngetChar)(c, doc->docIn);
3621
0
            c = '/';
3622
0
            break;
3623
0
        }
3624
3625
0
        if (c == '>')
3626
0
            return NULL;
3627
3628
0
        if (c =='<')
3629
0
        {
3630
0
            c = TY_(ReadChar)(doc->docIn);
3631
3632
0
            if (c == '%')
3633
0
            {
3634
0
                *asp = ParseAsp( doc );
3635
0
                return NULL;
3636
0
            }
3637
0
            else if (c == '?')
3638
0
            {
3639
0
                *php = ParsePhp( doc );
3640
0
                return NULL;
3641
0
            }
3642
3643
0
            TY_(UngetChar)(c, doc->docIn);
3644
0
            TY_(UngetChar)('<', doc->docIn);
3645
0
            TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_GT );
3646
0
            return NULL;
3647
0
        }
3648
3649
0
        if (c == '=')
3650
0
        {
3651
0
            TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_EQUALSIGN );
3652
0
            continue;
3653
0
        }
3654
3655
0
        if (c == '"' || c == '\'')
3656
0
        {
3657
0
            TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_QUOTEMARK );
3658
0
            continue;
3659
0
        }
3660
3661
0
        if (c == EndOfStream)
3662
0
        {
3663
0
            TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_END_OF_FILE_ATTR );
3664
0
            TY_(UngetChar)(c, doc->docIn);
3665
0
            return NULL;
3666
0
        }
3667
3668
3669
0
        if (!TY_(IsWhite)(c))
3670
0
           break;
3671
0
    }
3672
3673
0
    start = lexer->lexsize;
3674
0
    lastc = c;
3675
3676
0
    for (;;)
3677
0
    {
3678
     /* but push back '=' for parseValue() */
3679
0
        if (c == '=' || c == '>')
3680
0
        {
3681
0
            TY_(UngetChar)(c, doc->docIn);
3682
0
            break;
3683
0
        }
3684
3685
0
        if (c == '<' || c == EndOfStream)
3686
0
        {
3687
0
            TY_(UngetChar)(c, doc->docIn);
3688
0
            break;
3689
0
        }
3690
3691
0
        if (lastc == '-' && (c == '"' || c == '\''))
3692
0
        {
3693
0
            lexer->lexsize--;
3694
0
            --len;
3695
0
            TY_(UngetChar)(c, doc->docIn);
3696
0
            break;
3697
0
        }
3698
3699
0
        if (TY_(IsWhite)(c))
3700
0
            break;
3701
3702
0
        if (c == '/') /* Issue #395 - potential self closing tag */
3703
0
        {
3704
0
            c = TY_(ReadChar)(doc->docIn);  /* read next */
3705
0
            if (c == '>')
3706
0
            {
3707
                /* got a self closing tag - put is back and continue... */
3708
0
                TY_(UngetChar)(c, doc->docIn);
3709
0
                break;
3710
0
            }
3711
0
            else
3712
0
            {
3713
                /* Not '/>' - put it back */
3714
0
                TY_(UngetChar)(c, doc->docIn);
3715
0
                c = '/';  /* restore original char */
3716
0
            }
3717
0
        }
3718
3719
        /* what should be done about non-namechar characters? */
3720
        /* currently these are incorporated into the attr name */
3721
3722
0
        if ( cfg(doc, TidyUpperCaseAttrs) != TidyUppercasePreserve )
3723
0
        {
3724
0
            if ( !cfgBool(doc, TidyXmlTags) && TY_(IsUpper)(c) )
3725
0
                c = TY_(ToLower)(c);
3726
0
        }
3727
3728
0
        TY_(AddCharToLexer)( lexer, c );
3729
0
        lastc = c;
3730
0
        c = TY_(ReadChar)(doc->docIn);
3731
0
    }
3732
3733
    /* handle attribute names with multibyte chars */
3734
0
    len = lexer->lexsize - start;
3735
0
    attr = (len > 0 ? TY_(tmbstrndup)(doc->allocator,
3736
0
                                      lexer->lexbuf+start, len) : NULL);
3737
0
    lexer->lexsize = start;
3738
0
    return attr;
3739
0
}
3740
3741
/*
3742
 invoked when < is seen in place of attribute value
3743
 but terminates on whitespace if not ASP, PHP or Tango
3744
 this routine recognizes ' and " quoted strings
3745
*/
3746
static int ParseServerInstruction( TidyDocImpl* doc )
3747
0
{
3748
0
    Lexer* lexer = doc->lexer;
3749
0
    uint c;
3750
0
    int delim = '"';
3751
0
    Bool isrule = no;
3752
3753
0
    c = TY_(ReadChar)(doc->docIn);
3754
0
    TY_(AddCharToLexer)(lexer, c);
3755
3756
    /* check for ASP, PHP or Tango */
3757
0
    if (c == '%' || c == '?' || c == '@')
3758
0
        isrule = yes;
3759
3760
0
    for (;;)
3761
0
    {
3762
0
        c = TY_(ReadChar)(doc->docIn);
3763
3764
0
        if (c == EndOfStream)
3765
0
            break;
3766
3767
0
        if (c == '>')
3768
0
        {
3769
0
            if (isrule)
3770
0
                TY_(AddCharToLexer)(lexer, c);
3771
0
            else
3772
0
                TY_(UngetChar)(c, doc->docIn);
3773
3774
0
            break;
3775
0
        }
3776
3777
        /* if not recognized as ASP, PHP or Tango */
3778
        /* then also finish value on whitespace */
3779
0
        if (!isrule)
3780
0
        {
3781
0
            if (TY_(IsWhite)(c))
3782
0
                break;
3783
0
        }
3784
3785
0
        TY_(AddCharToLexer)(lexer, c);
3786
3787
0
        if (c == '"')
3788
0
        {
3789
0
            do
3790
0
            {
3791
0
                c = TY_(ReadChar)(doc->docIn);
3792
0
                if (c == EndOfStream) /* #427840 - fix by Terry Teague 30 Jun 01 */
3793
0
                {
3794
0
                    TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_END_OF_FILE_ATTR );
3795
0
                    TY_(UngetChar)(c, doc->docIn);
3796
0
                    return 0;
3797
0
                }
3798
0
                if (c == '>') /* #427840 - fix by Terry Teague 30 Jun 01 */
3799
0
                {
3800
0
                    TY_(UngetChar)(c, doc->docIn);
3801
0
                    TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_GT );
3802
0
                    return 0;
3803
0
                }
3804
0
                TY_(AddCharToLexer)(lexer, c);
3805
0
            }
3806
0
            while (c != '"');
3807
0
            delim = '\'';
3808
0
            continue;
3809
0
        }
3810
3811
0
        if (c == '\'')
3812
0
        {
3813
0
            do
3814
0
            {
3815
0
                c = TY_(ReadChar)(doc->docIn);
3816
0
                if (c == EndOfStream) /* #427840 - fix by Terry Teague 30 Jun 01 */
3817
0
                {
3818
0
                    TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_END_OF_FILE_ATTR );
3819
0
                    TY_(UngetChar)(c, doc->docIn);
3820
0
                    return 0;
3821
0
                }
3822
0
                if (c == '>') /* #427840 - fix by Terry Teague 30 Jun 01 */
3823
0
                {
3824
0
                    TY_(UngetChar)(c, doc->docIn);
3825
0
                    TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_GT );
3826
0
                    return 0;
3827
0
                }
3828
0
                TY_(AddCharToLexer)(lexer, c);
3829
0
            }
3830
0
            while (c != '\'');
3831
0
        }
3832
0
    }
3833
3834
0
    return delim;
3835
0
}
3836
3837
/* values start with "=" or " = " etc. */
3838
/* doesn't consume the ">" at end of start tag */
3839
3840
static tmbstr ParseValue( TidyDocImpl* doc, ctmbstr name,
3841
                          Bool foldCase, Bool *isempty, int *pdelim)
3842
0
{
3843
0
    Lexer* lexer = doc->lexer;
3844
0
    int len = 0, start;
3845
0
    Bool seen_gt = no;
3846
0
    Bool munge = yes;
3847
0
    uint c, lastc, delim, quotewarning;
3848
0
    tmbstr value;
3849
3850
0
    delim = (tmbchar) 0;
3851
0
    *pdelim = '"';
3852
3853
    /*
3854
     Henry Zrepa reports that some folk are using the
3855
     embed element with script attributes where newlines
3856
     are significant and must be preserved
3857
    */
3858
0
    if ( cfgBool(doc, TidyLiteralAttribs) )
3859
0
        munge = no;
3860
3861
 /* skip white space before the '=' */
3862
3863
0
    for (;;)
3864
0
    {
3865
0
        c = TY_(ReadChar)(doc->docIn);
3866
3867
0
        if (c == EndOfStream)
3868
0
        {
3869
0
            TY_(UngetChar)(c, doc->docIn);
3870
0
            break;
3871
0
        }
3872
3873
0
        if (!TY_(IsWhite)(c))
3874
0
           break;
3875
0
    }
3876
3877
/*
3878
  c should be '=' if there is a value
3879
  other legal possibilities are white
3880
  space, '/' and '>'
3881
*/
3882
3883
0
    if (c != '=' && c != '"' && c != '\'')
3884
0
    {
3885
0
        TY_(UngetChar)(c, doc->docIn);
3886
0
        return NULL;
3887
0
    }
3888
3889
 /* skip white space after '=' */
3890
3891
0
    for (;;)
3892
0
    {
3893
0
        c = TY_(ReadChar)(doc->docIn);
3894
3895
0
        if (c == EndOfStream)
3896
0
        {
3897
0
            TY_(UngetChar)(c, doc->docIn);
3898
0
            break;
3899
0
        }
3900
3901
0
        if (!TY_(IsWhite)(c))
3902
0
           break;
3903
0
    }
3904
3905
 /* check for quote marks */
3906
3907
0
    if (c == '"' || c == '\'')
3908
0
        delim = c;
3909
0
    else if (c == '<')
3910
0
    {
3911
0
        start = lexer->lexsize;
3912
0
        TY_(AddCharToLexer)(lexer, c);
3913
0
        *pdelim = ParseServerInstruction( doc );
3914
0
        len = lexer->lexsize - start;
3915
0
        lexer->lexsize = start;
3916
0
        return (len > 0 ? TY_(tmbstrndup)(doc->allocator,
3917
0
                                          lexer->lexbuf+start, len) : NULL);
3918
0
    }
3919
0
    else
3920
0
        TY_(UngetChar)(c, doc->docIn);
3921
3922
 /*
3923
   and read the value string
3924
   check for quote mark if needed
3925
 */
3926
3927
0
    quotewarning = 0;
3928
0
    start = lexer->lexsize;
3929
0
    c = '\0';
3930
3931
0
    for (;;)
3932
0
    {
3933
0
        lastc = c;  /* track last character */
3934
0
        c = TY_(ReadChar)(doc->docIn);
3935
3936
0
        if (c == EndOfStream)
3937
0
        {
3938
0
            TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_END_OF_FILE_ATTR );
3939
0
            TY_(UngetChar)(c, doc->docIn);
3940
0
            break;
3941
0
        }
3942
3943
0
        if (delim == (tmbchar)0)
3944
0
        {
3945
0
            if (c == '>')
3946
0
            {
3947
0
                TY_(UngetChar)(c, doc->docIn);
3948
0
                break;
3949
0
            }
3950
3951
0
            if (c == '"' || c == '\'')
3952
0
            {
3953
0
                uint q = c;
3954
3955
                /* handle <input onclick=s("btn1")> and <a title=foo""">...</a> */
3956
                /* this doesn't handle <a title=foo"/> which browsers treat as  */
3957
                /* 'foo"/' nor  <a title=foo" /> which browser treat as 'foo"'  */
3958
                
3959
0
                c = TY_(ReadChar)(doc->docIn);
3960
0
                if (c == '>')
3961
0
                {
3962
0
                    TY_(AddCharToLexer)(lexer, q);
3963
0
                    TY_(UngetChar)(c, doc->docIn);
3964
0
                    break;
3965
0
                }
3966
0
                else
3967
0
                {
3968
0
                    TY_(UngetChar)(c, doc->docIn);
3969
0
                    c = q;
3970
0
                }
3971
0
            }
3972
3973
0
            if (c == '<')
3974
0
            {
3975
0
                TY_(UngetChar)(c, doc->docIn);
3976
0
                c = '>';
3977
0
                TY_(UngetChar)(c, doc->docIn);
3978
0
                TY_(ReportAttrError)( doc, lexer->token, NULL, UNEXPECTED_GT );
3979
0
                break;
3980
0
            }
3981
3982
            /*
3983
             For cases like <br clear=all/> need to avoid treating /> as
3984
             part of the attribute value, however care is needed to avoid
3985
             so treating <a href=http://www.acme.com/> in this way, which
3986
             would map the <a> tag to <a href="http://www.acme.com"/>
3987
            */
3988
0
            if (c == '/')
3989
0
            {
3990
                /* peek ahead in case of /> */
3991
0
                c = TY_(ReadChar)(doc->docIn);
3992
3993
0
                if ( c == '>' && !TY_(IsUrl)(doc, name) )
3994
0
                {
3995
0
                    *isempty = yes;
3996
0
                    TY_(UngetChar)(c, doc->docIn);
3997
0
                    break;
3998
0
                }
3999
4000
                /* unget peeked character */
4001
0
                TY_(UngetChar)(c, doc->docIn);
4002
0
                c = '/';
4003
0
            }
4004
0
        }
4005
0
        else  /* delim is '\'' or '"' */
4006
0
        {
4007
0
            if (c == delim)
4008
0
                break;
4009
4010
0
            if (c == '\n' || c == '<' || c == '>')
4011
0
                ++quotewarning;
4012
4013
0
            if (c == '>')
4014
0
                seen_gt = yes;
4015
0
        }
4016
4017
0
        if (c == '&')
4018
0
        {
4019
0
            TY_(AddCharToLexer)(lexer, c);
4020
0
            ParseEntity( doc, IgnoreWhitespace );
4021
0
            if (lexer->lexbuf[lexer->lexsize - 1] == '\n' && munge)
4022
0
                ChangeChar(lexer, ' ');
4023
0
            continue;
4024
0
        }
4025
4026
        /*
4027
         kludge for JavaScript attribute values
4028
         with line continuations in string literals
4029
        */
4030
0
        if (c == '\\')
4031
0
        {
4032
0
            c = TY_(ReadChar)(doc->docIn);
4033
4034
0
            if (c != '\n')
4035
0
            {
4036
0
                TY_(UngetChar)(c, doc->docIn);
4037
0
                c = '\\';
4038
0
            }
4039
0
        }
4040
4041
0
        if (TY_(IsWhite)(c))
4042
0
        {
4043
0
            if ( delim == 0 )
4044
0
                break;
4045
4046
0
            if (munge)
4047
0
            {
4048
                /* discard line breaks in quoted URLs */ 
4049
                /* #438650 - fix by Randy Waki */
4050
0
                if ( c == '\n' && TY_(IsUrl)(doc, name) )
4051
0
                {
4052
                    /* warn that we discard this newline */
4053
0
                    TY_(ReportAttrError)( doc, lexer->token, NULL, NEWLINE_IN_URI);
4054
0
                    continue;
4055
0
                }
4056
                
4057
0
                c = ' ';
4058
4059
0
                if (lastc == ' ')
4060
0
                {
4061
0
                    if (TY_(IsUrl)(doc, name) )
4062
0
                        TY_(ReportAttrError)( doc, lexer->token, NULL, WHITE_IN_URI);
4063
0
                    continue;
4064
0
                }
4065
0
            }
4066
0
        }
4067
0
        else if (foldCase && TY_(IsUpper)(c))
4068
0
            c = TY_(ToLower)(c);
4069
4070
0
        TY_(AddCharToLexer)(lexer, c);
4071
0
    }
4072
4073
0
    if (quotewarning > 10 && seen_gt && munge)
4074
0
    {
4075
        /*
4076
           there is almost certainly a missing trailing quote mark
4077
           as we have see too many newlines, < or > characters.
4078
4079
           an exception is made for Javascript attributes and the
4080
           javascript URL scheme which may legitimately include < and >,
4081
           and for attributes starting with "<xml " as generated by
4082
           Microsoft Office.
4083
        */
4084
0
        if ( !TY_(IsScript)(doc, name) &&
4085
0
             !(TY_(IsUrl)(doc, name) && TY_(tmbstrncmp)(lexer->lexbuf+start, "javascript:", 11) == 0) &&
4086
0
             !(TY_(tmbstrncmp)(lexer->lexbuf+start, "<xml ", 5) == 0)
4087
0
           )
4088
0
            TY_(Report)( doc, NULL, NULL, SUSPECTED_MISSING_QUOTE ); 
4089
0
    }
4090
4091
0
    len = lexer->lexsize - start;
4092
0
    lexer->lexsize = start;
4093
4094
4095
0
    if (len > 0 || delim)
4096
0
    {
4097
        /* ignore leading and trailing white space for all but title, alt, value */
4098
        /* and prompts attributes unless --literal-attributes is set to yes      */
4099
        /* #994841 - Whitespace is removed from value attributes                 */
4100
4101
        /* Issue #217 - Also only if/while (len > 0) - MUST NEVER GO NEGATIVE! */
4102
0
        if ((len > 0) && munge &&
4103
0
            TY_(tmbstrcasecmp)(name, "alt") &&
4104
0
            TY_(tmbstrcasecmp)(name, "title") &&
4105
0
            TY_(tmbstrcasecmp)(name, "value") &&
4106
0
            TY_(tmbstrcasecmp)(name, "prompt"))
4107
0
        {
4108
0
            while (TY_(IsWhite)(lexer->lexbuf[start+len-1]) && (len > 0))
4109
0
                --len;
4110
4111
            /* Issue #497 - Fix leading space trimming */
4112
0
            while (TY_(IsWhite)(lexer->lexbuf[start]) && (len > 0))
4113
0
            {
4114
0
                ++start;
4115
0
                --len;
4116
0
            }
4117
0
        }
4118
4119
0
        value = TY_(tmbstrndup)(doc->allocator, lexer->lexbuf + start, len);
4120
0
    }
4121
0
    else
4122
0
        value = NULL;
4123
4124
    /* note delimiter if given */
4125
0
    *pdelim = delim;
4126
4127
0
    return value;
4128
0
}
4129
4130
/* attr must be non-NULL */
4131
static Bool IsValidAttrName( ctmbstr attr )
4132
0
{
4133
0
    uint i, c = attr[0];
4134
4135
    /* first character should be a letter */
4136
0
    if (!TY_(IsLetter)(c))
4137
0
        return no;
4138
4139
    /* remaining characters should be namechars */
4140
0
    for( i = 1; i < TY_(tmbstrlen)(attr); i++)
4141
0
    {
4142
0
        c = attr[i];
4143
4144
0
        if (TY_(IsNamechar)(c))
4145
0
            continue;
4146
4147
0
        return no;
4148
0
    }
4149
4150
0
    return yes;
4151
0
}
4152
4153
/* create a new attribute */
4154
AttVal *TY_(NewAttribute)( TidyDocImpl* doc )
4155
0
{
4156
0
    AttVal *av = (AttVal*) TidyDocAlloc( doc, sizeof(AttVal) );
4157
0
    TidyClearMemory( av, sizeof(AttVal) );
4158
0
    return av;
4159
0
}
4160
4161
/* create a new attribute with given name and value */
4162
AttVal* TY_(NewAttributeEx)( TidyDocImpl* doc, ctmbstr name, ctmbstr value,
4163
                             int delim )
4164
0
{
4165
0
    AttVal *av = TY_(NewAttribute)(doc);
4166
0
    av->attribute = TY_(tmbstrdup)(doc->allocator, name);
4167
0
    av->value = TY_(tmbstrdup)(doc->allocator, value);
4168
0
    av->delim = delim;
4169
0
    av->dict = TY_(FindAttribute)( doc, av );
4170
0
    return av;
4171
0
}
4172
4173
static void AddAttrToList( AttVal** list, AttVal* av )
4174
0
{
4175
0
  if ( *list == NULL )
4176
0
    *list = av;
4177
0
  else
4178
0
  {
4179
0
    AttVal* here = *list;
4180
0
    while ( here->next )
4181
0
      here = here->next;
4182
0
    here->next = av;
4183
0
  }
4184
0
}
4185
4186
void TY_(InsertAttributeAtEnd)( Node *node, AttVal *av )
4187
0
{
4188
0
    AddAttrToList(&node->attributes, av);
4189
0
}
4190
4191
void TY_(InsertAttributeAtStart)( Node *node, AttVal *av )
4192
0
{
4193
0
    av->next = node->attributes;
4194
0
    node->attributes = av;
4195
0
}
4196
4197
/* swallows closing '>' */
4198
4199
static AttVal* ParseAttrs( TidyDocImpl* doc, Bool *isempty )
4200
0
{
4201
0
    Lexer* lexer = doc->lexer;
4202
0
    AttVal *av, *list;
4203
0
    tmbstr value;
4204
0
    int delim;
4205
0
    Node *asp, *php;
4206
4207
0
    list = NULL;
4208
4209
0
    while ( !EndOfInput(doc) )
4210
0
    {
4211
0
        tmbstr attribute = ParseAttribute( doc, isempty, &asp, &php );
4212
4213
0
        if (attribute == NULL)
4214
0
        {
4215
            /* check if attributes are created by ASP markup */
4216
0
            if (asp)
4217
0
            {
4218
0
                av = TY_(NewAttribute)(doc);
4219
0
                av->asp = asp;
4220
0
                AddAttrToList( &list, av ); 
4221
0
                continue;
4222
0
            }
4223
4224
            /* check if attributes are created by PHP markup */
4225
0
            if (php)
4226
0
            {
4227
0
                av = TY_(NewAttribute)(doc);
4228
0
                av->php = php;
4229
0
                AddAttrToList( &list, av ); 
4230
0
                continue;
4231
0
            }
4232
4233
0
            break;
4234
0
        }
4235
4236
0
        value = ParseValue( doc, attribute, no, isempty, &delim );
4237
4238
0
        if (attribute && (IsValidAttrName(attribute) ||
4239
0
            (cfgBool(doc, TidyXmlTags) && IsValidXMLAttrName(attribute))))
4240
0
        {
4241
0
            av = TY_(NewAttribute)(doc);
4242
0
            av->delim = delim ? delim : '"';
4243
0
            av->attribute = attribute;
4244
0
            av->value = value;
4245
0
            av->dict = TY_(FindAttribute)( doc, av );
4246
0
            AddAttrToList( &list, av );
4247
0
            if ( !delim && value )
4248
0
                TY_(ReportAttrError)( doc, lexer->token, av, MISSING_QUOTEMARK_OPEN);
4249
0
        }
4250
0
        else
4251
0
        {
4252
0
            av = TY_(NewAttribute)(doc);
4253
0
            av->attribute = attribute;
4254
0
            av->value = value;
4255
4256
0
            if (LastChar(attribute) == '"')
4257
0
                TY_(ReportAttrError)( doc, lexer->token, av, MISSING_QUOTEMARK);
4258
0
            else if (value == NULL)
4259
0
                TY_(ReportAttrError)(doc, lexer->token, av, MISSING_ATTR_VALUE);
4260
0
            else
4261
0
                TY_(ReportAttrError)(doc, lexer->token, av, INVALID_ATTRIBUTE);
4262
4263
0
            TY_(FreeAttribute)( doc, av );
4264
0
        }
4265
0
    }
4266
4267
0
    return list;
4268
0
}
4269
4270
/*
4271
  Returns document type declarations like
4272
4273
  <!DOCTYPE foo PUBLIC "fpi" "sysid">
4274
  <!DOCTYPE bar SYSTEM "sysid">
4275
  <!DOCTYPE baz [ <!ENTITY ouml "&#246"> ]>
4276
4277
  as
4278
4279
  <foo PUBLIC="fpi" SYSTEM="sysid" />
4280
  <bar SYSTEM="sysid" />
4281
  <baz> &lt;!ENTITY ouml &quot;&amp;#246&quot;&gt; </baz>
4282
*/
4283
static Node *ParseDocTypeDecl(TidyDocImpl* doc)
4284
0
{
4285
0
    Lexer *lexer = doc->lexer;
4286
0
    int start = lexer->lexsize;
4287
0
    ParseDocTypeDeclState state = DT_DOCTYPENAME;
4288
0
    uint c;
4289
0
    uint delim = 0;
4290
0
    Bool hasfpi = yes;
4291
4292
0
    Node* node = TY_(NewNode)(lexer->allocator, lexer);
4293
0
    node->type = DocTypeTag;
4294
0
    node->start = lexer->txtstart;
4295
0
    node->end = lexer->txtend;
4296
4297
0
    lexer->waswhite = no;
4298
4299
    /* todo: reset lexer->lexsize when appropriate to avoid wasting memory */
4300
4301
0
    while ((c = TY_(ReadChar)(doc->docIn)) != EndOfStream)
4302
0
    {
4303
        /* convert newlines to spaces */
4304
0
        if (state != DT_INTSUBSET)
4305
0
            c = c == '\n' ? ' ' : c;
4306
4307
        /* convert white-space sequences to single space character */
4308
0
        if (TY_(IsWhite)(c) && state != DT_INTSUBSET)
4309
0
        {
4310
0
            if (!lexer->waswhite)
4311
0
            {
4312
0
                TY_(AddCharToLexer)(lexer, c);
4313
0
                lexer->waswhite = yes;
4314
0
            }
4315
0
            else
4316
0
            {
4317
                /* discard space */
4318
0
                continue;
4319
0
            }
4320
0
        }
4321
0
        else
4322
0
        {
4323
0
            TY_(AddCharToLexer)(lexer, c);
4324
0
            lexer->waswhite = no;
4325
0
        }
4326
4327
0
        switch(state)
4328
0
        {
4329
0
        case DT_INTERMEDIATE:
4330
            /* determine what's next */
4331
0
            if (TY_(ToUpper)(c) == 'P' || TY_(ToUpper)(c) == 'S')
4332
0
            {
4333
0
                start = lexer->lexsize - 1;
4334
0
                state = DT_PUBLICSYSTEM;
4335
0
                continue;
4336
0
            }
4337
0
            else if (c == '[')
4338
0
            {
4339
0
                start = lexer->lexsize;
4340
0
                state = DT_INTSUBSET;
4341
0
                continue;
4342
0
            }
4343
0
            else if (c == '\'' || c == '"')
4344
0
            {
4345
0
                start = lexer->lexsize;
4346
0
                delim = c;
4347
0
                state = DT_QUOTEDSTRING;
4348
0
                continue;
4349
0
            }
4350
0
            else if (c == '>')
4351
0
            {
4352
0
                AttVal* si;
4353
4354
0
                node->end = --(lexer->lexsize);
4355
4356
0
                si = TY_(GetAttrByName)(node, "SYSTEM");
4357
0
                if (si)
4358
0
                    TY_(CheckUrl)(doc, node, si);
4359
4360
0
                if (!node->element || !IsValidXMLElemName(node->element))
4361
0
                {
4362
0
                    TY_(Report)(doc, NULL, NULL, MALFORMED_DOCTYPE);
4363
0
                    TY_(FreeNode)(doc, node);
4364
0
                    return NULL;
4365
0
                }
4366
0
                return node;
4367
0
            }
4368
0
            else
4369
0
            {
4370
                /* error */
4371
0
            }
4372
0
            break;
4373
0
        case DT_DOCTYPENAME:
4374
            /* read document type name */
4375
0
            if (TY_(IsWhite)(c) || c == '>' || c == '[')
4376
0
            {
4377
0
                node->element = TY_(tmbstrndup)(doc->allocator,
4378
0
                                                lexer->lexbuf + start,
4379
0
                                                lexer->lexsize - start - 1);
4380
0
                if (c == '>' || c == '[')
4381
0
                {
4382
0
                    --(lexer->lexsize);
4383
0
                    TY_(UngetChar)(c, doc->docIn);
4384
0
                }
4385
4386
0
                state = DT_INTERMEDIATE;
4387
0
                continue;
4388
0
            }
4389
0
            break;
4390
0
        case DT_PUBLICSYSTEM:
4391
            /* read PUBLIC/SYSTEM */
4392
0
            if (TY_(IsWhite)(c) || c == '>')
4393
0
            {
4394
0
                char *attname = TY_(tmbstrndup)(doc->allocator,
4395
0
                                                lexer->lexbuf + start,
4396
0
                                                lexer->lexsize - start - 1);
4397
0
                hasfpi = !(TY_(tmbstrcasecmp)(attname, "SYSTEM") == 0);
4398
4399
0
                TidyDocFree(doc, attname);
4400
4401
                /* todo: report an error if SYSTEM/PUBLIC not uppercase */
4402
4403
0
                if (c == '>')
4404
0
                {
4405
0
                    --(lexer->lexsize);
4406
0
                    TY_(UngetChar)(c, doc->docIn);
4407
0
                }
4408
4409
0
                state = DT_INTERMEDIATE;
4410
0
                continue;
4411
0
            }
4412
0
            break;
4413
0
        case DT_QUOTEDSTRING:
4414
            /* read quoted string */
4415
0
            if (c == delim)
4416
0
            {
4417
0
                char *value = TY_(tmbstrndup)(doc->allocator,
4418
0
                                              lexer->lexbuf + start,
4419
0
                                              lexer->lexsize - start - 1);
4420
0
                AttVal* att = TY_(AddAttribute)(doc, node, hasfpi ? "PUBLIC" : "SYSTEM", value);
4421
0
                TidyDocFree(doc, value);
4422
0
                att->delim = delim;
4423
0
                hasfpi = no;
4424
0
                state = DT_INTERMEDIATE;
4425
0
                delim = 0;
4426
0
                continue;
4427
0
            }
4428
0
            break;
4429
0
        case DT_INTSUBSET:
4430
            /* read internal subset */
4431
0
            if (c == ']')
4432
0
            {
4433
0
                Node* subset;
4434
0
                lexer->txtstart = start;
4435
0
                lexer->txtend = lexer->lexsize - 1;
4436
0
                subset = TY_(TextToken)(lexer);
4437
0
                TY_(InsertNodeAtEnd)(node, subset);
4438
0
                state = DT_INTERMEDIATE;
4439
0
            }
4440
0
            break;
4441
0
        }
4442
0
    }
4443
4444
    /* document type declaration not finished */
4445
0
    TY_(Report)(doc, NULL, NULL, MALFORMED_DOCTYPE);
4446
0
    TY_(FreeNode)(doc, node);
4447
0
    return NULL;
4448
0
}
4449
4450
4451
/****************************************************************************//*
4452
 ** MARK: - Node Stack
4453
 ***************************************************************************/
4454
4455
4456
/**
4457
 * Create a new stack with a given starting capacity. If memory allocation
4458
 * fails, then the allocator will panic the program automatically.
4459
 */
4460
Stack* TY_(newStack)(TidyDocImpl *doc, uint capacity)
4461
0
{
4462
0
    Stack *stack = (Stack *)TidyAlloc(doc->allocator, sizeof(Stack));
4463
0
    stack->top = -1;
4464
0
    stack->capacity = capacity;
4465
0
    stack->firstNode = (Node **)TidyAlloc(doc->allocator, stack->capacity * sizeof(Node**));
4466
0
    stack->allocator = doc->allocator;
4467
0
    return stack;
4468
0
}
4469
 
4470
4471
/**
4472
 *  Increase the stack size. This will be called automatically when the
4473
 *  current stack is full. If memory allocation fails, then the allocator
4474
 *  will panic the program automatically.
4475
 */
4476
void TY_(growStack)(Stack *stack)
4477
0
{
4478
0
    uint new_capacity = stack->capacity * 2;
4479
    
4480
0
    Node **firstNode = (Node **)TidyAlloc(stack->allocator, new_capacity * sizeof(Node**));
4481
    
4482
0
    memcpy( firstNode, stack->firstNode, sizeof(Node**) * (stack->top + 1) );
4483
0
    TidyFree(stack->allocator, stack->firstNode);
4484
4485
0
    stack->firstNode = firstNode;
4486
0
    stack->capacity = new_capacity;
4487
0
}
4488
4489
4490
/**
4491
 * Stack is full when top is equal to the last index.
4492
 */
4493
Bool TY_(stackFull)(Stack *stack)
4494
0
{
4495
0
    return stack->top == stack->capacity - 1;
4496
0
}
4497
4498
4499
/**
4500
 * Stack is empty when top is equal to -1
4501
 */
4502
Bool TY_(stackEmpty)(Stack *stack)
4503
0
{
4504
0
    return stack->top == -1;
4505
0
}
4506
 
4507
4508
/**
4509
 * Push an item to the stack.
4510
 */
4511
void TY_(push)(Stack *stack, Node *node)
4512
0
{
4513
0
    if (TY_(stackFull)(stack))
4514
0
        TY_(growStack)(stack);
4515
    
4516
0
    if (node)
4517
0
        stack->firstNode[++stack->top] = node;
4518
0
}
4519
4520
4521
/**
4522
 * Pop an item from the stack.
4523
 */
4524
Node* TY_(pop)(Stack *stack)
4525
0
{
4526
0
    return TY_(stackEmpty)(stack) ? NULL : stack->firstNode[stack->top--];
4527
0
}
4528
4529
4530
/**
4531
 * Peek at the stack.
4532
 */
4533
FUNC_UNUSED Node* TY_(peek)(Stack *stack)
4534
0
{
4535
0
    return TY_(stackEmpty)(stack) ? NULL : stack->firstNode[stack->top--];
4536
0
}
4537
4538
/**
4539
 *  Frees the stack when done.
4540
 */
4541
void TY_(freeStack)(Stack *stack)
4542
0
{
4543
0
    TidyFree( stack->allocator, stack->firstNode );
4544
0
    stack->top = -1;
4545
0
    stack->capacity = 0;
4546
0
    stack->firstNode = NULL;
4547
    stack->allocator = NULL;
4548
0
}