parser: reject isolated CRs in string literals

According to <https://doc.rust-lang.org/reference/tokens.html#string-literals>.

Fixes <https://github.com/askama-rs/askama/issues/482>.
Fixes <https://issues.oss-fuzz.com/issues/424227903>.
This commit is contained in:
René Kijewski 2025-06-13 06:17:12 +02:00
parent 163d9780bf
commit 341b850351
4 changed files with 53 additions and 1 deletions

View File

@ -573,6 +573,7 @@ fn str_lit<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> {
Text(&'a str),
Close,
Escape,
Cr(bool),
}
let start = *i;
@ -583,9 +584,10 @@ fn str_lit<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> {
while !i.is_empty() {
let seq = alt((
repeat::<_, _, (), _, _>(1.., none_of(['\\', '"']))
repeat::<_, _, (), _, _>(1.., none_of(['\r', '\\', '"']))
.take()
.map(Sequence::Text),
preceded('\r', opt('\n')).map(|c| Sequence::Cr(c.is_some())),
'\\'.value(Sequence::Escape),
peek('"').value(Sequence::Close),
))
@ -598,6 +600,16 @@ fn str_lit<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> {
contains_null = contains_null || s.bytes().any(|c: u8| c == 0);
continue;
}
Sequence::Cr(has_lf) => {
if has_lf {
continue;
} else {
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
r#"bare CR not allowed in string, use `\r` instead"#,
start,
)));
}
}
Sequence::Close => break,
Sequence::Escape => {}
}

View File

@ -0,0 +1 @@
˙˙˙{{ (" ")}}˙ ˙i˙

View File

@ -102,4 +102,19 @@ struct SurrogateLow3;
#[template(ext = "txt", source = r#"{{ c"hello \u{de09} world" }}"#)]
struct SurrogateHigh3;
// CR (\r) is only allowed if followed by NL (\n)
// (regression test for <https://github.com/askama-rs/askama/issues/482>)
#[derive(Template)]
#[template(ext = "txt", source = "{{ \"hello \r world\" }}")]
struct UnpairedCr;
#[derive(Template)]
#[template(ext = "txt", source = "{{ b\"hello \r world\" }}")]
struct UnpairedCrInBytes;
#[derive(Template)]
#[template(ext = "txt", source = "{{ c\"hello \r world\" }}")]
struct UnpairedCrInCstring;
fn main() {}

View File

@ -141,3 +141,27 @@ error: unicode escape must not be a surrogate
|
102 | #[template(ext = "txt", source = r#"{{ c"hello \u{de09} world" }}"#)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: bare CR not allowed in string, use `\r` instead
--> <source attribute>:1:4
"hello \r world\" }}"
--> tests/ui/illegal-string-literals.rs:109:34
|
109 | #[template(ext = "txt", source = "{{ \"hello \r world\" }}")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: bare CR not allowed in string, use `\r` instead
--> <source attribute>:1:5
"hello \r world\" }}"
--> tests/ui/illegal-string-literals.rs:113:34
|
113 | #[template(ext = "txt", source = "{{ b\"hello \r world\" }}")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: bare CR not allowed in string, use `\r` instead
--> <source attribute>:1:5
"hello \r world\" }}"
--> tests/ui/illegal-string-literals.rs:117:34
|
117 | #[template(ext = "txt", source = "{{ c\"hello \r world\" }}")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^