1mod build;
6mod content;
7mod ctxt;
8mod document;
9mod dtd;
10mod encodings;
11mod uri;
12
13use std::cell::RefCell;
14use std::rc::Rc;
15
16use rupg_common::SqlState;
17
18use crate::TypeError;
19use ctxt::{Report, cstr};
20
21pub use build::{
22 XmlStandalone, escape_xml, sql_name, xml_base64, xml_binhex, xml_comment, xml_concat,
23 xml_element, xml_name, xml_pi, xml_root, xml_text,
24};
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28enum DeclError {
29 InvalidChar,
30 SpaceRequired,
31 StandaloneValue,
32 VersionMissing,
33 MissingEncoding,
34 XmldeclNotFinished,
35}
36
37impl DeclError {
38 fn detail(self) -> &'static str {
40 match self {
41 DeclError::InvalidChar => "Invalid character value.",
42 DeclError::SpaceRequired => "Space required.",
43 DeclError::StandaloneValue => "standalone accepts only 'yes' or 'no'.",
44 DeclError::VersionMissing => "Malformed declaration: missing version.",
45 DeclError::MissingEncoding => "Missing encoding in text declaration.",
46 DeclError::XmldeclNotFinished => "Parsing XML declaration: '?>' expected.",
47 }
48 }
49}
50
51#[derive(Debug, Default)]
53struct Decl<'a> {
54 len: usize,
56 version: Option<&'a [u8]>,
57 encoding: Option<&'a [u8]>,
58 standalone: i32,
60}
61
62fn is_space(c: u8) -> bool {
64 matches!(c, b' ' | b'\t' | b'\n' | b'\r')
65}
66
67fn at(s: &[u8], i: usize) -> u8 {
69 s.get(i).copied().unwrap_or(0)
70}
71
72fn utf8_char(s: &[u8]) -> i64 {
74 let len = s.len().min(4);
75 let b = |i: usize| i64::from(s[i]);
76 if len < 1 {
77 return -1;
78 }
79 let c = s[0];
80 if c & 0x80 == 0 {
81 return b(0);
82 }
83 if len < 2 || s[1] & 0xc0 != 0x80 {
84 return -1;
85 }
86 if c & 0xe0 != 0xe0 {
87 return (b(0) & 0x1f) << 6 | (b(1) & 0x3f);
88 }
89 if len < 3 || s[2] & 0xc0 != 0x80 {
90 return -1;
91 }
92 if c & 0xf0 != 0xf0 {
93 return (b(0) & 0x0f) << 12 | (b(1) & 0x3f) << 6 | (b(2) & 0x3f);
94 }
95 if c & 0xf8 != 0xf0 || len < 4 || s[3] & 0xc0 != 0x80 {
96 return -1;
97 }
98 (b(0) & 0x07) << 18 | (b(1) & 0x3f) << 12 | (b(2) & 0x3f) << 6 | (b(3) & 0x3f)
99}
100
101fn is_name_char(c: i64) -> bool {
103 c.is_positive()
104 && (c >= 0xf8
105 || (0xc0..=0xd6).contains(&c)
106 || (0xd8..=0xf6).contains(&c)
107 || c == 0xb7
108 || u8::try_from(c)
109 .is_ok_and(|c| c.is_ascii_alphanumeric() || matches!(c, b'.' | b'-' | b'_' | b':')))
110}
111
112fn parse_xml_decl(s: &[u8]) -> Result<Decl<'_>, DeclError> {
114 let s = cstr(s);
115 let mut decl = Decl { standalone: -1, ..Decl::default() };
116 let mut p = 0;
117 let skip = |p: &mut usize| {
118 while is_space(at(s, *p)) {
119 *p += 1;
120 }
121 };
122 let quoted = |p: &mut usize| -> Option<&[u8]> {
123 let q = at(s, *p);
124 if q != b'\'' && q != b'"' {
125 return None;
126 }
127 let end = s[*p + 1..].iter().position(|&c| c == q)? + *p + 1;
128 let value = &s[*p + 1..end];
129 *p = end + 1;
130 Some(value)
131 };
132 if s.starts_with(b"<?xml") && !is_name_char(utf8_char(&s[5..])) {
133 p = 5;
134 if !is_space(at(s, p)) {
135 return Err(DeclError::SpaceRequired);
136 }
137 skip(&mut p);
138 if !s[p..].starts_with(b"version") {
139 return Err(DeclError::VersionMissing);
140 }
141 p += 7;
142 skip(&mut p);
143 if at(s, p) != b'=' {
144 return Err(DeclError::VersionMissing);
145 }
146 p += 1;
147 skip(&mut p);
148 decl.version = Some(quoted(&mut p).ok_or(DeclError::VersionMissing)?);
149
150 let save = p;
151 skip(&mut p);
152 if s[p..].starts_with(b"encoding") {
153 if !is_space(at(s, save)) {
154 return Err(DeclError::SpaceRequired);
155 }
156 p += 8;
157 skip(&mut p);
158 if at(s, p) != b'=' {
159 return Err(DeclError::MissingEncoding);
160 }
161 p += 1;
162 skip(&mut p);
163 decl.encoding = Some(quoted(&mut p).ok_or(DeclError::MissingEncoding)?);
164 } else {
165 p = save;
166 }
167
168 let save = p;
169 skip(&mut p);
170 if s[p..].starts_with(b"standalone") {
171 if !is_space(at(s, save)) {
172 return Err(DeclError::SpaceRequired);
173 }
174 p += 10;
175 skip(&mut p);
176 if at(s, p) != b'=' {
177 return Err(DeclError::StandaloneValue);
178 }
179 p += 1;
180 skip(&mut p);
181 let rest = &s[p..];
182 if rest.starts_with(b"'yes'") || rest.starts_with(b"\"yes\"") {
183 decl.standalone = 1;
184 p += 5;
185 } else if rest.starts_with(b"'no'") || rest.starts_with(b"\"no\"") {
186 decl.standalone = 0;
187 p += 4;
188 } else {
189 return Err(DeclError::StandaloneValue);
190 }
191 } else {
192 p = save;
193 }
194
195 skip(&mut p);
196 if !s[p..].starts_with(b"?>") {
197 return Err(DeclError::XmldeclNotFinished);
198 }
199 p += 2;
200 }
201 if !s[..p].is_ascii() {
202 return Err(DeclError::InvalidChar);
203 }
204 decl.len = p;
205 Ok(decl)
206}
207
208fn print_xml_decl(out: &mut Vec<u8>, version: Option<&[u8]>, standalone: i32) -> bool {
210 if version.is_some_and(|v| v != b"1.0") || standalone != -1 {
211 out.extend_from_slice(b"<?xml version=\"");
212 out.extend_from_slice(version.unwrap_or(b"1.0"));
213 out.push(b'"');
214 match standalone {
215 1 => out.extend_from_slice(b" standalone=\"yes\""),
216 0 => out.extend_from_slice(b" standalone=\"no\""),
217 _ => {}
218 }
219 out.extend_from_slice(b"?>");
220 true
221 } else {
222 false
223 }
224}
225
226fn doctype_in_content(s: &[u8]) -> bool {
228 let find = |s: &[u8], from: usize, pat: &[u8]| {
229 s.get(from..)?.windows(pat.len()).position(|w| w == pat).map(|i| i + from)
230 };
231 let mut p = 0;
232 loop {
233 while is_space(at(s, p)) {
234 p += 1;
235 }
236 if at(s, p) != b'<' {
237 return false;
238 }
239 p += 1;
240 if at(s, p) == b'!' {
241 p += 1;
242 if s[p..].starts_with(b"DOCTYPE") {
243 return true;
244 }
245 if !s[p..].starts_with(b"--") {
246 return false;
247 }
248 match find(s, p + 2, b"--") {
249 Some(e) if at(s, e + 2) == b'>' => p = e + 3,
250 _ => return false,
251 }
252 continue;
253 }
254 if at(s, p) != b'?' {
255 return false;
256 }
257 p += 1;
258 match find(s, p, b"?>") {
259 Some(e) => p = e + 2,
260 None => return false,
261 }
262 }
263}
264
265fn parse_error(sqlstate: SqlState, message: &str, report: &Report) -> TypeError {
267 let mut error = TypeError::new(sqlstate, message.to_string());
268 if !report.err_buf.is_empty() {
269 error.detail = Some(String::from_utf8_lossy(&report.err_buf).into_owned());
270 }
271 error
272}
273
274fn xml_parse(s: &[u8], document: bool, report: &Rc<RefCell<Report>>) -> Result<(), TypeError> {
276 let s = cstr(s);
277 let mut as_document = document;
278 let mut count = 0;
279 if !document {
280 let decl = parse_xml_decl(s).map_err(|code| {
281 let mut error = TypeError::new(
282 SqlState::INVALID_XML_CONTENT,
283 "invalid XML content: invalid XML declaration".to_string(),
284 );
285 error.detail = Some(code.detail().to_string());
286 error
287 })?;
288 count = decl.len;
289 as_document = doctype_in_content(&s[count..]);
290 }
291 if as_document {
292 let well_formed = document::read_doc(s, report);
293 let report = report.borrow();
294 if !well_formed || report.err_occurred {
295 return Err(if document {
296 parse_error(SqlState::INVALID_XML_DOCUMENT, "invalid XML document", &report)
297 } else {
298 parse_error(SqlState::INVALID_XML_CONTENT, "invalid XML content", &report)
299 });
300 }
301 } else if count < s.len() {
302 let ret = document::balanced_chunk(&s[count..], report);
303 let report = report.borrow();
304 if ret != 0 || report.err_occurred {
305 return Err(parse_error(SqlState::INVALID_XML_CONTENT, "invalid XML content", &report));
306 }
307 }
308 Ok(())
309}
310
311pub fn xml_in(text: &[u8], document: bool, warn: &mut dyn FnMut(String)) -> Result<(), TypeError> {
317 let report = Rc::new(RefCell::new(Report::default()));
318 let result = xml_parse(text, document, &report);
319 for warning in std::mem::take(&mut report.borrow_mut().warnings) {
320 warn(warning);
321 }
322 result
323}
324
325#[must_use]
327pub fn xml_encoding(bytes: &[u8]) -> Option<String> {
328 parse_xml_decl(bytes).ok()?.encoding.map(|e| String::from_utf8_lossy(e).into_owned())
329}
330
331pub fn xml_out(text: &str, out: &mut Vec<u8>) -> Option<TypeError> {
333 let bytes = text.as_bytes();
334 match parse_xml_decl(bytes) {
335 Ok(decl) => {
336 let mut len = decl.len;
337 if !print_xml_decl(out, decl.version, decl.standalone) && at(bytes, len) == b'\n' {
338 len += 1;
339 }
340 out.extend_from_slice(&bytes[len..]);
341 None
342 }
343 Err(code) => {
344 out.extend_from_slice(bytes);
345 let mut error = TypeError::new(
346 SqlState::DATA_CORRUPTED,
347 "could not parse XML declaration in stored value".to_string(),
348 );
349 error.detail = Some(code.detail().to_string());
350 Some(error)
351 }
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358
359 fn check(text: &str, document: bool) -> Result<Vec<String>, String> {
360 let mut warnings = Vec::new();
361 xml_in(text.as_bytes(), document, &mut |w| warnings.push(w))
362 .map(|()| warnings)
363 .map_err(|e| format!("{}: {}", e.message, e.detail.unwrap_or_default()))
364 }
365
366 fn out(text: &str) -> String {
367 let mut buf = Vec::new();
368 assert!(xml_out(text, &mut buf).is_none());
369 String::from_utf8(buf).unwrap()
370 }
371
372 #[test]
373 fn declarations() {
374 assert_eq!(parse_xml_decl(b"<a/>").map(|d| d.len), Ok(0));
375 assert_eq!(parse_xml_decl(b"<?xml-stylesheet?>").map(|d| d.len), Ok(0));
376 assert_eq!(parse_xml_decl(b"<?xmlversion='1.0'?>").map(|d| d.len), Ok(0));
377 assert_eq!(parse_xml_decl(b"<?xml version='1.0'?>").map(|d| d.len), Ok(21));
378 assert_eq!(parse_xml_decl(b"<?xml?>").map(|d| d.len), Err(DeclError::SpaceRequired));
379 assert_eq!(parse_xml_decl(b"<?xml ?>").map(|d| d.len), Err(DeclError::VersionMissing));
380 assert_eq!(
381 parse_xml_decl(b"<?xml version='1.0'encoding='x'?>").map(|d| d.len),
382 Err(DeclError::SpaceRequired)
383 );
384 assert_eq!(
385 parse_xml_decl(b"<?xml version='1.0' standalone='maybe'?>").map(|d| d.len),
386 Err(DeclError::StandaloneValue)
387 );
388 assert_eq!(
389 parse_xml_decl(b"<?xml version='1.0'").map(|d| d.len),
390 Err(DeclError::XmldeclNotFinished)
391 );
392 assert_eq!(
393 parse_xml_decl("<?xml version='\u{e9}'?>".as_bytes()).map(|d| d.len),
394 Err(DeclError::InvalidChar)
395 );
396 assert_eq!(
397 xml_encoding(b"<?xml version='1.0' encoding=\"LATIN1\"?>").as_deref(),
398 Some("LATIN1")
399 );
400 }
401
402 #[test]
403 fn input() {
404 assert_eq!(check("<a/>", false), Ok(vec![]));
405 assert_eq!(check("", false), Ok(vec![]));
406 assert_eq!(check("text <b>x</b> &", false), Ok(vec![]));
407 assert!(check("<a", false).is_err_and(|e| e.starts_with("invalid XML content: ")));
408 assert!(check("<a/>", true).is_ok());
409 assert!(check("a", true).is_err_and(|e| e.starts_with("invalid XML document: ")));
410 assert!(check("<!DOCTYPE a><a/>", false).is_ok());
411 assert!(check("<?xml version=\"1.1\"?><a/>", true).is_ok_and(|w| w.len() == 1));
412 }
413
414 #[test]
415 fn output() {
416 assert_eq!(out("<a/>"), "<a/>");
417 assert_eq!(out("<?xml version=\"1.0\"?>\n<a/>"), "<a/>");
418 assert_eq!(
419 out("<?xml version='1.1' encoding='UTF-8'?><a/>"),
420 "<?xml version=\"1.1\"?><a/>"
421 );
422 assert_eq!(
423 out("<?xml version='1.0' standalone='no'?><a/>"),
424 "<?xml version=\"1.0\" standalone=\"no\"?><a/>"
425 );
426 }
427}