1#[cfg(test)]
16mod test_from_str;
17
18use std::{borrow::Cow, fmt, iter::Peekable, ops::RangeInclusive};
19
20use super::Element;
21use crate::{
22 warning::{self, CaveatDeferred, IntoCaveatDeferred as _},
23 Caveat, IntoCaveat as _,
24};
25
26const ESCAPE_CHAR: char = '\\';
27
28#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
30pub enum Warning {
31 ControlCharacter(usize),
33
34 DecodeUtf16(usize, u16),
36
37 InvalidEscape(usize),
39
40 UnexpectedEndOfString(usize),
42}
43
44impl crate::Warning for Warning {
45 fn id(&self) -> warning::Id {
47 match self {
48 Self::ControlCharacter(_) => {
49 warning::Id::from_static("control_character_while_parsing_string")
50 }
51 Self::DecodeUtf16(..) => warning::Id::from_static("decode_utf_1_6"),
52 Self::InvalidEscape(_) => warning::Id::from_static("invalid_escape"),
53 Self::UnexpectedEndOfString(_) => warning::Id::from_static("unexpected_end_of_string"),
54 }
55 }
56}
57
58impl fmt::Display for Warning {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 match self {
61 Self::ControlCharacter(index) => {
62 write!(
63 f,
64 "Control chars were found at index `{index}` while decoding a JSON string."
65 )
66 }
67 Self::DecodeUtf16(index, code) => {
68 write!(
69 f,
70 "A UTF-16 surrogate pair `{code}` failed to decode at index: `{index}`."
71 )
72 }
73 Self::InvalidEscape(index) => {
74 write!(
75 f,
76 "String contains an invalid escape char at index: `{index})`."
77 )
78 }
79 Self::UnexpectedEndOfString(index) => {
80 write!(f, "The String ended prematurely at index: `{index}`.")
81 }
82 }
83 }
84}
85
86pub(super) fn analyze<'buf>(
88 s: &'buf str,
89 elem: &Element<'buf>,
90) -> Caveat<super::PendingStr<'buf>, Warning> {
91 let mut warnings = warning::Set::new();
92
93 if let Some((index, _)) = s.char_indices().find(|(_, ch)| ch.is_control()) {
96 warnings.insert(elem, Warning::ControlCharacter(index));
97 }
98
99 if s.chars().any(|ch| ch == ESCAPE_CHAR) {
100 super::PendingStr::HasEscapes(super::EscapeStr(s)).into_caveat(warnings)
101 } else {
102 super::PendingStr::NoEscapes(s).into_caveat(warnings)
103 }
104}
105
106pub(super) fn from_raw<'buf>(s: &'buf str) -> CaveatDeferred<Cow<'buf, str>, Warning> {
112 let mut warnings = warning::SetDeferred::new();
113
114 if !s.chars().any(|ch| ch == ESCAPE_CHAR) {
117 if let Some((index, _)) = s.char_indices().find(|(_, ch)| ch.is_control()) {
118 warnings.insert(Warning::ControlCharacter(index));
119 }
120 return Cow::Borrowed(s).into_caveat_deferred(warnings);
121 }
122
123 let mut buf = Buffer::with_capacity(s.len());
124 for decoded in Decoded::from_str(s) {
125 match decoded {
126 Ok(ch) => buf.push(ch),
127 Err(warn_kind) => {
128 warnings.insert(warn_kind);
129 return Cow::Borrowed(s).into_caveat_deferred(warnings);
130 }
131 }
132 }
133
134 Cow::<'buf, str>::Owned(buf.into_string()).into_caveat_deferred(warnings)
135}
136
137pub(super) fn lexical_issues(s: &str) -> super::LexicalIssues {
142 let escapes = s.as_bytes().contains(&b'\\');
145
146 let non_printable_ascii = if escapes {
150 decoded_has_non_printable_ascii(s)
151 } else {
152 s.chars().any(is_non_printable_ascii)
153 };
154
155 super::LexicalIssues {
156 escapes,
157 non_printable_ascii,
158 }
159}
160
161fn decoded_has_non_printable_ascii(s: &str) -> bool {
166 let mut found = false;
167 for decoded in Decoded::from_str(s) {
168 match decoded {
169 Ok(ch) => {
170 if is_non_printable_ascii(ch) {
171 found = true;
172 }
173 }
174 Err(_) => return s.chars().any(is_non_printable_ascii),
175 }
176 }
177 found
178}
179
180fn is_non_printable_ascii(ch: char) -> bool {
182 ch.is_ascii_whitespace() || ch.is_ascii_control()
183}
184
185pub(super) fn eq(raw: &str, other: &str) -> Result<bool, Warning> {
193 let mut decoded = Decoded::from_str(raw);
194 let mut expected = other.chars();
195
196 loop {
197 match decoded.next() {
198 Some(Err(warn_kind)) => return Err(warn_kind),
199 Some(Ok(actual)) => {
200 if expected.next() != Some(actual) {
201 return Ok(false);
202 }
203 }
204 None => return Ok(expected.next().is_none()),
206 }
207 }
208}
209
210pub(super) fn eq_ignore_ascii_case(raw: &str, other: &str) -> Result<bool, Warning> {
212 let mut decoded = Decoded::from_str(raw);
213 let mut expected = other.chars();
214
215 loop {
216 match decoded.next() {
217 Some(Err(warn_kind)) => return Err(warn_kind),
218 Some(Ok(actual)) => match expected.next() {
219 Some(expected) if expected.eq_ignore_ascii_case(&actual) => {}
220 _ => return Ok(false),
221 },
222 None => return Ok(expected.next().is_none()),
224 }
225 }
226}
227
228fn parse_escape(chars: &mut Chars<'_>) -> Result<char, Warning> {
233 let (index, ch) = chars.next_or_eof()?;
234
235 let ch = match ch {
236 '"' => '"',
237 '\\' => '\\',
238 '/' => '/',
239 'b' => '\x08',
240 'f' => '\x0c',
241 'n' => '\n',
242 'r' => '\r',
243 't' => '\t',
244 'u' => return parse_unicode_escape(chars),
245 _ => {
246 return Err(Warning::InvalidEscape(index));
247 }
248 };
249
250 if ch.is_control() {
251 return Err(Warning::ControlCharacter(index));
252 }
253
254 Ok(ch)
255}
256
257fn parse_unicode_escape(chars: &mut Chars<'_>) -> Result<char, Warning> {
264 const HIGH_SURROGATE: RangeInclusive<u16> = 0xD800..=0xDBFF;
269
270 let n1 = decode_hex_escape(chars)?;
271
272 let ch = if HIGH_SURROGATE.contains(&n1) {
273 let Some(n2) = chars.is_next_escape()? else {
278 return Err(Warning::InvalidEscape(chars.index));
279 };
280 decode_surrogate_pair(n1, n2, chars.index)?
281 } else {
282 let Some(ch) = char::from_u32(u32::from(n1)) else {
283 return Err(Warning::InvalidEscape(chars.index));
284 };
285 ch
286 };
287
288 if ch.is_control() {
289 return Err(Warning::ControlCharacter(chars.index));
290 }
291
292 Ok(ch)
293}
294
295struct Chars<'buf> {
297 char_indices: Peekable<std::str::CharIndices<'buf>>,
303
304 push_back: Option<(usize, char)>,
307
308 index: usize,
310}
311
312impl<'buf> Chars<'buf> {
313 fn from_str(s: &'buf str) -> Self {
315 Self {
316 char_indices: s.char_indices().peekable(),
317 push_back: None,
318 index: 0,
319 }
320 }
321
322 fn next_or_eof(&mut self) -> Result<(usize, char), Warning> {
325 if let Some((index, ch)) = self.next() {
326 if ch.is_control() {
327 return Err(Warning::ControlCharacter(index));
328 }
329
330 Ok((index, ch))
331 } else {
332 Err(Warning::UnexpectedEndOfString(self.index))
333 }
334 }
335
336 fn is_next_escape(&mut self) -> Result<Option<u16>, Warning> {
342 let Some(backslash) = self.char_indices.next_if(|(_, ch)| *ch == ESCAPE_CHAR) else {
343 return Ok(None);
344 };
345
346 if self.char_indices.next_if(|(_, ch)| *ch == 'u').is_none() {
347 self.push_back = Some(backslash);
348 return Ok(None);
349 }
350
351 let n = decode_hex_escape(self)?;
352 Ok(Some(n))
353 }
354}
355
356impl Iterator for Chars<'_> {
357 type Item = (usize, char);
358
359 fn next(&mut self) -> Option<Self::Item> {
360 if let Some(item) = self.push_back.take() {
361 self.index = item.0;
362 return Some(item);
363 }
364 if let Some((index, char)) = self.char_indices.next() {
365 self.index = index;
366 Some((index, char))
367 } else {
368 None
369 }
370 }
371}
372
373struct Decoded<'buf> {
378 chars: Chars<'buf>,
379}
380
381impl<'buf> Decoded<'buf> {
382 fn from_str(s: &'buf str) -> Self {
384 Self {
385 chars: Chars::from_str(s),
386 }
387 }
388}
389
390impl Iterator for Decoded<'_> {
391 type Item = Result<char, Warning>;
392
393 fn next(&mut self) -> Option<Self::Item> {
394 let (index, ch) = self.chars.next()?;
395
396 if ch == ESCAPE_CHAR {
397 Some(parse_escape(&mut self.chars))
398 } else if ch.is_control() {
399 Some(Err(Warning::ControlCharacter(index)))
400 } else {
401 Some(Ok(ch))
402 }
403 }
404}
405
406struct Buffer {
408 buf: String,
410}
411
412impl Buffer {
413 fn with_capacity(capacity: usize) -> Self {
415 Self {
416 buf: String::with_capacity(capacity),
417 }
418 }
419
420 fn push(&mut self, ch: char) {
422 self.buf.push(ch);
423 }
424
425 fn into_string(self) -> String {
427 self.buf
428 }
429}
430
431fn decode_surrogate_pair(n1: u16, n2: u16, index: usize) -> Result<char, Warning> {
435 let Some(ch) = char::decode_utf16([n1, n2]).next() else {
436 return Err(Warning::InvalidEscape(index));
437 };
438
439 match ch {
440 Ok(ch) => Ok(ch),
441 Err(err) => Err(Warning::DecodeUtf16(index, err.unpaired_surrogate())),
442 }
443}
444
445fn decode_hex_escape(chars: &mut Chars<'_>) -> Result<u16, Warning> {
447 const RADIX: u32 = 16;
448
449 let (_, one) = chars.next_or_eof()?;
450 let (_, two) = chars.next_or_eof()?;
451 let (_, three) = chars.next_or_eof()?;
452 let (index, four) = chars.next_or_eof()?;
453
454 let string = [one, two, three, four].into_iter().collect::<String>();
455 let Ok(n) = u16::from_str_radix(&string, RADIX) else {
456 return Err(Warning::InvalidEscape(index));
457 };
458
459 Ok(n)
460}