File-Raw-XML
view release on metacpan or search on metacpan
include/frx/frx_lex.h view on Meta::CPAN
#ifndef FRX_LEX_H
#define FRX_LEX_H
/* Bytes in, tokens out, every refusal with an offset.
*
* This is the whole of the attacker-facing surface: everything after it
* operates on validated names and decoded strings already in the arena. A
* pull tokeniser rather than a lexer folded into the parser, so that every
* refusal is testable through the _lex accessor without a tree, and so that
* the every-offset test can hit the lexer alone.
*
* What it accepts: XML 1.0 with the five predefined entities and numeric
* character references, in UTF-8. What it refuses, each with the offset it
* was found at: any DOCTYPE (and any <! that is not a comment or CDATA -
* one rule removes entity expansion, external entities, the billion laughs
* and XXE as a class, with no option to allow them); UTF-16 and UTF-32 by
* BOM or by first pair; a declared encoding other than utf-8; version 1.1;
* a <?xml declaration anywhere but offset 0; a named reference other than
* the five; a reference to a non-Char; ]]> in content; a literal < in an
* attribute value; -- inside a comment; a PI whose target is xml in any
* case; a Name that is not a QName (at most one colon, both parts
* non-empty); anything not well-formed.
*
* Normalisation, in this order and with this word: line ends first (XML
* 1.0 section 2.11, CRLF and CR become LF, in text and in attribute
* values), then attribute values (section 3.3.3: with no DTD every
* attribute is CDATA, so each LITERAL TAB, LF or CR becomes a space). A
* character reference to one of those - 	 
 
 - stays the
* character. Canonical XML escapes TAB, LF and CR in attribute values, and
* the only way such a character reaches the serialiser is through a
* reference, because the literal form was already a space. A lexer that
* normalises after expanding references produces canonical bytes that
* differ from every other implementation's.
*
* Every string a token carries is in the arena, NUL-terminated. CDATA is a
* TEXT token with the cdata flag, so the parser can merge it with its
* neighbours; there is no CDATA node kind. The attribute list of a START or
* EMPTY token is borrowed from the lexer's scratch and valid until the next
* frx_lex_next.
*
* THE FULL PROFILE reads a DOCTYPE and expands the internal subset's
* entities. An entity is an input frame, not a string substitution: the
* lexer's in/len/pos describe the frame being read, a reference saves them
* and points them at the replacement text, and reaching the end of that
* text restores them. Every token-level function reads through in/len/pos
* unchanged; the frame boundary is the one place the rules of section 4.4
* are enforced (an element, comment, PI, CDATA section or attribute value
* that opened inside a frame must close inside it; an end tag inside a
* frame must close an element opened inside it), and that is the whole of
* the well-formedness of a parsed entity. Offsets: `base` is the document
* offset the current frame's byte 0 corresponds to, 0 for the document
* itself and the entity literal's first byte otherwise, so a refusal
* inside an entity points into its declaration; FRX_LEX_FAIL takes a
* frame-relative offset and adds base, FRX_LEX_FAIL_ABS takes a document
* offset. Attribute types come from the DTD before an element is lexed
* (the DTD is complete before the root starts), which is what lets the
* 3.3.3 normalisation by type happen here, in the one place the literal
* whitespace rule already lives. The declaration parser is frx_dtd.h and
* the frame stack's push, pop and budgets are frx_entity.h; both need this
* file's primitives, so they follow it and are declared below.
*
* Needs frx_err.h, frx_arena.h, frx_buf.h, frx_utf8.h, frx_enc.h,
* frx_tree.h. */
typedef struct frx_eldecl frx_eldecl; /* frx_dtd.h; frx_dtd and frx_doctype are frx_tree.h's */
typedef struct frx_attdef frx_attdef; /* frx_dtd.h */
typedef struct frx_entity frx_entity; /* frx_dtd.h */
/* what an entity reference saves: the frame it interrupted */
typedef struct frx_frame_in {
const unsigned char *in; /* the interrupted frame's bytes */
size_t len, pos, base;
frx_str cur_base; /* its base URI */
int flat; /* its offsets were flat */
int ext_decl; /* it allowed PE references inside declarations */
int in_dtd; /* it was inside a parameter entity or the external subset */
frx_entity *entity; /* the entity whose text is now current; NULL for fetched text with none */
include/frx/frx_lex.h view on Meta::CPAN
i += 3;
continue;
}
if (frx_lex_starts(l, i, "<?")) {
i += 2;
while (i < l->len && !frx_lex_starts(l, i, "?>")) i++;
if (i >= l->len) return 0;
i += 2;
continue;
}
if (c == '[') depth++;
else if (c == ']') { if (depth) depth--; }
else if (c == '>' && depth == 0) return 1;
i++;
}
return 0;
}
/* a pseudo-attribute of the XML declaration: name S? = S? quoted; the
* value is returned as a slice of the input */
static int
frx_lex_pseudo(frx_lex *l, const char *name, const unsigned char **v, size_t *vlen)
{
unsigned char q;
size_t start;
if (!frx_lex_starts(l, l->pos, name)) {
if (!l->eof && frx_lex_short(l, l->pos, name)) l->need_more = 1;
return 0;
}
l->pos += strlen(name);
frx_lex_skip_s(l);
if (l->pos >= l->len) { if (!l->eof) l->need_more = 1; return 0; }
if (l->in[l->pos] != '=') return 0;
l->pos++;
frx_lex_skip_s(l);
if (l->pos >= l->len) { if (!l->eof) l->need_more = 1; return 0; }
q = l->in[l->pos];
if (q != '"' && q != '\'') return 0;
start = ++l->pos;
while (l->pos < l->len && l->in[l->pos] != q) l->pos++;
if (l->pos >= l->len) { if (!l->eof) l->need_more = 1; return 0; }
*v = l->in + start;
*vlen = l->pos - start;
l->pos++;
return 1;
}
static int
frx_lex_ieq(const unsigned char *v, size_t n, const char *lit)
{
size_t i;
if (n != strlen(lit)) return 0;
for (i = 0; i < n; i++) {
unsigned char a = v[i], b = (unsigned char)lit[i];
if (a >= 'A' && a <= 'Z') a = (unsigned char)(a - 'A' + 'a');
if (a != b) return 0;
}
return 1;
}
/* the prolog: max_bytes, the BOMs, the first pair, the declaration. With
* eof clear it asks for more bytes wherever the declaration may go on. */
#define FRX_PROLOG_NEED(l, what) \
(((l)->need_more || ((l)->pos >= (l)->len && !(l)->eof)) ? ((l)->need_more = 1, 0) : FRX_LEX_FAIL((l), FRX_E_SYNTAX, at, (what)))
static int
frx_lex_prolog(frx_lex *l)
{
const unsigned char *in = l->in;
size_t len = l->len;
const unsigned char *v;
size_t vlen, had_s;
if (l->prolog_max_bytes && len > l->prolog_max_bytes)
return FRX_LEX_FAIL(l, FRX_E_TOO_LARGE, l->prolog_max_bytes, "input exceeds max_bytes");
/* the marks below need four bytes, and a declaration is judged whole */
if (!l->eof && (len < 4 || frx_lex_short(l, 0, "<?xml "))) { l->need_more = 1; return 0; }
if (len >= 2 && ((in[0] == 0xFE && in[1] == 0xFF) || (in[0] == 0xFF && in[1] == 0xFE)))
return FRX_LEX_FAIL(l, FRX_E_ENCODING, 0, "UTF-16 or UTF-32 byte order mark; only UTF-8 is accepted");
if (len >= 4 && in[0] == 0 && in[1] == 0 && in[2] == 0xFE && in[3] == 0xFF)
return FRX_LEX_FAIL(l, FRX_E_ENCODING, 0, "UTF-32 byte order mark; only UTF-8 is accepted");
if (len >= 2 && ((in[0] == '<' && in[1] == 0) || (in[0] == 0 && in[1] == '<')))
return FRX_LEX_FAIL(l, FRX_E_ENCODING, 0, "UTF-16 without a byte order mark; only UTF-8 is accepted");
if (len >= 3 && in[0] == 0xEF && in[1] == 0xBB && in[2] == 0xBF)
l->pos = 3;
/* the declaration: <?xml S version S? = S? "1.0" (S encoding ...)? (S standalone ...)? S? ?> */
if (frx_lex_starts(l, l->pos, "<?xml") && l->pos + 5 < len && frx_is_s(in[l->pos + 5])) {
size_t at = l->pos;
l->pos += 5;
frx_lex_skip_s(l);
if (!frx_lex_pseudo(l, "version", &v, &vlen))
return FRX_PROLOG_NEED(l, "the XML declaration must start with version");
if (vlen == 3 && memcmp(v, "1.1", 3) == 0 && l->profile == FRX_PROFILE_FULL) {
l->version11 = 1; /* full only; strict keeps 0.01's refusal */
} else if (vlen == 3 && memcmp(v, "1.0", 3) == 0) {
/* the one version both profiles know */
} else if (l->profile == FRX_PROFILE_FULL && vlen > 2 && v[0] == '1' && v[1] == '.') {
/* XML 1.0 fifth edition section 2.8: a 1.x this processor does
* not know is processed as 1.0; the declared string is kept */
size_t k;
for (k = 2; k < vlen; k++)
if (v[k] < '0' || v[k] > '9')
return FRX_LEX_FAIL(l, FRX_E_SYNTAX, at, "only XML version 1.0 is accepted");
} else {
return FRX_LEX_FAIL(l, FRX_E_SYNTAX, at, "only XML version 1.0 is accepted");
}
if (!frx_lex_intern(l, v, vlen, &l->version)) return 0;
had_s = frx_lex_skip_s(l);
if (l->pos >= l->len)
return FRX_PROLOG_NEED(l, "unterminated XML declaration");
if (frx_lex_starts(l, l->pos, "encoding")) {
if (!had_s)
return FRX_LEX_FAIL(l, FRX_E_SYNTAX, l->pos, "expected whitespace before encoding in the XML declaration");
if (!frx_lex_pseudo(l, "encoding", &v, &vlen))
return FRX_PROLOG_NEED(l, "malformed encoding declaration");
if (l->profile == FRX_PROFILE_FULL) {
/* XML 1.0 4.3.3: the declaration must name what the
* document arrived in. What arrived was detected by
* frx_enc.h and the bytes here are already UTF-8. */
( run in 1.280 second using v1.01-cache-2.11-cpan-8dfa8b56332 )