Coverage Report

Created: 2026-08-14 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/quick-xml-0.41.0/src/parser/dtd.rs
Line
Count
Source
1
use crate::parser::{CommentParser, ElementParser, Parser, PiParser};
2
3
#[derive(Debug, Clone, Copy, PartialEq)]
4
pub enum DtdParser {
5
    /// If inside a PubidLiteral or SystemLiteral, it holds the quote type (either `'` or `"`).
6
    /// Otherwise, it holds `0` (this is an initial state).
7
    ///
8
    /// ```text
9
    /// [28]    doctypedecl     ::=   '<!DOCTYPE' S Name (S ExternalID)? S? ('[' intSubset ']' S?)? '>'
10
    /// ```
11
    BeforeInternalSubset(u8),
12
    /// Inside of the `intSubset` rule.
13
    ///
14
    /// ```text
15
    /// [28a]   DeclSep         ::=   PEReference | S
16
    /// [28b]   intSubset       ::=   (markupdecl | DeclSep)*
17
    /// [29]    markupdecl      ::=   elementdecl | AttlistDecl | EntityDecl | NotationDecl | PI | Comment
18
    /// ```
19
    InsideOfInternalSubset,
20
    /// After `]` but before `>`.
21
    AfterInternalSubset,
22
    InComment(CommentParser),
23
    InPi(PiParser),
24
    /// ```text
25
    /// [45]    elementdecl     ::=   '<!ELEMENT' S Name S contentspec S? '>'
26
    /// ```
27
    InElementDecl,
28
    /// This state handles ATTLIST, ENTITY and NOTATION elements, i.e. all elements that can have
29
    /// quotes strings (`'...'` or `"..."`) inside their markup, in which `>` should not be threated
30
    /// as the end of the markup.
31
    ///
32
    /// This state handles the following productions from XML grammar:
33
    ///
34
    /// ### ATTLIST
35
    ///
36
    /// ```text
37
    /// [52]    AttlistDecl     ::=   '<!ATTLIST' S Name AttDef* S? '>'
38
    /// [53]    AttDef          ::=   S Name S AttType S DefaultDecl
39
    /// [60]    DefaultDecl     ::=   '#REQUIRED' | '#IMPLIED' | (('#FIXED' S)? AttValue)
40
    /// ```
41
    ///
42
    /// ### ENTITY
43
    ///
44
    /// ```text
45
    /// [70]    EntityDecl      ::=   GEDecl | PEDecl
46
    /// [71]    GEDecl          ::=   '<!ENTITY' S Name S EntityDef S? '>'
47
    /// [72]    PEDecl          ::=   '<!ENTITY' S '%' S Name S PEDef S? '>'
48
    /// [73]    EntityDef       ::=   EntityValue | (ExternalID NDataDecl?)
49
    /// [74]    PEDef           ::=   EntityValue | ExternalID
50
    /// [75]    ExternalID      ::=   'SYSTEM' S SystemLiteral | 'PUBLIC' S PubidLiteral S SystemLiteral
51
    /// [76]    NDataDecl       ::=   S 'NDATA' S Name
52
    /// ```
53
    ///
54
    /// ### NOTATION
55
    ///
56
    /// ```text
57
    /// [82]    NotationDecl    ::=   '<!NOTATION' S Name S (ExternalID | PublicID) S? '>'
58
    /// ```
59
    InQuoteSensitive(ElementParser),
60
    /// The state where it was not possible to determine which markup it was during the previous iteration.  \
61
    /// It holds the number of bytes read since the start of the markup.
62
    UndecidedMarkup(usize),
63
    Finished,
64
}
65
66
impl DtdParser {
67
    /// Skip DTD contents.
68
    ///
69
    /// # Parameters (as same as `reader::BangType::parse`)
70
    /// - `buf`: buffer with data consumed on previous iterations
71
    /// - `chunk`: data read on current iteration and not yet consumed from reader
72
0
    pub fn feed(&mut self, buf: &[u8], chunk: &[u8]) -> Option<usize> {
73
        // This method assumes the DTD is well-formed.
74
        // Since this crate does not support parsing DTDs, the inability to read non-well-formed DTDs
75
        // is not particularly problematic; the only point of interest is reporting well-formed DTDs
76
        // to the user without errors.
77
78
0
        let mut cur = chunk;
79
0
        while !cur.is_empty() {
80
0
            match *self {
81
                Self::BeforeInternalSubset(0) => {
82
                    // Find the
83
                    // - start of quoted string ('...' or "...")
84
                    // - start of internal subset ([...])
85
                    // - end of DOCTYPE declaration (>)
86
0
                    if let Some(i) = cur
87
0
                        .iter()
88
0
                        .position(|&b| matches!(b, b'\'' | b'"' | b'[' | b'>'))
89
                    {
90
0
                        let b = cur[i];
91
0
                        match b {
92
                            b'\'' | b'"' => {
93
                                // SystemLiteral or PubidLiteral
94
0
                                *self = Self::BeforeInternalSubset(b);
95
0
                                cur = &cur[i + 1..]; // +1 to skip `'` or `"`
96
0
                                continue;
97
                            }
98
                            b'[' => {
99
0
                                *self = Self::InsideOfInternalSubset;
100
0
                                cur = &cur[i + 1..]; // +1 to skip `[`
101
0
                                continue;
102
                            }
103
                            b'>' => {
104
0
                                *self = Self::Finished;
105
0
                                return Some(chunk.len() - cur.len() + i);
106
                            }
107
0
                            _ => {}
108
                        }
109
0
                        continue;
110
0
                    }
111
0
                    break;
112
                }
113
                // Inside the quoted string (this is PubidLiteral or SystemLiteral) we do not want to
114
                // recognize other special characters (namely [ and >). Find only the closing quote
115
0
                Self::BeforeInternalSubset(quote) => {
116
                    // ExternalID handling
117
0
                    if let Some(i) = memchr::memchr(quote, cur) {
118
0
                        *self = Self::BeforeInternalSubset(0);
119
0
                        cur = &cur[i + 1..];
120
0
                        continue;
121
0
                    }
122
0
                    break;
123
                }
124
                Self::InsideOfInternalSubset => {
125
                    // Find the end of internal subset (]) or the start of the markup inside (<)
126
0
                    if let Some(i) = memchr::memchr2(b']', b'<', cur) {
127
0
                        if cur[i] == b']' {
128
0
                            *self = Self::AfterInternalSubset;
129
0
                            cur = &cur[i + 1..]; // +1 to skip `]`
130
0
                            continue;
131
0
                        }
132
                        // +1 to start after `<`
133
0
                        if let Some(skip) = self.switch(&cur[i + 1..]) {
134
0
                            cur = &cur[i + 1 + skip..]; // +1 to skip `<`
135
0
                            continue;
136
0
                        }
137
                        // Keep the number of already looked bytes (started from byte after `<`, so -1),
138
                        // try to decide after feeding the new chunk.
139
0
                        let skipped = cur.len() - i - 1;
140
                        // The 9-byte work buffer in `UndecidedMarkup` is sized
141
                        // for `!NOTATION` (the longest keyword). If the chunk
142
                        // already gave us 9+ bytes after `<` and `switch()`
143
                        // returned `None`, the markup is definitively not one
144
                        // of `<!--`, `<![CDATA[`, `<!ELEMENT`, `<!ATTLIST`,
145
                        // `<!ENTITY`, `<!NOTATION`, so skip until `>` rather
146
                        // than staging more bytes than the buffer can hold
147
                        // (which would panic on the slice copy in
148
                        // `UndecidedMarkup`).
149
0
                        if skipped >= 9 {
150
0
                            cur = &cur[i + 1..];
151
0
                            *self = Self::InElementDecl;
152
0
                            continue;
153
0
                        }
154
0
                        *self = Self::UndecidedMarkup(skipped);
155
0
                    }
156
0
                    break;
157
                }
158
                Self::AfterInternalSubset => {
159
0
                    if let Some(i) = memchr::memchr(b'>', cur) {
160
0
                        *self = Self::Finished;
161
0
                        return Some(chunk.len() - cur.len() + i);
162
0
                    }
163
0
                    break;
164
                }
165
0
                Self::InComment(ref mut parser) => {
166
                    // If comment is ended, return to the main state, otherwise keep in the current state
167
0
                    if let Some(i) = parser.feed(cur) {
168
0
                        *self = Self::InsideOfInternalSubset;
169
0
                        cur = &cur[i..];
170
0
                        continue;
171
0
                    }
172
0
                    break;
173
                }
174
0
                Self::InPi(ref mut parser) => {
175
                    // If processing instruction is ended, return to the main state,
176
                    // otherwise keep in the current state
177
0
                    if let Some(i) = parser.feed(cur) {
178
0
                        *self = Self::InsideOfInternalSubset;
179
0
                        cur = &cur[i..];
180
0
                        continue;
181
0
                    }
182
0
                    break;
183
                }
184
                Self::InElementDecl => {
185
                    // `<!ELEMENT >` does not have places where `>` could be escaped
186
                    // so the first occurrence ends that state
187
0
                    if let Some(i) = memchr::memchr(b'>', cur) {
188
0
                        *self = Self::InsideOfInternalSubset;
189
0
                        cur = &cur[i + 1..]; // +1 for `>`
190
0
                        continue;
191
0
                    }
192
0
                    break;
193
                }
194
0
                Self::InQuoteSensitive(ref mut parser) => {
195
                    // If ATTLIST, ENTITY or NOTATION is ended, return to the main state,
196
                    // otherwise keep in the current state
197
0
                    if let Some(i) = parser.feed(cur) {
198
0
                        *self = Self::InsideOfInternalSubset;
199
0
                        cur = &cur[i..];
200
0
                        continue;
201
0
                    }
202
0
                    break;
203
                }
204
0
                Self::UndecidedMarkup(skipped) => {
205
                    // Buffer is long enough to store the longest possible keyword `!NOTATION`
206
0
                    let mut bytes = [0u8; 9];
207
208
                    // Copy the last `skipped` bytes from the previous iteration into buffer,
209
                    // for example, "!NOT" (skipped = 4 in that case)...
210
0
                    bytes[..skipped].copy_from_slice(&buf[buf.len() - skipped..]);
211
212
                    // ...add new bytes to the buffer from current iteration,
213
                    // for example, "ATION"...
214
0
                    let end = bytes.len().min(skipped + cur.len());
215
0
                    bytes[skipped..end].copy_from_slice(&cur[..end - skipped]);
216
217
                    // ...and try to match over it.
218
                    // For example, "!NOTATION" will return 9, and we skip 9-4=5 bytes of "ATION"
219
0
                    if let Some(skip) = self.switch(&bytes[..end]) {
220
0
                        cur = &cur[skip - skipped..];
221
0
                        continue;
222
0
                    }
223
                    // No keyword matched. If we have a full 9-byte window the
224
                    // markup is definitively not one of `<!--`, `<![CDATA[`,
225
                    // `<!ELEMENT`, `<!ATTLIST`, `<!ENTITY`, `<!NOTATION`, so
226
                    // fall back to skipping until the closing `>` instead of
227
                    // accumulating `skipped` past `bytes.len()` (which would
228
                    // panic on the slice-copy above on a later iteration).
229
0
                    if end == bytes.len() {
230
0
                        cur = &cur[end - skipped..];
231
0
                        *self = Self::InElementDecl;
232
0
                        continue;
233
0
                    }
234
0
                    *self = Self::UndecidedMarkup(skipped + cur.len());
235
0
                    break;
236
                }
237
0
                Self::Finished => break,
238
            }
239
        }
240
241
0
        None
242
0
    }
243
244
    #[inline]
245
0
    fn switch(&mut self, markup: &[u8]) -> Option<usize> {
246
0
        match markup {
247
0
            [b'?', ..] => {
248
                // <?
249
0
                *self = Self::InPi(PiParser(false));
250
0
                Some(1)
251
            }
252
0
            [b'!', b'-', b'-', ..] => {
253
                // <!--
254
0
                *self = Self::InComment(CommentParser::Seen0);
255
0
                Some(3)
256
            }
257
0
            [b'!', b'E', b'L', b'E', b'M', b'E', b'N', b'T', ..] => {
258
                // <!ELEMENT
259
0
                *self = Self::InElementDecl;
260
0
                Some(8)
261
            }
262
0
            [b'!', b'E', b'N', b'T', b'I', b'T', b'Y', ..] => {
263
                // <!ENTITY
264
0
                *self = Self::InQuoteSensitive(ElementParser::Outside);
265
0
                Some(7)
266
            }
267
0
            [b'!', b'A', b'T', b'T', b'L', b'I', b'S', b'T', ..] => {
268
                // <!ATTLIST
269
0
                *self = Self::InQuoteSensitive(ElementParser::Outside);
270
0
                Some(8)
271
            }
272
0
            [b'!', b'N', b'O', b'T', b'A', b'T', b'I', b'O', b'N', ..] => {
273
                // <!NOTATION
274
0
                *self = Self::InQuoteSensitive(ElementParser::Outside);
275
0
                Some(9)
276
            }
277
            // <... - `markup` does not have enough data to determine markup
278
            // or markup is not known.
279
            // Undecided markup bytes will be written to `buf` to be available on
280
            // next iteration.
281
            _ => {
282
                // FIXME: to correctly report error position in DTD we need to provide
283
                // DTD events. For now our task just to skip (correct) DTD, so we postpone
284
                // error reporting and go with ending the unknown markup with `>`.
285
0
                if let Some(i) = memchr::memchr(b'>', markup) {
286
0
                    *self = Self::InsideOfInternalSubset;
287
0
                    Some(i + 1) // +1 to skip `>`
288
                } else {
289
0
                    None
290
                }
291
            }
292
        }
293
0
    }
294
}