xml/reader/config.rs
1//! Contains parser configuration structure.
2use std::collections::HashMap;
3use std::io::Read;
4
5use crate::reader::EventReader;
6use crate::util::Encoding;
7
8/// Limits to defend from billion laughs attack
9const DEFAULT_MAX_ENTITY_EXPANSION_LENGTH: usize = 1_000_000;
10const DEFAULT_MAX_ENTITY_EXPANSION_DEPTH: u8 = 10;
11
12/// Parser configuration structure. **There are more config methods than public fileds — see methods below**.
13///
14/// This structure contains various configuration options which affect
15/// behavior of the parser.
16#[derive(Clone, PartialEq, Eq, Debug)]
17#[non_exhaustive]
18pub struct ParserConfig {
19 /// Whether or not should whitespace in textual events be removed. Default is false.
20 ///
21 /// When true, all standalone whitespace will be removed (this means no
22 /// `Whitespace` events will be emitted), and leading and trailing whitespace
23 /// from `Character` events will be deleted. If after trimming `Characters`
24 /// event will be empty, it will also be omitted from output stream. This is
25 /// possible, however, only if `whitespace_to_characters` or
26 /// `cdata_to_characters` options are set.
27 ///
28 /// This option does not affect CDATA events, unless `cdata_to_characters`
29 /// option is also set. In that case CDATA content will also be trimmed.
30 pub trim_whitespace: bool,
31
32 /// Whether or not should whitespace be converted to characters.
33 /// Default is false.
34 ///
35 /// If true, instead of `Whitespace` events `Characters` events with the
36 /// same content will be emitted. If `trim_whitespace` is also true, these
37 /// events will be trimmed to nothing and, consequently, not emitted.
38 pub whitespace_to_characters: bool,
39
40 /// Whether or not should CDATA be converted to characters.
41 /// Default is false.
42 ///
43 /// If true, instead of `CData` events `Characters` events with the same
44 /// content will be emitted. If `trim_whitespace` is also true, these events
45 /// will be trimmed. If corresponding CDATA contained nothing but whitespace,
46 /// this event will be omitted from the stream.
47 pub cdata_to_characters: bool,
48
49 /// Whether or not should comments be omitted. Default is true.
50 ///
51 /// If true, `Comment` events will not be emitted at all.
52 pub ignore_comments: bool,
53
54 /// Whether or not should sequential `Characters` events be merged.
55 /// Default is true.
56 ///
57 /// If true, multiple sequential `Characters` events will be merged into
58 /// a single event, that is, their data will be concatenated.
59 ///
60 /// Multiple sequential `Characters` events are only possible if either
61 /// `cdata_to_characters` or `ignore_comments` are set. Otherwise character
62 /// events will always be separated by other events.
63 pub coalesce_characters: bool,
64
65 /// A map of extra entities recognized by the parser. Default is an empty map.
66 ///
67 /// By default the XML parser recognizes the entities defined in the XML spec. Sometimes,
68 /// however, it is convenient to make the parser recognize additional entities which
69 /// are also not available through the DTD definitions (especially given that at the moment
70 /// DTD parsing is not supported).
71 pub extra_entities: HashMap<String, String>,
72
73 /// Whether or not the parser should ignore the end of stream. Default is false.
74 ///
75 /// By default the parser will either error out when it encounters a premature end of
76 /// stream or complete normally if the end of stream was expected. If you want to continue
77 /// reading from a stream whose input is supplied progressively, you can set this option to true.
78 /// In this case the parser will allow you to invoke the `next()` method even if a supposed end
79 /// of stream has happened.
80 ///
81 /// Note that support for this functionality is incomplete; for example, the parser will fail if
82 /// the premature end of stream happens inside PCDATA. Therefore, use this option at your own risk.
83 pub ignore_end_of_stream: bool,
84
85 /// Whether or not non-unicode entity references get replaced with the replacement character
86 ///
87 /// When true, any decimal or hexadecimal character reference that cannot be converted from a
88 /// u32 to a char using [std::char::from_u32](https://doc.rust-lang.org/std/char/fn.from_u32.html)
89 /// will be converted into the unicode REPLACEMENT CHARACTER (U+FFFD).
90 pub replace_unknown_entity_references: bool,
91
92 /// Whether or not whitespace at the root level of the document is ignored. Default is true.
93 ///
94 /// By default any whitespace that is not enclosed within at least one level of elements will be
95 /// ignored. Setting this value to false will cause root level whitespace events to be emitted.
96 pub ignore_root_level_whitespace: bool,
97
98 /// Use this encoding as the default. Necessary for UTF-16 files without BOM.
99 pub override_encoding: Option<Encoding>,
100
101 /// Allow `<?xml encoding="…">` to contain unsupported encoding names,
102 /// and interpret them as Latin1 instead. This will mangle non-ASCII characters, but usually it won't fail parsing.
103 pub ignore_invalid_encoding_declarations: bool,
104
105 /// Documents with multiple root elements are ill-formed
106 pub allow_multiple_root_elements: bool,
107
108 /// Abort if custom entities create a string longer than this
109 pub max_entity_expansion_length: usize,
110
111 /// Entities can expand into other entities this many times (be careful about exponential cost!)
112 pub max_entity_expansion_depth: u8,
113
114 /// Maximum length of tag name or attribute name
115 pub max_name_length: usize,
116
117 /// Max number of attributes per element
118 pub max_attributes: usize,
119
120 /// Max number of bytes in each attribute
121 pub max_attribute_length: usize,
122
123 /// Maximum length of strings reprsenting characters, comments, and processing instructions
124 pub max_data_length: usize,
125}
126
127impl ParserConfig {
128 /// Returns a new config with default values.
129 ///
130 /// You can tweak default values using builder-like pattern:
131 ///
132 /// ```rust
133 /// use xml::reader::ParserConfig;
134 ///
135 /// let config = ParserConfig::new()
136 /// .trim_whitespace(true)
137 /// .ignore_comments(true)
138 /// .coalesce_characters(false);
139 /// ```
140 #[must_use]
141 #[inline]
142 pub fn new() -> Self {
143 Self {
144 trim_whitespace: false,
145 whitespace_to_characters: false,
146 cdata_to_characters: false,
147 ignore_comments: true,
148 coalesce_characters: true,
149 extra_entities: HashMap::new(),
150 ignore_end_of_stream: false,
151 replace_unknown_entity_references: false,
152 ignore_root_level_whitespace: true,
153
154 override_encoding: None,
155 ignore_invalid_encoding_declarations: false,
156 allow_multiple_root_elements: true,
157 max_entity_expansion_length: DEFAULT_MAX_ENTITY_EXPANSION_LENGTH,
158 max_entity_expansion_depth: DEFAULT_MAX_ENTITY_EXPANSION_DEPTH,
159 max_attributes: 1 << 16,
160 max_attribute_length: 1 << 30,
161 max_data_length: 1 << 30,
162 max_name_length: 1 << 18,
163 }
164 }
165
166 /// Creates an XML reader with this configuration. The reader should be wrapped in a `BufReader`, otherwise parsing may be very slow.
167 ///
168 /// This is a convenience method for configuring and creating a reader at the same time:
169 ///
170 /// ```rust
171 /// use xml::reader::ParserConfig;
172 ///
173 /// let mut source: &[u8] = b"...";
174 ///
175 /// let reader = ParserConfig::new()
176 /// .trim_whitespace(true)
177 /// .ignore_comments(true)
178 /// .coalesce_characters(false)
179 /// .create_reader(&mut source);
180 /// ```
181 ///
182 /// This method is exactly equivalent to calling `EventReader::new_with_config()` with
183 /// this configuration object.
184 #[inline]
185 pub fn create_reader<R: Read>(self, source: R) -> EventReader<R> {
186 EventReader::new_with_config(source, self)
187 }
188
189 /// Adds a new entity mapping and returns an updated config object.
190 ///
191 /// This is a convenience method for adding external entities mappings to the XML parser.
192 /// An example:
193 ///
194 /// ```rust
195 /// use xml::reader::ParserConfig;
196 ///
197 /// let mut source: &[u8] = b"...";
198 ///
199 /// let reader = ParserConfig::new()
200 /// .add_entity("nbsp", " ")
201 /// .add_entity("copy", "©")
202 /// .add_entity("reg", "®")
203 /// .create_reader(&mut source);
204 /// ```
205 #[must_use]
206 #[inline]
207 pub fn add_entity<S: Into<String>, T: Into<String>>(mut self, entity: S, value: T) -> Self {
208 self.extra_entities.insert(entity.into(), value.into());
209 self
210 }
211
212 /// Adds entities and returns an updated config object.
213 ///
214 /// This is a convenience method for adding external entities mappings to the XML parser.
215 /// An example:
216 ///
217 /// ```rust
218 /// use xml::reader::ParserConfig;
219 ///
220 /// let mut source: &[u8] = b"...";
221 ///
222 /// let reader = ParserConfig::new()
223 /// .add_entities([
224 /// ("nbsp", " "),
225 /// ("copy", "©"),
226 /// ("reg", "®"),
227 /// ])
228 /// .create_reader(&mut source);
229 /// ```
230 #[must_use]
231 #[inline]
232 pub fn add_entities<S: Into<String>, T: Into<String>>(mut self, entities: impl IntoIterator<Item = (S, T)>) -> Self {
233 self.extra_entities.extend(entities.into_iter().map(|(k, v)| (k.into(), v.into())));
234 self
235 }
236}
237
238gen_setters! { ParserConfig,
239 trim_whitespace: val bool,
240 whitespace_to_characters: val bool,
241 cdata_to_characters: val bool,
242 ignore_comments: val bool,
243 coalesce_characters: val bool,
244 ignore_end_of_stream: val bool,
245 replace_unknown_entity_references: val bool,
246 /// Whether or not whitespace at the root level of the document is ignored. Default is true.
247 ignore_root_level_whitespace: val bool
248}
249
250impl Default for ParserConfig {
251 fn default() -> Self {
252 Self::new()
253 }
254}
255
256impl ParserConfig {
257 /// Read character encoding from `Content-Type` header.
258 /// Set this when parsing XML documents fetched over HTTP.
259 ///
260 /// `text/*` MIME types do *not* imply latin1. UTF-8 is always the default fallback.
261 #[must_use]
262 pub fn content_type(mut self, mime_type: &str) -> Self {
263 let charset = mime_type.split_once(';')
264 .and_then(|(_, args)| args.split_once("charset"))
265 .and_then(|(_, args)| args.split_once('='));
266 if let Some((_, charset)) = charset {
267 let name = charset.trim().trim_matches('"');
268 if let Ok(enc) = name.parse() {
269 self.override_encoding = Some(enc);
270 }
271 }
272 self
273 }
274}
275
276gen_setters! { ParserConfig,
277 /// Set if you got one in the HTTP header
278 override_encoding: val Option<Encoding>,
279 /// Allows invalid documents. There should be only a single root element in XML.
280 allow_multiple_root_elements: val bool,
281 /// Abort if custom entities create a string longer than this
282 max_entity_expansion_length: val usize,
283 /// Entities can expand into other entities this many times (be careful about exponential cost!)
284 max_entity_expansion_depth: val u8,
285 /// Max number of attributes per element
286 max_attributes: val usize,
287 /// Maximum length of tag name or attribute name
288 max_name_length: val usize,
289 /// Max number of bytes in each attribute
290 max_attribute_length: val usize,
291 /// Maximum length of strings reprsenting characters, comments, and processing instructions
292 max_data_length: val usize,
293 /// Allow `<?xml encoding="bogus"?>`
294 ignore_invalid_encoding_declarations: val bool
295}
296
297#[test]
298fn mime_parse() {
299 let c = ParserConfig::new()
300 .content_type("text/xml;charset=Us-AScii")
301 .max_entity_expansion_length(1000);
302 assert_eq!(c.override_encoding, Some(Encoding::Ascii));
303
304 let c = ParserConfig::new()
305 .max_entity_expansion_depth(3)
306 .content_type("text/xml;charset = \"UTF-16\"");
307 assert_eq!(c.override_encoding, Some(Encoding::Utf16));
308}