leptris 1.9.377.0-aarch64-linux → 1.9.378.0-aarch64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +54 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/SECURITY.md +94 -0
- data/vendor-src/libleptris/TODO.SOTA.md +25 -3
- data/vendor-src/libleptris/docs/MISSION-PLAN.md +12 -0
- data/vendor-src/libleptris/scripts/check_shared_exports.sh +11 -7
- data/vendor-src/libleptris/src/leptris/dom/element_modify.c +21 -0
- data/vendor-src/libleptris/src/leptris/html/html_parse.c +248 -20
- data/vendor-src/libleptris/test/CMakeLists.txt +22 -7
- data/vendor-src/libleptris/test/html/test_html5lib.cpp +14 -2
- data/vendor-src/libleptris/test/xpath/test_xpath.cpp +105 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2ba79bde3af1d4d761b8ef2c76181e70bb301c495580df031c9ec1367e3b8162
|
|
4
|
+
data.tar.gz: 8a8a3cf775405cde5b8ee9afa1f86f442cf1c1bf06bcc2f7c527a3acf401ffa0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dd19893f0914c2360defb6cfac75de98d375533c6f677b89a711857a84bbc56dfe28f70a7dc5464db8b5084b1b96864e09b5fd071f0ab5a6b2c027406b9b2a5e
|
|
7
|
+
data.tar.gz: 4403b111e2d1eee3bbcab572b49ee159cd51c8dd92841d88d656efe766e3e8d82f1e806483ef04d9ec89192d561f3f1669094153122ef1d25e8ac1e5135e908d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## [1.9.378.0] - 2026-10-21
|
|
2
|
+
|
|
3
|
+
### Fixed — engine sync (libleptris 1.9.378)
|
|
4
|
+
|
|
5
|
+
- **Node-set XPath after sibling insertion (leptris-ruby#519,
|
|
6
|
+
leptris/leptris#1802)**: the lazy element index was invalidated
|
|
7
|
+
by `add_child` but not by the sibling/prepend splice paths, so
|
|
8
|
+
`xpath("//name")` after `add_next_sibling` answered from the
|
|
9
|
+
pre-insert tree until an unrelated child insert rebuilt it.
|
|
10
|
+
`insert_before`, `insert_after` and `prepend_child` now
|
|
11
|
+
invalidate the document index.
|
|
12
|
+
|
|
13
|
+
### Changed — engine sync
|
|
14
|
+
|
|
15
|
+
- **WHATWG HTML verdict parity complete (leptris/leptris#1803)**:
|
|
16
|
+
1555/1555 corpus documents now report parse errors exactly when
|
|
17
|
+
html5lib does, with tree parity unchanged at 1555/1555.
|
|
18
|
+
|
|
1
19
|
## [1.9.356.0] - 2026-10-14
|
|
2
20
|
|
|
3
21
|
### Fixed — engine sync (libleptris 1.9.356)
|
data/Rakefile
CHANGED
|
@@ -8,7 +8,7 @@ RSpec::Core::RakeTask.new(:spec)
|
|
|
8
8
|
# Pin for `rake compile` and the platform-gem builds. Keep in lockstep
|
|
9
9
|
# with .github/workflows/build.yml (which calls `rake compile`) and the
|
|
10
10
|
# CHANGELOG when libleptris releases.
|
|
11
|
-
LIBLEPTRIS_VERSION = "1.9.
|
|
11
|
+
LIBLEPTRIS_VERSION = "1.9.378"
|
|
12
12
|
# Vendored alongside libleptris for fn:normalize-unicode (TODO
|
|
13
13
|
# .restructure/20): built per platform with a RELOCATABLE @rpath
|
|
14
14
|
# install name, loaded by ffi.rb before libleptris so the
|
data/lib/leptris/version.rb
CHANGED
data/lib/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.378] - 2026-10-21
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **SECURITY.md: private reporting channel and CVE-response note
|
|
8
|
+
(PR #1804).** Reports go through GitHub Security Advisories, with
|
|
9
|
+
an acknowledgment target, coordinated disclosure, and CVE
|
|
10
|
+
assignment when warranted. The supported-version table follows
|
|
11
|
+
the lockstep rule: a fix is delivered only when the binding
|
|
12
|
+
release carries the fixed engine. The hardening inventory records
|
|
13
|
+
what actually ships: zero required runtime dependencies,
|
|
14
|
+
ASAN+leak and macOS `leaks` lanes, nightly libFuzzer on the
|
|
15
|
+
parser, explicit depth/size limits, and the conformance suites.
|
|
16
|
+
- **Test suite runs in parallel without changing any test (PR
|
|
17
|
+
#1799).** `gtest_discover_tests` now bakes the case list at build
|
|
18
|
+
time for native builds (`POST_BUILD`; cross builds keep
|
|
19
|
+
`PRE_TEST`), so `ctest --parallel` expands real cases instead of
|
|
20
|
+
`_NOT_BUILT` placeholders. CI runs the native and matrix lanes at
|
|
21
|
+
`--parallel 4` (qemu s390x and ILP32 stay serial). The CLI test
|
|
22
|
+
helper passed `WORKING_DIRECTORY` inside `PROPERTIES`, which
|
|
23
|
+
silently dropped the following `RUN_SERIAL`; passing it as a
|
|
24
|
+
keyword restores the serialization. Same 2074 cases, all green:
|
|
25
|
+
66.0 s serial → 31.0 s `-j4` locally; the export check alone
|
|
26
|
+
20.7-23.3 s → 8.7-9.3 s.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **dom: node-set XPath no longer answers from a stale element
|
|
31
|
+
index after sibling insertion (leptris-ruby#519, PR #1802).** The
|
|
32
|
+
lazy TODO 132 index was invalidated by `append_child` but not by
|
|
33
|
+
`insert_before`, `insert_after` or `prepend_child`, so a
|
|
34
|
+
`//name` query after `add_next_sibling` missed the new element
|
|
35
|
+
until an unrelated child insert rebuilt the index. All three
|
|
36
|
+
splice paths now invalidate the document index; the regression
|
|
37
|
+
spec covers each path plus the compiled-handle FFI face the
|
|
38
|
+
bindings evaluate with.
|
|
39
|
+
- **html: WHATWG verdict parity is complete — 1555/1555, zero
|
|
40
|
+
misses (PR #1803).** The remaining 20 corpus documents now report
|
|
41
|
+
at their html5lib source sites: `adoption-agency-1.3` for
|
|
42
|
+
formatting end tags whose element is in scope but not the current
|
|
43
|
+
node; the `</menuitem>` endTagOther pair (stray, and found with
|
|
44
|
+
non-implied open elements above); tokenizer
|
|
45
|
+
`expected-dashes-or-doctype`,
|
|
46
|
+
`unexpected-character-after-solidus-in-tag` and
|
|
47
|
+
`attributes-in-end-tag`; AfterAfterFrameset trailing characters,
|
|
48
|
+
BeforeHead `end-tag-after-implied-root`, BeforeHtml
|
|
49
|
+
`unexpected-end-tag-before-html`, and foreign-content end-tag
|
|
50
|
+
mismatch; `unexpected-form-in-table` for both the insertion and
|
|
51
|
+
the ignored duplicate plus the `</form>` table-voodoo pair;
|
|
52
|
+
template `foster-parenting-character`; and
|
|
53
|
+
`two-heads-are-not-better-than-one`. Tree parity stays
|
|
54
|
+
1555/1555; the verdict floor is pinned at the full corpus count.
|
|
55
|
+
|
|
56
|
+
|
|
3
57
|
## [1.9.377] - 2026-10-20
|
|
4
58
|
|
|
5
59
|
### Added
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
libleptris is a parser and transformation engine: it reads untrusted
|
|
4
|
+
documents. Memory safety and predictable resource behavior on hostile
|
|
5
|
+
input are part of the product, not an afterthought.
|
|
6
|
+
|
|
7
|
+
## Supported versions
|
|
8
|
+
|
|
9
|
+
Security fixes land on the latest release of the current major line and
|
|
10
|
+
are shipped in the next patch release. The engine follows the lockstep
|
|
11
|
+
runtime rule — binding versions are `{engine-semver}.{binding-patch}` —
|
|
12
|
+
so a fix is considered delivered only when the corresponding binding
|
|
13
|
+
release carries the fixed engine.
|
|
14
|
+
|
|
15
|
+
| Version | Supported |
|
|
16
|
+
|---|---|
|
|
17
|
+
| latest `1.x` release | yes |
|
|
18
|
+
| any earlier release/tag | no (upgrade to the latest patch) |
|
|
19
|
+
|
|
20
|
+
## Reporting a vulnerability
|
|
21
|
+
|
|
22
|
+
**Do not open a public issue for a security report.**
|
|
23
|
+
|
|
24
|
+
Use GitHub's private vulnerability reporting on this repository:
|
|
25
|
+
**Security → Advisories → Report a vulnerability** (direct link:
|
|
26
|
+
<https://github.com/leptris/leptris/security/advisories/new>).
|
|
27
|
+
|
|
28
|
+
Please include, when available:
|
|
29
|
+
|
|
30
|
+
- the engine version (or binding version and its pinned engine),
|
|
31
|
+
- the input document/expression that triggers the problem,
|
|
32
|
+
- the build configuration (sanitizer output, platform, compiler),
|
|
33
|
+
- the observed impact (crash, out-of-bounds access, infinite loop,
|
|
34
|
+
excessive memory, code execution, information disclosure).
|
|
35
|
+
|
|
36
|
+
If you report a bug that affects a binding, include both the binding
|
|
37
|
+
version and the engine version it pins; the engine is the shared
|
|
38
|
+
implementation and the fix belongs there first.
|
|
39
|
+
|
|
40
|
+
## What to expect
|
|
41
|
+
|
|
42
|
+
- **Acknowledgment** within 5 business days.
|
|
43
|
+
- **Triage and reproduction** in the private advisory thread. We may
|
|
44
|
+
ask for the exact input and a minimized reproducer.
|
|
45
|
+
- **Fix and coordinated disclosure.** The fix lands on a private or
|
|
46
|
+
public branch as circumstances require, is released through the
|
|
47
|
+
normal release workflow, and the advisory is published with credit
|
|
48
|
+
(unless you prefer to remain anonymous).
|
|
49
|
+
- **CVE assignment** is requested through the GitHub advisory when the
|
|
50
|
+
issue warrants one.
|
|
51
|
+
|
|
52
|
+
Please give us a reasonable window to ship a patch before public
|
|
53
|
+
disclosure. We will keep you informed of the timeline in the advisory
|
|
54
|
+
thread.
|
|
55
|
+
|
|
56
|
+
## In scope
|
|
57
|
+
|
|
58
|
+
- The C engine: XML/HTML parsing, DTD validation, XPath, XSLT,
|
|
59
|
+
XInclude, serialization, encoding conversion, SAX and pull APIs.
|
|
60
|
+
- The shipped CLI (`leptris`) and public C API surface.
|
|
61
|
+
- The FFI layer and descriptor surface consumed by the language
|
|
62
|
+
bindings (leptris-ruby, leptris-py, leptris-ts, leptris-rs).
|
|
63
|
+
- Resource-exhaustion behavior on untrusted input (unbounded
|
|
64
|
+
recursion, allocation amplification, algorithmic complexity) when it
|
|
65
|
+
is reachable without a misconfiguration.
|
|
66
|
+
|
|
67
|
+
## Out of scope
|
|
68
|
+
|
|
69
|
+
- Vulnerabilities in third-party dependencies of the language
|
|
70
|
+
bindings, unless the engine's handling of their output is unsafe.
|
|
71
|
+
- Builds with sanitizers or assertions disabled by the reporter.
|
|
72
|
+
- Reports that require a modified engine binary.
|
|
73
|
+
- Denial of service by simply feeding a very large document when the
|
|
74
|
+
configured limits are explicitly disabled by the caller.
|
|
75
|
+
|
|
76
|
+
## Hardening in the repository
|
|
77
|
+
|
|
78
|
+
- **Zero required runtime dependencies** by default; `utf8proc` and
|
|
79
|
+
`iconv` are optional compile-time features.
|
|
80
|
+
- **Sanitizers in CI**: AddressSanitizer with leak detection on Linux
|
|
81
|
+
(`asan.yml`), plus the macOS `leaks` lane.
|
|
82
|
+
- **Fuzzing**: nightly libFuzzer runs over the parser
|
|
83
|
+
(`fuzz-nightly.yml`), with the crash corpus triaged into regression
|
|
84
|
+
specs; the XPath and mutation harnesses live alongside it in
|
|
85
|
+
`test/fuzz/`.
|
|
86
|
+
- **Depth and size limits** enforced by the parser; all limits are
|
|
87
|
+
explicit and documented in the public headers.
|
|
88
|
+
- **Conformance suites** (W3C XPath, libxslt, WHATWG html5lib, XSD,
|
|
89
|
+
DTD) run on every change, so hardening fixes cannot regress silently.
|
|
90
|
+
|
|
91
|
+
## Recognition
|
|
92
|
+
|
|
93
|
+
We are happy to credit reporters in the published advisory and in the
|
|
94
|
+
release notes. Thank you for helping keep document parsing safe.
|
|
@@ -28,9 +28,23 @@ docs/MISSION-PLAN.md and is updated at every release.
|
|
|
28
28
|
## Gate A — Conformance: zero reds everywhere
|
|
29
29
|
|
|
30
30
|
### A1 WHATWG verdict parity: 1394/1555 → 1555/1555
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
**Measured 2026-10-21 (main after #1800): 1535/1555 verdict,
|
|
32
|
+
1555/1555 tree, zero false positives; floor pinned at 1535.** The
|
|
33
|
+
remaining 20 verdict misses: adoption-agency (tests2:10, tests19:98,
|
|
34
|
+
tests19:99, tests26:1, tests26:9), menuitem strays (menuitem:10/12),
|
|
35
|
+
table/caption singletons (tests9:16, tests10:15, tests16:197,
|
|
36
|
+
pending-spec-changes:2, tests20:48), tokenizer singletons (tests10:2,
|
|
37
|
+
tests2:18, tests2:58, tests19:41, tests19:82) and two-heads
|
|
38
|
+
(tests3:13). The remaining ~270 code-set divergences are prose-dialect
|
|
39
|
+
labels from older corpus datasets; the verdict gate is presence-only.
|
|
40
|
+
The slices already shipped: initial mode, raw-text EOF, unterminated
|
|
41
|
+
close, frameset, fenced walk, InBody EOF allowed-set, in-select,
|
|
42
|
+
foster-by-kind, unknown-doctype (#1778-#1791), raw-text strays,
|
|
43
|
+
non-html-root, end-tag-too-early, after-body/frameset phases,
|
|
44
|
+
start-tag phases, select/table/foreign, need-space-after-doctype
|
|
45
|
+
(#1794), misplaced/stray-p, after-html codes, image (#1797, #1800).
|
|
46
|
+
Ground every further slice in html5lib's actual source
|
|
47
|
+
(~/src/external/html5lib):
|
|
34
48
|
- [ ] escaped-state EOF codes (`unexpected-eof-in-text-mode`,
|
|
35
49
|
`eof-in-script-in-script`) — the script-data state machine's
|
|
36
50
|
EOF classification
|
|
@@ -76,6 +90,14 @@ actual source (~/src/external/html5lib):
|
|
|
76
90
|
## Gate B — Performance: every published row ≥1.00x
|
|
77
91
|
|
|
78
92
|
### B1 build row: ~2.9x → ≤1.0x (the #242 campaign)
|
|
93
|
+
**Measured 2026-10-21: 1.86-1.92x** (moxml `sota/04-performance`
|
|
94
|
+
branch + leptris 1.9.376.0; harness `wt/460-harness/build_bench.rb`,
|
|
95
|
+
31 interleaved reps: 4.08ms vs Nokogiri 2.20ms for 250 records).
|
|
96
|
+
Was 2.95x at the 1.9.363 era; the add_child alias and same-doc fast
|
|
97
|
+
path landed since. StackProf attribution: `NativeNode.create_element`
|
|
98
|
+
14.6%, GC mark+sweep 19%, `prime_contract` 7.7%, attach path 45% of
|
|
99
|
+
tree; the `invalidate_namespace_cache!` line attribution is a
|
|
100
|
+
sampling artifact (deleting both attach invalidations gains ~5%).
|
|
79
101
|
Harness: wt/460-harness/build_bench.rb. Decomposition on record.
|
|
80
102
|
- [ ] per-element object budget: one Ruby object per created node
|
|
81
103
|
(drop the shell entirely post-create-priming; the mint cache
|
|
@@ -161,3 +161,15 @@ One table, updated at every release train: gate, command, last run,
|
|
|
161
161
|
verdict. When every row of Gate A and Gate B reads "green (dated)"
|
|
162
162
|
and Gate D1 is green, the mission is achieved — and v1.0.0 is the
|
|
163
163
|
tag that says so.
|
|
164
|
+
|
|
165
|
+
| Gate | Command | Last run | Verdict |
|
|
166
|
+
|------|---------|----------|---------|
|
|
167
|
+
| A1 WHATWG tree parity | `test_html5lib --gtest_filter=Html5LibCorpus.*` | 2026-10-21 | green, 1555/1555 |
|
|
168
|
+
| A1 WHATWG verdict parity | `Html5LibCorpus.ErrorVerdictParity` | 2026-10-21 | 1535/1555 (floor 1535; **red**) |
|
|
169
|
+
| A7 libxslt | `LibxsltSuite` | 2026-10-20 | 205/205 (204 + 1 macOS skip) |
|
|
170
|
+
| B1 build row | `wt/460-harness/build_bench.rb` | 2026-10-21 | 1.86-1.92x (**red**, sota/04 branch) |
|
|
171
|
+
| B2 walk row | `wt/460-harness/walk_read_bench.rb` | 2026-10-20 | 0.88x (green) |
|
|
172
|
+
| Test-suite runtime (fidelity) | `ctest`, 2074 cases | 2026-10-21 | 66.0 s serial → 31.0 s `-j4` (same 2074/2074; PR #1799) |
|
|
173
|
+
| Rides 1.9.377 | registries | 2026-10-21 | rubygems/PyPI 1.9.377.0, npm 0.1.21 |
|
|
174
|
+
| Issues | all repos | 2026-10-21 | 0 open (ruby#514/#515 closed with gem verification) |
|
|
175
|
+
|
|
@@ -26,7 +26,7 @@ cmake -B "$BUILD" -S "$ROOT" \
|
|
|
26
26
|
-DLEPTRIS_BUILD_BENCHMARKS=OFF \
|
|
27
27
|
> /dev/null
|
|
28
28
|
|
|
29
|
-
cmake --build "$BUILD" --target leptris_shared > /dev/null
|
|
29
|
+
cmake --build "$BUILD" --target leptris_shared --parallel > /dev/null
|
|
30
30
|
|
|
31
31
|
LIB=$(find "$BUILD" -name 'libleptris.*.dylib' -o -name 'libleptris.so*' \
|
|
32
32
|
2>/dev/null | grep -v '[0-9]\.dylib$' | head -1)
|
|
@@ -98,13 +98,17 @@ REQUIRED=(
|
|
|
98
98
|
)
|
|
99
99
|
|
|
100
100
|
rc=0
|
|
101
|
+
# One nm pass instead of one per symbol: the export table is a few KB
|
|
102
|
+
# but nm re-reads the whole dylib each time (~55 spawns + reads).
|
|
103
|
+
# macOS: nm -gU; Linux: nm -D --defined-only. The macOS spelling
|
|
104
|
+
# prefixes symbols with '_', hence the optional underscore.
|
|
105
|
+
EXPORTS=$(mktemp)
|
|
106
|
+
trap 'rm -f "$EXPORTS"' EXIT
|
|
107
|
+
if ! nm -gU "$LIB" > "$EXPORTS" 2>/dev/null; then
|
|
108
|
+
nm -D --defined-only "$LIB" > "$EXPORTS" 2>/dev/null || true
|
|
109
|
+
fi
|
|
101
110
|
for sym in "${REQUIRED[@]}"; do
|
|
102
|
-
|
|
103
|
-
# macOS: nm -gU <lib>
|
|
104
|
-
# Linux: nm -D --defined-only <lib>
|
|
105
|
-
if nm -gU "$LIB" 2>/dev/null | grep -qE " _?${sym}\$"; then
|
|
106
|
-
echo " OK $sym"
|
|
107
|
-
elif nm -D --defined-only "$LIB" 2>/dev/null | grep -qE " ${sym}\$"; then
|
|
111
|
+
if grep -qE "[[:space:]]_?${sym}\$" "$EXPORTS"; then
|
|
108
112
|
echo " OK $sym"
|
|
109
113
|
else
|
|
110
114
|
echo " MISS $sym"
|
|
@@ -543,6 +543,13 @@ LeptrisStatus leptris_element_prepend_child(LeptrisElement parent, LeptrisElemen
|
|
|
543
543
|
/* Call internal void function, assume success */
|
|
544
544
|
leptris_element_prepend_child_internal(parent, (LeptrisNode*)child);
|
|
545
545
|
leptris_element_invalidate_child_cache(parent);
|
|
546
|
+
|
|
547
|
+
/* TODO 132: the child splice changed the element set — a cached
|
|
548
|
+
* index would describe the pre-insert tree (leptris-ruby #519). */
|
|
549
|
+
if (leptris_element_get_document(parent)) {
|
|
550
|
+
leptris_element_index_invalidate(
|
|
551
|
+
leptris_element_get_document(parent));
|
|
552
|
+
}
|
|
546
553
|
return LEPTRIS_OK;
|
|
547
554
|
}
|
|
548
555
|
|
|
@@ -671,6 +678,13 @@ LeptrisStatus leptris_element_insert_before(LeptrisElement sibling, LeptrisEleme
|
|
|
671
678
|
|
|
672
679
|
leptris_node_increment_version(LEPTRIS_ELEMENT_AS_NODE(parent));
|
|
673
680
|
leptris_element_invalidate_child_cache(parent);
|
|
681
|
+
|
|
682
|
+
/* TODO 132: the sibling splice changed the element set — a cached
|
|
683
|
+
* index would describe the pre-insert tree (leptris-ruby #519). */
|
|
684
|
+
if (leptris_element_get_document(parent)) {
|
|
685
|
+
leptris_element_index_invalidate(
|
|
686
|
+
leptris_element_get_document(parent));
|
|
687
|
+
}
|
|
674
688
|
return LEPTRIS_OK;
|
|
675
689
|
}
|
|
676
690
|
|
|
@@ -785,6 +799,13 @@ LeptrisStatus leptris_element_insert_after(LeptrisElement sibling, LeptrisElemen
|
|
|
785
799
|
|
|
786
800
|
leptris_node_increment_version(LEPTRIS_ELEMENT_AS_NODE(parent));
|
|
787
801
|
leptris_element_invalidate_child_cache(parent);
|
|
802
|
+
|
|
803
|
+
/* TODO 132: the sibling splice changed the element set — a cached
|
|
804
|
+
* index would describe the pre-insert tree (leptris-ruby #519). */
|
|
805
|
+
if (leptris_element_get_document(parent)) {
|
|
806
|
+
leptris_element_index_invalidate(
|
|
807
|
+
leptris_element_get_document(parent));
|
|
808
|
+
}
|
|
788
809
|
return LEPTRIS_OK;
|
|
789
810
|
}
|
|
790
811
|
|
|
@@ -4035,10 +4035,13 @@ static void h_append(HBuilder* b, LeptrisNodeRef n) {
|
|
|
4035
4035
|
if (*q != ' ' && *q != '\t' && *q != '\n' &&
|
|
4036
4036
|
*q != '\r') { ws = 0; break; }
|
|
4037
4037
|
if (!ws) {
|
|
4038
|
-
/* AfterFrameset processCharacters
|
|
4039
|
-
*
|
|
4040
|
-
|
|
4041
|
-
|
|
4038
|
+
/* AfterFrameset/AfterAfterFrameset processCharacters
|
|
4039
|
+
* both report and DROP the run (no phase switch). */
|
|
4040
|
+
if (b->after_html)
|
|
4041
|
+
h_diag(b, "expected-eof-but-got-char",
|
|
4042
|
+
b->err_line_memo_off,
|
|
4043
|
+
"characters after </html> in the frameset phase");
|
|
4044
|
+
else
|
|
4042
4045
|
h_diag(b, "unexpected-char-after-frameset",
|
|
4043
4046
|
b->err_line_memo_off,
|
|
4044
4047
|
"characters after </frameset>");
|
|
@@ -4068,12 +4071,15 @@ static void h_append(HBuilder* b, LeptrisNodeRef n) {
|
|
|
4068
4071
|
b->after_body_done = 1;
|
|
4069
4072
|
/* AfterAfterBody/AfterAfterFrameset processCharacters
|
|
4070
4073
|
* report expected-eof-but-got-char; AfterBody reports
|
|
4071
|
-
* unexpected-char-after-body (tests2:55, tests19:41).
|
|
4072
|
-
|
|
4074
|
+
* unexpected-char-after-body (tests2:55, tests19:41).
|
|
4075
|
+
* AfterAfterBody then switches to in-body and inserts
|
|
4076
|
+
* the run — clear the phase flags so later comments are
|
|
4077
|
+
* body content, not document epilog. */
|
|
4078
|
+
if (b->after_html) {
|
|
4073
4079
|
h_diag(b, "expected-eof-but-got-char",
|
|
4074
4080
|
b->err_line_memo_off,
|
|
4075
4081
|
"characters after </html>");
|
|
4076
|
-
else
|
|
4082
|
+
} else
|
|
4077
4083
|
h_diag(b, "unexpected-char-after-body",
|
|
4078
4084
|
b->err_line_memo_off,
|
|
4079
4085
|
"characters after </body>");
|
|
@@ -4214,6 +4220,31 @@ static void h_append(HBuilder* b, LeptrisNodeRef n) {
|
|
|
4214
4220
|
b->err_line_memo_off,
|
|
4215
4221
|
"hidden input inserted in table context");
|
|
4216
4222
|
}
|
|
4223
|
+
/* Template content whose last node is a table-context
|
|
4224
|
+
* element inserts text as that node's sibling (the template
|
|
4225
|
+
* flavor of foster parenting). html5lib still reports the
|
|
4226
|
+
* foster (template.dat:112: <template><tr>…</tr>a). */
|
|
4227
|
+
if (b->whatwg && b->whatwg_foster &&
|
|
4228
|
+
leptris_node_get_type(n) == LEPTRIS_NODE_TYPE_TEXT &&
|
|
4229
|
+
b->depth > 0 &&
|
|
4230
|
+
b->open_id[b->depth - 1] == h_id_template) {
|
|
4231
|
+
const char* tc =
|
|
4232
|
+
leptris_text_node_get_content((LeptrisTextNode*)n);
|
|
4233
|
+
int nonws = 0;
|
|
4234
|
+
if (tc)
|
|
4235
|
+
for (const char* w = tc; *w; w++)
|
|
4236
|
+
if (*w != ' ' && *w != '\t' && *w != '\n' &&
|
|
4237
|
+
*w != '\r') { nonws = 1; break; }
|
|
4238
|
+
LeptrisNodeRef last = leptris_node_last_child_internal(
|
|
4239
|
+
(LeptrisNode*)b->open[b->depth - 1]);
|
|
4240
|
+
if (nonws && last &&
|
|
4241
|
+
leptris_node_get_type(last) ==
|
|
4242
|
+
LEPTRIS_NODE_TYPE_ELEMENT &&
|
|
4243
|
+
h_is_table_context((LeptrisElement)last))
|
|
4244
|
+
h_diag(b, "foster-parenting-character",
|
|
4245
|
+
b->err_line_memo_off,
|
|
4246
|
+
"text fostered in template table content");
|
|
4247
|
+
}
|
|
4217
4248
|
/* #659 foster (WHATWG only): text/elements in table context
|
|
4218
4249
|
* go before the nearest open table in ITS parent. */
|
|
4219
4250
|
if (b->whatwg_foster && tbl_ctx && h_fosterable(b, n)) {
|
|
@@ -4652,7 +4683,7 @@ static void h_reconstruct(HBuilder* b) {
|
|
|
4652
4683
|
/* The adoption agency proper. Returns 1 when the end tag is fully
|
|
4653
4684
|
* consumed; 0 means no formatting entry matched and the caller
|
|
4654
4685
|
* runs its generic (any-other-end-tag) handling. */
|
|
4655
|
-
static int h_afe_end(HBuilder* b, const char* subject) {
|
|
4686
|
+
static int h_afe_end(HBuilder* b, const char* subject, size_t off) {
|
|
4656
4687
|
if (b->depth > 0) {
|
|
4657
4688
|
LeptrisElement cur = b->open[b->depth - 1];
|
|
4658
4689
|
const char* cn = leptris_element_name(cur);
|
|
@@ -4703,6 +4734,14 @@ static int h_afe_end(HBuilder* b, const char* subject) {
|
|
|
4703
4734
|
return 1;
|
|
4704
4735
|
}
|
|
4705
4736
|
}
|
|
4737
|
+
/* html5lib adoption-agency-1.3: the formatting element is
|
|
4738
|
+
* in the stack and in scope, but open elements sit above
|
|
4739
|
+
* it (it is not the current node). Parsing proceeds. */
|
|
4740
|
+
if (b->whatwg_adopt && b->depth != (size_t)si + 1) {
|
|
4741
|
+
h_diag(b, "adoption-agency-1.3", off,
|
|
4742
|
+
"</%s> formatting element is not the current node",
|
|
4743
|
+
subject);
|
|
4744
|
+
}
|
|
4706
4745
|
int fbi = -1;
|
|
4707
4746
|
for (size_t k = (size_t)si + 1; k < b->depth; k++) {
|
|
4708
4747
|
/* Only HTML-namespace elements are furthest-block
|
|
@@ -6107,6 +6146,15 @@ static LeptrisDocument html_parse_shared(
|
|
|
6107
6146
|
}
|
|
6108
6147
|
p = (ce + 3 <= end) ? ce + 3 : end;
|
|
6109
6148
|
} else {
|
|
6149
|
+
/* Tokenizer markup-declaration-open: "<!" that
|
|
6150
|
+
* is neither a comment nor DOCTYPE (and, in
|
|
6151
|
+
* HTML content, not CDATA) reports
|
|
6152
|
+
* expected-dashes-or-doctype (tests10:2:
|
|
6153
|
+
* <![CDATA[a]]> outside foreign content). */
|
|
6154
|
+
if (b.whatwg)
|
|
6155
|
+
h_diag(&b, "expected-dashes-or-doctype",
|
|
6156
|
+
(size_t)(p - b.input_base),
|
|
6157
|
+
"<! markup declaration");
|
|
6110
6158
|
/* CDATA-ish bogus: skip to '>'. WHATWG makes
|
|
6111
6159
|
* it a bogus comment (tests1:42-49) — except
|
|
6112
6160
|
* doctype-shaped constructs, which the
|
|
@@ -6217,6 +6265,20 @@ static LeptrisDocument html_parse_shared(
|
|
|
6217
6265
|
const char* q = ns;
|
|
6218
6266
|
while (q < end && !h_is_ws(*q) && *q != '>' && *q != '/') q++;
|
|
6219
6267
|
size_t nlen = (size_t)(q - ns);
|
|
6268
|
+
/* Tokenizer: an end tag may carry attributes
|
|
6269
|
+
* syntactically (they are ignored at tree time, but
|
|
6270
|
+
* the token reports attributes-in-end-tag;
|
|
6271
|
+
* tests2:18's </b test<b ...>). A lone "/" before
|
|
6272
|
+
* ">" is the self-closing form, not an attribute. */
|
|
6273
|
+
{
|
|
6274
|
+
const char* tq = q;
|
|
6275
|
+
while (tq < end && h_is_ws(*tq)) tq++;
|
|
6276
|
+
if (tq < end && *tq != '>' &&
|
|
6277
|
+
!(*tq == '/' && tq + 1 < end && tq[1] == '>'))
|
|
6278
|
+
h_diag(&b, "attributes-in-end-tag",
|
|
6279
|
+
(size_t)(tq - b.input_base),
|
|
6280
|
+
"attributes in an end tag");
|
|
6281
|
+
}
|
|
6220
6282
|
while (q < end && *q != '>') q++;
|
|
6221
6283
|
if (q < end) q++;
|
|
6222
6284
|
/* Flush pending text before closing. A
|
|
@@ -6254,6 +6316,17 @@ static LeptrisDocument html_parse_shared(
|
|
|
6254
6316
|
for (size_t i = 0; i < cl0; i++)
|
|
6255
6317
|
lname0[i] = h_lower(ns[i]);
|
|
6256
6318
|
lname0[cl0] = 0;
|
|
6319
|
+
/* html5lib InForeignContent.processEndTag: an end
|
|
6320
|
+
* tag that does not name the current foreign node
|
|
6321
|
+
* reports before the walk (tests19:82: <math></p>). */
|
|
6322
|
+
{
|
|
6323
|
+
const char* topn = leptris_element_name(
|
|
6324
|
+
b.open[b.depth - 1]);
|
|
6325
|
+
if (topn && !h_ieq_raw(topn, lname0))
|
|
6326
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6327
|
+
(size_t)(p - b.input_base),
|
|
6328
|
+
"</%s> in foreign content", lname0);
|
|
6329
|
+
}
|
|
6257
6330
|
int match_foreign = 0, crossed_ip = 0;
|
|
6258
6331
|
for (size_t d = b.depth; d > 0; d--) {
|
|
6259
6332
|
if (b.open_ns[d - 1] == H_NS_HTML) break;
|
|
@@ -6304,6 +6377,15 @@ static LeptrisDocument html_parse_shared(
|
|
|
6304
6377
|
text = p;
|
|
6305
6378
|
continue;
|
|
6306
6379
|
}
|
|
6380
|
+
/* AfterAfterBody/AfterAfterFrameset processEndTag: ANY
|
|
6381
|
+
* end tag is a phase error, and the token is then
|
|
6382
|
+
* reprocessed in body — report once and fall through
|
|
6383
|
+
* with the phase flags cleared (tests19:43). */
|
|
6384
|
+
if (b.whatwg && b.after_html) {
|
|
6385
|
+
h_diag(&b, "expected-eof-but-got-end-tag",
|
|
6386
|
+
(size_t)(p - b.input_base),
|
|
6387
|
+
"end tag after </html>");
|
|
6388
|
+
}
|
|
6307
6389
|
/* Stray end tags with no open stack. The raw-text family
|
|
6308
6390
|
* completes during start-tag handling; void elements and
|
|
6309
6391
|
* menuitem have no in-body end handler. html5lib's
|
|
@@ -6319,7 +6401,16 @@ static LeptrisDocument html_parse_shared(
|
|
|
6319
6401
|
int stray = h_is_rawtext_name(rn) ||
|
|
6320
6402
|
(h_is_void(rn) && strcmp(rn, "br") != 0) ||
|
|
6321
6403
|
strcmp(rn, "menuitem") == 0;
|
|
6322
|
-
|
|
6404
|
+
/* BeforeHtml endTagOther: every end tag other than
|
|
6405
|
+
* head/body/html/br reports here (html5lib
|
|
6406
|
+
* BeforeHtmlPhase; tests2:18's </b ...>). */
|
|
6407
|
+
int before_html = !b.top_tail && !b.head_tag_seen &&
|
|
6408
|
+
!b.body_seen && !b.body_tag_seen;
|
|
6409
|
+
int bb_exempt = strcmp(rn, "head") == 0 ||
|
|
6410
|
+
strcmp(rn, "body") == 0 ||
|
|
6411
|
+
strcmp(rn, "html") == 0 ||
|
|
6412
|
+
strcmp(rn, "br") == 0;
|
|
6413
|
+
if (stray || (before_html && !bb_exempt)) {
|
|
6323
6414
|
const char* code = "unexpected-end-tag";
|
|
6324
6415
|
if (b.after_html)
|
|
6325
6416
|
code = "expected-eof-but-got-end-tag";
|
|
@@ -6329,9 +6420,8 @@ static LeptrisDocument html_parse_shared(
|
|
|
6329
6420
|
code = "unexpected-end-tag-after-frameset";
|
|
6330
6421
|
else if (b.frameset)
|
|
6331
6422
|
code = "unexpected-end-tag-in-frameset";
|
|
6332
|
-
else if (
|
|
6333
|
-
|
|
6334
|
-
code = "end-tag-after-implied-root";
|
|
6423
|
+
else if (before_html)
|
|
6424
|
+
code = "unexpected-end-tag-before-html";
|
|
6335
6425
|
h_diag(&b, code, (size_t)(p - b.input_base),
|
|
6336
6426
|
"</%s> has no matching open element", rn);
|
|
6337
6427
|
p = q;
|
|
@@ -6339,6 +6429,39 @@ static LeptrisDocument html_parse_shared(
|
|
|
6339
6429
|
continue;
|
|
6340
6430
|
}
|
|
6341
6431
|
}
|
|
6432
|
+
/* InHead/AfterHead endTagOther: a </p> before body
|
|
6433
|
+
* content is dropped with unexpected-end-tag and
|
|
6434
|
+
* inserts no empty p (tests19:3). Depth-0 only — the
|
|
6435
|
+
* in-body stray-p rule below owns the body phase. */
|
|
6436
|
+
if (b.whatwg && nlen == 1 && h_lower(ns[0]) == 'p' &&
|
|
6437
|
+
b.depth == 0 && !b.body_seen &&
|
|
6438
|
+
(b.head_tag_seen || b.head_end_seen) &&
|
|
6439
|
+
!b.frameset && !b.after_body && !b.after_html)
|
|
6440
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6441
|
+
(size_t)(p - b.input_base),
|
|
6442
|
+
"</p> outside the body phase");
|
|
6443
|
+
/* BeforeHead endTagOther: before the head phase starts
|
|
6444
|
+
* (only the html element open), any end tag other than
|
|
6445
|
+
* head/body/html/br is end-tag-after-implied-root
|
|
6446
|
+
* (tests19:2: <html></p><!--foo-->). */
|
|
6447
|
+
if (b.whatwg && nlen && b.depth == 1 &&
|
|
6448
|
+
b.open_id[0] == h_id_html &&
|
|
6449
|
+
!b.head_tag_seen && !b.head_end_seen &&
|
|
6450
|
+
!b.body_seen && !b.body_tag_seen &&
|
|
6451
|
+
!b.frameset && !b.after_body && !b.after_html) {
|
|
6452
|
+
char* ph = h_pooled_lower(b.pool, ns, nlen);
|
|
6453
|
+
if (ph && strcmp(ph, "head") != 0 &&
|
|
6454
|
+
strcmp(ph, "body") != 0 &&
|
|
6455
|
+
strcmp(ph, "html") != 0 &&
|
|
6456
|
+
strcmp(ph, "br") != 0) {
|
|
6457
|
+
h_diag(&b, "end-tag-after-implied-root",
|
|
6458
|
+
(size_t)(p - b.input_base),
|
|
6459
|
+
"</%s> before the head phase", ph);
|
|
6460
|
+
p = q;
|
|
6461
|
+
text = p;
|
|
6462
|
+
continue;
|
|
6463
|
+
}
|
|
6464
|
+
}
|
|
6342
6465
|
if (nlen && (b.depth > 0 ||
|
|
6343
6466
|
(b.whatwg &&
|
|
6344
6467
|
((nlen == 4 &&
|
|
@@ -6376,7 +6499,7 @@ static LeptrisDocument html_parse_shared(
|
|
|
6376
6499
|
* at body); a SPECIAL element crossed
|
|
6377
6500
|
* ignores the tag (menuitem:8: x stays in
|
|
6378
6501
|
* the p). */
|
|
6379
|
-
int msp = 0;
|
|
6502
|
+
int msp = 0, implied_ok = 1;
|
|
6380
6503
|
for (size_t d2 = b.depth; d2 > 0; d2--) {
|
|
6381
6504
|
const char* on2 =
|
|
6382
6505
|
leptris_element_name(b.open[d2 - 1]);
|
|
@@ -6387,13 +6510,39 @@ static LeptrisDocument html_parse_shared(
|
|
|
6387
6510
|
break;
|
|
6388
6511
|
}
|
|
6389
6512
|
if (h_is_special_ww(on2)) break;
|
|
6513
|
+
if (!h_is_implied_end_name(on2))
|
|
6514
|
+
implied_ok = 0;
|
|
6390
6515
|
}
|
|
6391
|
-
|
|
6516
|
+
/* html5lib endTagOther reports in both
|
|
6517
|
+
* shapes: the walk found the element but
|
|
6518
|
+
* non-implied nodes sit above it, or the
|
|
6519
|
+
* walk stopped at a special (stray). */
|
|
6520
|
+
if (!msp)
|
|
6521
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6522
|
+
(size_t)(p - b.input_base),
|
|
6523
|
+
"</menuitem> is a stray end tag");
|
|
6524
|
+
else if (!implied_ok)
|
|
6525
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6526
|
+
(size_t)(p - b.input_base),
|
|
6527
|
+
"</menuitem> with open elements above");
|
|
6392
6528
|
p = q;
|
|
6393
6529
|
text = p;
|
|
6394
6530
|
continue;
|
|
6395
6531
|
}
|
|
6396
6532
|
if (b.whatwg && strcmp(lname, "form") == 0) {
|
|
6533
|
+
/* InTable endTagOther runs the body rule
|
|
6534
|
+
* under table voodoo: both codes report
|
|
6535
|
+
* (tests16:197: <form><table></form>). */
|
|
6536
|
+
if (b.depth > 0 &&
|
|
6537
|
+
h_id_table_ctx(b.open_id[b.depth - 1])) {
|
|
6538
|
+
h_diag(&b,
|
|
6539
|
+
"unexpected-end-tag-implies-table-voodoo",
|
|
6540
|
+
(size_t)(p - b.input_base),
|
|
6541
|
+
"</form> inside a table");
|
|
6542
|
+
h_diag(&b, "unexpected-end-tag",
|
|
6543
|
+
(size_t)(p - b.input_base),
|
|
6544
|
+
"</form> inside a table");
|
|
6545
|
+
}
|
|
6397
6546
|
/* Vendored rule: </form> closes implied
|
|
6398
6547
|
* end-tag layers (option/optgroup/li/dd/
|
|
6399
6548
|
* dt/p), then pops the form ONLY when it
|
|
@@ -6564,7 +6713,8 @@ static LeptrisDocument html_parse_shared(
|
|
|
6564
6713
|
* matched; otherwise the generic path below
|
|
6565
6714
|
* is the any-other-end-tag run. */
|
|
6566
6715
|
if (b.whatwg_adopt && h_is_formatting(lname) &&
|
|
6567
|
-
h_afe_end(&b, lname
|
|
6716
|
+
h_afe_end(&b, lname,
|
|
6717
|
+
(size_t)(p - b.input_base))) {
|
|
6568
6718
|
p = q;
|
|
6569
6719
|
text = p;
|
|
6570
6720
|
continue;
|
|
@@ -7255,9 +7405,14 @@ static LeptrisDocument html_parse_shared(
|
|
|
7255
7405
|
}
|
|
7256
7406
|
if (!name) goto done;
|
|
7257
7407
|
/* #659 (13.2.6.4.7): <image> is renamed <img> and
|
|
7258
|
-
* reprocessed (tests1:90)
|
|
7259
|
-
|
|
7408
|
+
* reprocessed (tests1:90); html5lib reports the legacy
|
|
7409
|
+
* spelling as image-start-tag (tests19:89). */
|
|
7410
|
+
if (b.whatwg && strcmp(name, "image") == 0) {
|
|
7411
|
+
h_diag(&b, "image-start-tag",
|
|
7412
|
+
(size_t)(p - b.input_base),
|
|
7413
|
+
"<image> acts as <img>");
|
|
7260
7414
|
name = h_pooled_lower(b.pool, "img", 3);
|
|
7415
|
+
}
|
|
7261
7416
|
/* #1218: ONE classification per start tag — every
|
|
7262
7417
|
* downstream consumer (frameset-ok, void, open) reads the
|
|
7263
7418
|
* resolved entry instead of re-walking the bucket. */
|
|
@@ -7367,7 +7522,41 @@ static LeptrisDocument html_parse_shared(
|
|
|
7367
7522
|
/* InBody/AfterHead startTagHead: any <head> after
|
|
7368
7523
|
* the head phase started is an unexpected start tag
|
|
7369
7524
|
* (tests6:1: </head> <head>). */
|
|
7370
|
-
|
|
7525
|
+
/* An explicit <head> while the IMPLIED head already
|
|
7526
|
+
* holds head content is html5lib's InHead
|
|
7527
|
+
* startTagHead — two-heads-are-not-better-than-one
|
|
7528
|
+
* (tests3:13: <html><meta><head>). */
|
|
7529
|
+
int head_content_before = 0;
|
|
7530
|
+
if (!b.head_tag_seen && !b.head_end_seen &&
|
|
7531
|
+
b.depth == 1 && b.open[0] &&
|
|
7532
|
+
b.open_id[0] == h_id_html) {
|
|
7533
|
+
LeptrisNodeRef hc =
|
|
7534
|
+
leptris_node_last_child_internal(
|
|
7535
|
+
(LeptrisNode*)b.open[0]);
|
|
7536
|
+
if (hc && leptris_node_get_type(hc) ==
|
|
7537
|
+
LEPTRIS_NODE_TYPE_ELEMENT) {
|
|
7538
|
+
const char* hn2 = leptris_element_name(
|
|
7539
|
+
(LeptrisElement)hc);
|
|
7540
|
+
head_content_before =
|
|
7541
|
+
hn2 && (h_ieq_raw(hn2, "title") ||
|
|
7542
|
+
h_ieq_raw(hn2, "meta") ||
|
|
7543
|
+
h_ieq_raw(hn2, "link") ||
|
|
7544
|
+
h_ieq_raw(hn2, "base") ||
|
|
7545
|
+
h_ieq_raw(hn2, "basefont") ||
|
|
7546
|
+
h_ieq_raw(hn2, "bgsound") ||
|
|
7547
|
+
h_ieq_raw(hn2, "script") ||
|
|
7548
|
+
h_ieq_raw(hn2, "style") ||
|
|
7549
|
+
h_ieq_raw(hn2, "noframes") ||
|
|
7550
|
+
h_ieq_raw(hn2, "noscript") ||
|
|
7551
|
+
h_ieq_raw(hn2, "template"));
|
|
7552
|
+
}
|
|
7553
|
+
}
|
|
7554
|
+
if (b.whatwg && head_content_before)
|
|
7555
|
+
h_diag(&b,
|
|
7556
|
+
"two-heads-are-not-better-than-one",
|
|
7557
|
+
(size_t)(p - b.input_base),
|
|
7558
|
+
"<head> in the implied head");
|
|
7559
|
+
else if (b.whatwg &&
|
|
7371
7560
|
(b.head_tag_seen || b.head_end_seen ||
|
|
7372
7561
|
b.body_tag_seen || b.body_seen))
|
|
7373
7562
|
h_diag(&b, "unexpected-start-tag",
|
|
@@ -7685,7 +7874,30 @@ static LeptrisDocument html_parse_shared(
|
|
|
7685
7874
|
/* 13.2.6.4.7: a <form> start tag with the form pointer
|
|
7686
7875
|
* already set (a form still open) is IGNORED - <form><form>
|
|
7687
7876
|
* is one form (tests6:13); likewise inside a table
|
|
7688
|
-
* (tests20:46/47). </form> clears the pointer.
|
|
7877
|
+
* (tests20:46/47). </form> clears the pointer.
|
|
7878
|
+
*
|
|
7879
|
+
* html5lib reports both flavors: InTable startTagForm
|
|
7880
|
+
* always says unexpected-form-in-table (even when it
|
|
7881
|
+
* inserts), and InBody reports unexpected-start-tag for a
|
|
7882
|
+
* duplicate (tests20:48: <table><form></table><form>). */
|
|
7883
|
+
if (b.whatwg && strcmp(name, "form") == 0) {
|
|
7884
|
+
int in_tbl = b.depth > 0 &&
|
|
7885
|
+
h_id_table_ctx(b.open_id[b.depth - 1]);
|
|
7886
|
+
if (!in_tbl && b.depth >= 2 &&
|
|
7887
|
+
b.open_ns[b.depth - 1] == H_NS_HTML &&
|
|
7888
|
+
b.open_id[b.depth - 1] == h_id_form &&
|
|
7889
|
+
b.open_ns[b.depth - 2] == H_NS_HTML &&
|
|
7890
|
+
b.open_id[b.depth - 2] == h_id_table)
|
|
7891
|
+
in_tbl = 1; /* table > form wrapper */
|
|
7892
|
+
if (in_tbl)
|
|
7893
|
+
h_diag(&b, "unexpected-form-in-table",
|
|
7894
|
+
(size_t)(p - b.input_base),
|
|
7895
|
+
"<form> in table context");
|
|
7896
|
+
else if (b.form_open)
|
|
7897
|
+
h_diag(&b, "unexpected-start-tag",
|
|
7898
|
+
(size_t)(p - b.input_base),
|
|
7899
|
+
"<form> while a form is open");
|
|
7900
|
+
}
|
|
7689
7901
|
if (b.whatwg && strcmp(name, "form") == 0 && b.form_open) {
|
|
7690
7902
|
/* The generic pre-element flush above already drained
|
|
7691
7903
|
* the pending run. */
|
|
@@ -8331,7 +8543,7 @@ static LeptrisDocument html_parse_shared(
|
|
|
8331
8543
|
}
|
|
8332
8544
|
}
|
|
8333
8545
|
if (!dup_out_of_scope) {
|
|
8334
|
-
h_afe_end(&b, name);
|
|
8546
|
+
h_afe_end(&b, name, (size_t)(p - b.input_base));
|
|
8335
8547
|
/* The agency's 8-iteration cap can leave the
|
|
8336
8548
|
* entry — the start-tag branch removes it
|
|
8337
8549
|
* unconditionally. */
|
|
@@ -8415,6 +8627,14 @@ static LeptrisDocument html_parse_shared(
|
|
|
8415
8627
|
q += 2;
|
|
8416
8628
|
break;
|
|
8417
8629
|
}
|
|
8630
|
+
/* Tokenizer self-closing-start-tag: a solidus not
|
|
8631
|
+
* followed by '>' reports and reprocesses in
|
|
8632
|
+
* before-attribute-name (tests2:58). */
|
|
8633
|
+
if (b.whatwg)
|
|
8634
|
+
h_diag(&b,
|
|
8635
|
+
"unexpected-character-after-solidus-in-tag",
|
|
8636
|
+
(size_t)(q - b.input_base),
|
|
8637
|
+
"'/' before an attribute name");
|
|
8418
8638
|
q++;
|
|
8419
8639
|
continue;
|
|
8420
8640
|
}
|
|
@@ -8749,6 +8969,14 @@ static LeptrisDocument html_parse_shared(
|
|
|
8749
8969
|
drop = 1;
|
|
8750
8970
|
break;
|
|
8751
8971
|
}
|
|
8972
|
+
/* AfterAfterFrameset processCharacters reports
|
|
8973
|
+
* expected-eof-but-got-char for every non-space
|
|
8974
|
+
* character (tests19:41). */
|
|
8975
|
+
if (drop)
|
|
8976
|
+
h_diag(&b, "expected-eof-but-got-char",
|
|
8977
|
+
(size_t)(text - b.input_base),
|
|
8978
|
+
"characters after </html> in the frameset"
|
|
8979
|
+
" phase");
|
|
8752
8980
|
}
|
|
8753
8981
|
if (!drop) {
|
|
8754
8982
|
if (!b.post_table_text) h_reconstruct(&b);
|
|
@@ -19,6 +19,17 @@ endif()
|
|
|
19
19
|
|
|
20
20
|
include(GoogleTest)
|
|
21
21
|
|
|
22
|
+
# Native builds discover the gtest cases at BUILD time so the list is
|
|
23
|
+
# baked into CTestTestfile and `ctest --parallel` can schedule the
|
|
24
|
+
# whole suite. PRE_TEST expands the list while ctest runs, which only
|
|
25
|
+
# works serially — under -j the _NOT_BUILT placeholders execute. Cross
|
|
26
|
+
# builds must keep PRE_TEST: their binaries cannot run on the host.
|
|
27
|
+
if(CMAKE_CROSSCOMPILING)
|
|
28
|
+
set(LEPTRIS_GTEST_DISCOVERY_MODE PRE_TEST)
|
|
29
|
+
else()
|
|
30
|
+
set(LEPTRIS_GTEST_DISCOVERY_MODE POST_BUILD)
|
|
31
|
+
endif()
|
|
32
|
+
|
|
22
33
|
# Convenience aggregate target — `cmake --build build --target check_all`.
|
|
23
34
|
add_custom_target(check_all
|
|
24
35
|
COMMENT "Running all Leptris specs")
|
|
@@ -52,10 +63,12 @@ function(leptris_add_test name)
|
|
|
52
63
|
# Timing specs measure ratios; concurrent processes steal cache
|
|
53
64
|
# and skew short measurements — run test_perf's cases serially.
|
|
54
65
|
if(name STREQUAL "test_perf")
|
|
55
|
-
gtest_discover_tests(${name}
|
|
66
|
+
gtest_discover_tests(${name}
|
|
67
|
+
DISCOVERY_MODE ${LEPTRIS_GTEST_DISCOVERY_MODE}
|
|
56
68
|
PROPERTIES RUN_SERIAL TRUE)
|
|
57
69
|
else()
|
|
58
|
-
gtest_discover_tests(${name}
|
|
70
|
+
gtest_discover_tests(${name}
|
|
71
|
+
DISCOVERY_MODE ${LEPTRIS_GTEST_DISCOVERY_MODE})
|
|
59
72
|
endif()
|
|
60
73
|
add_dependencies(check_all ${name})
|
|
61
74
|
endfunction()
|
|
@@ -69,13 +82,15 @@ function(leptris_add_cli_test name)
|
|
|
69
82
|
target_compile_definitions(${name} PRIVATE
|
|
70
83
|
LEPTRIS_CLI_BIN="${CMAKE_BINARY_DIR}/cli/leptris"
|
|
71
84
|
LEPTRIS_SOURCE_DIR="${CMAKE_SOURCE_DIR}")
|
|
85
|
+
# WORKING_DIRECTORY is a first-class keyword in the modern
|
|
86
|
+
# gtest_discover_tests signature. Passing it inside PROPERTIES
|
|
87
|
+
# made the parser eat RUN_SERIAL as an unparsed argument, so the
|
|
88
|
+
# CLI cases were never actually serialized (ctest -j4 then races
|
|
89
|
+
# the shared fixture cwd — the schematron/rng flakes).
|
|
72
90
|
gtest_discover_tests(${name}
|
|
73
|
-
DISCOVERY_MODE
|
|
74
|
-
PROPERTIES
|
|
91
|
+
DISCOVERY_MODE ${LEPTRIS_GTEST_DISCOVERY_MODE}
|
|
75
92
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
|
76
|
-
|
|
77
|
-
# parallel ctest flakes them — run serially.
|
|
78
|
-
RUN_SERIAL TRUE
|
|
93
|
+
PROPERTIES RUN_SERIAL TRUE
|
|
79
94
|
)
|
|
80
95
|
add_dependencies(check_all ${name} leptris-cli)
|
|
81
96
|
endfunction()
|
|
@@ -998,6 +998,18 @@ TEST(Html5LibCorpus, ErrorVerdictParity) {
|
|
|
998
998
|
* eof-in-comment-double-dash; the plaintext/EOF slice adds the
|
|
999
999
|
* plaintext open-at-EOF and the "</" tokenizer EOF codes; the
|
|
1000
1000
|
* stray-end-tag slice makes the depth-0 branch phase-aware for
|
|
1001
|
-
* raw-text, void and menuitem names
|
|
1002
|
-
|
|
1001
|
+
* raw-text, void and menuitem names; this batch adds the
|
|
1002
|
+
* depth-0 </p> phase code, the after-</html> end-tag code, the
|
|
1003
|
+
* frameset after-html character code, and image-start-tag; the
|
|
1004
|
+
* agency slice adds adoption-agency-1.3 for formatting end tags
|
|
1005
|
+
* whose element is in scope but not the current node; the
|
|
1006
|
+
* menuitem slice adds the endTagOther pair for </menuitem>
|
|
1007
|
+
* (stray before a special, or found with non-implied open
|
|
1008
|
+
* elements above); the closing slice adds the frameset
|
|
1009
|
+
* trailing-character code, BeforeHead end-tag-after-implied-
|
|
1010
|
+
* root, foreign-content end-tag mismatch, markup-declaration
|
|
1011
|
+
* (expected-dashes-or-doctype), solidus-in-tag, attributes-in-
|
|
1012
|
+
* end-tag, before-html end tags, form-in-table, template foster
|
|
1013
|
+
* characters and two-heads. Only raises. */
|
|
1014
|
+
EXPECT_GE(parity, (size_t)1555);
|
|
1003
1015
|
}
|
|
@@ -94,6 +94,111 @@ TEST(XPathFunctions, CountReturnsNodeSetSize) {
|
|
|
94
94
|
leptris_document_free(doc);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/* leptris-ruby #519: a node-set XPath run primes the TODO 132 element
|
|
98
|
+
* index. Sibling insertions change the element set but did not
|
|
99
|
+
* invalidate it, so the next //name query answered from the stale
|
|
100
|
+
* index and missed the inserted element. Child insertion (append)
|
|
101
|
+
* always invalidated; every splice path must too. */
|
|
102
|
+
TEST(XPathIndexInvalidation, SiblingInsertionsRefreshDescendantAxis) {
|
|
103
|
+
const char xml[] = "<root><anchor/></root>";
|
|
104
|
+
|
|
105
|
+
auto eval_count = [](LeptrisDocument doc, const char* expr) -> int {
|
|
106
|
+
LeptrisXPathResult r = leptris_xpath_eval(doc, nullptr, expr);
|
|
107
|
+
if (!r) return -1;
|
|
108
|
+
int n = (int)leptris_xpath_result_count(r);
|
|
109
|
+
leptris_xpath_result_free(r);
|
|
110
|
+
return n;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/* TODO 190: the index builds on the SECOND axis query. Prime it so
|
|
114
|
+
* the post-insert query is served from the cached index — the
|
|
115
|
+
* stale-read shape in the report. */
|
|
116
|
+
auto prime = [&eval_count](LeptrisDocument doc) {
|
|
117
|
+
EXPECT_EQ(eval_count(doc, "//anchor"), 1);
|
|
118
|
+
EXPECT_EQ(eval_count(doc, "//anchor"), 1);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/* insert_after — the shape in the report (add_next_sibling). */
|
|
122
|
+
{
|
|
123
|
+
LeptrisDocument doc =
|
|
124
|
+
leptris_parse_string(xml, strlen(xml), nullptr);
|
|
125
|
+
ASSERT_NE(doc, nullptr);
|
|
126
|
+
prime(doc);
|
|
127
|
+
EXPECT_EQ(eval_count(doc, "//ext"), 0);
|
|
128
|
+
LeptrisElement anchor = leptris_element_first_child_any(
|
|
129
|
+
leptris_document_root(doc));
|
|
130
|
+
ASSERT_NE(anchor, nullptr);
|
|
131
|
+
LeptrisElement ext = leptris_element_create(doc, "ext");
|
|
132
|
+
ASSERT_NE(ext, nullptr);
|
|
133
|
+
ASSERT_EQ(leptris_element_insert_after(anchor, ext), LEPTRIS_OK);
|
|
134
|
+
EXPECT_EQ(eval_count(doc, "//ext"), 1);
|
|
135
|
+
leptris_document_free(doc);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* insert_before. */
|
|
139
|
+
{
|
|
140
|
+
LeptrisDocument doc =
|
|
141
|
+
leptris_parse_string(xml, strlen(xml), nullptr);
|
|
142
|
+
ASSERT_NE(doc, nullptr);
|
|
143
|
+
prime(doc);
|
|
144
|
+
EXPECT_EQ(eval_count(doc, "//ext"), 0);
|
|
145
|
+
LeptrisElement anchor = leptris_element_first_child_any(
|
|
146
|
+
leptris_document_root(doc));
|
|
147
|
+
ASSERT_NE(anchor, nullptr);
|
|
148
|
+
LeptrisElement ext = leptris_element_create(doc, "ext");
|
|
149
|
+
ASSERT_NE(ext, nullptr);
|
|
150
|
+
ASSERT_EQ(leptris_element_insert_before(anchor, ext), LEPTRIS_OK);
|
|
151
|
+
EXPECT_EQ(eval_count(doc, "//ext"), 1);
|
|
152
|
+
leptris_document_free(doc);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* prepend_child — the other child splice that skipped invalidation. */
|
|
156
|
+
{
|
|
157
|
+
LeptrisDocument doc =
|
|
158
|
+
leptris_parse_string(xml, strlen(xml), nullptr);
|
|
159
|
+
ASSERT_NE(doc, nullptr);
|
|
160
|
+
prime(doc);
|
|
161
|
+
EXPECT_EQ(eval_count(doc, "//ext"), 0);
|
|
162
|
+
LeptrisElement root = leptris_document_root(doc);
|
|
163
|
+
ASSERT_NE(root, nullptr);
|
|
164
|
+
LeptrisElement ext = leptris_element_create(doc, "ext");
|
|
165
|
+
ASSERT_NE(ext, nullptr);
|
|
166
|
+
ASSERT_EQ(leptris_element_prepend_child(root, ext), LEPTRIS_OK);
|
|
167
|
+
EXPECT_EQ(eval_count(doc, "//ext"), 1);
|
|
168
|
+
leptris_document_free(doc);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Compiled handle — the raw FFI face the binding evaluates with. */
|
|
172
|
+
{
|
|
173
|
+
LeptrisDocument doc =
|
|
174
|
+
leptris_parse_string(xml, strlen(xml), nullptr);
|
|
175
|
+
ASSERT_NE(doc, nullptr);
|
|
176
|
+
prime(doc);
|
|
177
|
+
LeptrisXPathCompiled compiled = leptris_xpath_compile("//ext");
|
|
178
|
+
ASSERT_NE(compiled, nullptr);
|
|
179
|
+
LeptrisXPathResult first =
|
|
180
|
+
leptris_xpath_compiled_eval(compiled, doc, nullptr);
|
|
181
|
+
ASSERT_NE(first, nullptr);
|
|
182
|
+
EXPECT_EQ(leptris_xpath_result_count(first), 0u);
|
|
183
|
+
leptris_xpath_result_free(first);
|
|
184
|
+
|
|
185
|
+
LeptrisElement anchor = leptris_element_first_child_any(
|
|
186
|
+
leptris_document_root(doc));
|
|
187
|
+
ASSERT_NE(anchor, nullptr);
|
|
188
|
+
LeptrisElement ext = leptris_element_create(doc, "ext");
|
|
189
|
+
ASSERT_NE(ext, nullptr);
|
|
190
|
+
ASSERT_EQ(leptris_element_insert_after(anchor, ext), LEPTRIS_OK);
|
|
191
|
+
|
|
192
|
+
LeptrisXPathResult second =
|
|
193
|
+
leptris_xpath_compiled_eval(compiled, doc, nullptr);
|
|
194
|
+
ASSERT_NE(second, nullptr);
|
|
195
|
+
EXPECT_EQ(leptris_xpath_result_count(second), 1u);
|
|
196
|
+
leptris_xpath_result_free(second);
|
|
197
|
+
leptris_xpath_compiled_free(compiled);
|
|
198
|
+
leptris_document_free(doc);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
97
202
|
/* Lane 07 tail (#692-B): function items report a distinct public
|
|
98
203
|
* result type at the boundary — closures and named references. */
|
|
99
204
|
TEST(XPathResultTypes, FunctionItemsReportFunctionType) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
|
|
3
3
|
"name": "leptris",
|
|
4
|
-
"version": "1.9.
|
|
4
|
+
"version": "1.9.378",
|
|
5
5
|
"description": "High-performance XML parser and XPath 1.0 engine in C (libleptris)",
|
|
6
6
|
"homepage": "https://github.com/leptris/leptris",
|
|
7
7
|
"license": "MIT",
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.9.
|
|
4
|
+
version: 1.9.378.0
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -206,6 +206,7 @@ files:
|
|
|
206
206
|
- vendor-src/libleptris/CONTEXT.md
|
|
207
207
|
- vendor-src/libleptris/LICENSE.md
|
|
208
208
|
- vendor-src/libleptris/README.adoc
|
|
209
|
+
- vendor-src/libleptris/SECURITY.md
|
|
209
210
|
- vendor-src/libleptris/TODO.SOTA.md
|
|
210
211
|
- vendor-src/libleptris/TODO.assemble/README.md
|
|
211
212
|
- vendor-src/libleptris/TODO.bindings/01-mutation-construction.md
|