@@ -1896,6 +1896,84 @@ mod resolve {
18961896 }
18971897}
18981898
1899+ /// A DOCTYPE declaration inside an element's text content used to split
1900+ /// the surrounding character data into two consecutive `DeEvent::Text`
1901+ /// events, tripping an `unreachable!()` in `read_text` ("Cannot be two
1902+ /// consequent Text events"). The reader emits Text/DocType/Text for an
1903+ /// input like `<a>x<!DOCTYPE y>z</a>`, and `drain_text` did not drain
1904+ /// across DocType events, so the two text runs reached the
1905+ /// deserializer un-merged. Discovered via libFuzzer on a real-world
1906+ /// SAML response harness.
1907+ ///
1908+ /// The expected behavior is: DOCTYPE inside an element is captured by
1909+ /// the entity resolver (it has the same role as a prolog-level DOCTYPE
1910+ /// for entity definitions) and the surrounding text is treated as one
1911+ /// continuous run.
1912+ mod doctype_in_element_text {
1913+ use super :: * ;
1914+ use pretty_assertions:: assert_eq;
1915+
1916+ #[ derive( Debug , Deserialize , PartialEq ) ]
1917+ struct Wrapper {
1918+ #[ serde( rename = "$text" ) ]
1919+ text : String ,
1920+ }
1921+
1922+ /// Minimal regression repro: a single DOCTYPE event between two
1923+ /// adjacent text runs inside an element should not panic.
1924+ #[ test]
1925+ fn single_doctype_between_text ( ) {
1926+ let xml = r#"<a>x<!DOCTYPE y>z</a>"# ;
1927+ let got: Wrapper = from_str ( xml) . unwrap ( ) ;
1928+ assert_eq ! ( got, Wrapper { text: "xz" . into( ) } ) ;
1929+ }
1930+
1931+ /// Multiple DOCTYPE events stacked between text runs (matching the
1932+ /// shape of the fuzzer-discovered input that originally surfaced
1933+ /// this panic on real SAML traffic).
1934+ #[ test]
1935+ fn multiple_doctypes_between_text ( ) {
1936+ let xml = r#"<a>x<!DOCTYPE y><!DOCTYPE z>w</a>"# ;
1937+ let got: Wrapper = from_str ( xml) . unwrap ( ) ;
1938+ assert_eq ! ( got, Wrapper { text: "xw" . into( ) } ) ;
1939+ }
1940+
1941+ /// DOCTYPE followed by text only (no leading text). Should produce
1942+ /// the trailing text without panic.
1943+ #[ test]
1944+ fn leading_doctype_then_text ( ) {
1945+ let xml = r#"<a><!DOCTYPE y>x</a>"# ;
1946+ let got: Wrapper = from_str ( xml) . unwrap ( ) ;
1947+ assert_eq ! ( got, Wrapper { text: "x" . into( ) } ) ;
1948+ }
1949+
1950+ /// Whitespace adjacent to the DOCTYPE on both sides should be
1951+ /// preserved verbatim when the two text runs are merged — the
1952+ /// DOCTYPE is treated as transparent, not as a delimiter.
1953+ #[ test]
1954+ fn whitespace_around_doctype ( ) {
1955+ let xml = "<a>x <!DOCTYPE y>\t z</a>" ;
1956+ let got: Wrapper = from_str ( xml) . unwrap ( ) ;
1957+ assert_eq ! (
1958+ got,
1959+ Wrapper {
1960+ text: "x \t z" . into( )
1961+ }
1962+ ) ;
1963+
1964+ // Whitespace at the element boundaries (before the leading
1965+ // text and after the trailing text) is also preserved.
1966+ let xml = "<a> x<!DOCTYPE y>z </a>" ;
1967+ let got: Wrapper = from_str ( xml) . unwrap ( ) ;
1968+ assert_eq ! (
1969+ got,
1970+ Wrapper {
1971+ text: " xz " . into( )
1972+ }
1973+ ) ;
1974+ }
1975+ }
1976+
18991977/// Tests for https://github.com/tafia/quick-xml/pull/603.
19001978///
19011979/// According to <https://www.w3.org/TR/xml11/#NT-prolog> comments,
0 commit comments