From fd9394370a0c2260891afbad3a06bf94a703c572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Fri, 30 Jul 2021 17:16:13 +0200 Subject: [PATCH] Ensure that {%break%} is only used inside of a loop --- askama_shared/src/parser.rs | 123 +++++++++++------- testing/tests/ui/break_outside_of_loop.rs | 11 ++ testing/tests/ui/break_outside_of_loop.stderr | 8 ++ 3 files changed, 95 insertions(+), 47 deletions(-) create mode 100644 testing/tests/ui/break_outside_of_loop.rs create mode 100644 testing/tests/ui/break_outside_of_loop.stderr diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs index 941eb0963..eb41844f2 100644 --- a/askama_shared/src/parser.rs +++ b/askama_shared/src/parser.rs @@ -1,3 +1,6 @@ +use std::cell::Cell; +use std::str; + use nom::branch::alt; use nom::bytes::complete::{escaped, is_not, tag, take_until}; use nom::character::complete::{anychar, char, digit1}; @@ -6,7 +9,6 @@ use nom::error::{Error, ParseError}; use nom::multi::{fold_many0, many0, many1, separated_list0, separated_list1}; use nom::sequence::{delimited, pair, preceded, terminated, tuple}; use nom::{self, error_position, Compare, IResult, InputTake}; -use std::str; use crate::{CompileError, Syntax}; @@ -179,14 +181,19 @@ enum ContentState { End(usize), } -fn take_content<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> ParserError<'a, Node<'a>> { +struct State<'a> { + syntax: &'a Syntax<'a>, + loop_depth: Cell, +} + +fn take_content<'a>(i: &'a [u8], s: &State<'a>) -> ParserError<'a, Node<'a>> { use crate::parser::ContentState::*; - let bs = s.block_start.as_bytes()[0]; - let be = s.block_start.as_bytes()[1]; - let cs = s.comment_start.as_bytes()[0]; - let ce = s.comment_start.as_bytes()[1]; - let es = s.expr_start.as_bytes()[0]; - let ee = s.expr_start.as_bytes()[1]; + let bs = s.syntax.block_start.as_bytes()[0]; + let be = s.syntax.block_start.as_bytes()[1]; + let cs = s.syntax.comment_start.as_bytes()[0]; + let ce = s.syntax.comment_start.as_bytes()[1]; + let es = s.syntax.expr_start.as_bytes()[0]; + let ee = s.syntax.expr_start.as_bytes()[1]; let mut state = Start; for (idx, c) in i.iter().enumerate() { @@ -663,7 +670,7 @@ fn expr_any(i: &[u8]) -> IResult<&[u8], Expr<'_>> { alt((range_right, compound, expr_or))(i) } -fn expr_node<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>> { +fn expr_node<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], Node<'a>> { let mut p = tuple(( |i| tag_expr_start(i, s), cut(tuple((opt(tag("-")), ws(expr_any), opt(tag("-")), |i| { @@ -709,7 +716,7 @@ fn cond_if(i: &[u8]) -> IResult<&[u8], CondTest<'_>> { Ok((i, CondTest { target, expr })) } -fn cond_block<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Cond<'a>> { +fn cond_block<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], Cond<'a>> { let mut p = tuple(( |i| tag_block_start(i, s), opt(tag("-")), @@ -725,7 +732,7 @@ fn cond_block<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Cond<'a>> Ok((i, (Ws(pws.is_some(), nws.is_some()), cond, block))) } -fn block_if<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>> { +fn block_if<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], Node<'a>> { let mut p = tuple(( opt(tag("-")), cond_if, @@ -751,7 +758,7 @@ fn block_if<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>> { Ok((i, Node::Cond(res, Ws(pws2.is_some(), nws2.is_some())))) } -fn match_else_block<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], When<'a>> { +fn match_else_block<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], When<'a>> { let mut p = tuple(( |i| tag_block_start(i, s), opt(tag("-")), @@ -769,7 +776,7 @@ fn match_else_block<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Whe )) } -fn when_block<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], When<'a>> { +fn when_block<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], When<'a>> { let mut p = tuple(( |i| tag_block_start(i, s), opt(tag("-")), @@ -785,7 +792,7 @@ fn when_block<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], When<'a>> Ok((i, (Ws(pws.is_some(), nws.is_some()), target, block))) } -fn block_match<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>> { +fn block_match<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], Node<'a>> { let mut p = tuple(( opt(tag("-")), ws(tag("match")), @@ -865,7 +872,14 @@ fn block_let(i: &[u8]) -> IResult<&[u8], Node<'_>> { )) } -fn block_for<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>> { +fn parse_loop_content<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], Vec>> { + s.loop_depth.set(s.loop_depth.get() + 1); + let (i, node) = parse_template(i, s)?; + s.loop_depth.set(s.loop_depth.get() - 1); + Ok((i, node)) +} + +fn block_for<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], Node<'a>> { let mut p = tuple(( opt(tag("-")), ws(tag("for")), @@ -877,7 +891,7 @@ fn block_for<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>> opt(tag("-")), |i| tag_block_end(i, s), cut(tuple(( - |i| parse_template(i, s), + |i| parse_loop_content(i, s), cut(tuple(( |i| tag_block_start(i, s), opt(tag("-")), @@ -906,7 +920,7 @@ fn block_extends(i: &[u8]) -> IResult<&[u8], Node<'_>> { Ok((i, Node::Extends(name))) } -fn block_block<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>> { +fn block_block<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], Node<'a>> { let mut start = tuple(( opt(tag("-")), ws(tag("block")), @@ -981,7 +995,7 @@ fn block_import(i: &[u8]) -> IResult<&[u8], Node<'_>> { )) } -fn block_macro<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>> { +fn block_macro<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], Node<'a>> { let mut p = tuple(( opt(tag("-")), ws(tag("macro")), @@ -1021,7 +1035,7 @@ fn block_macro<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a> )) } -fn block_raw<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>> { +fn block_raw<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], Node<'a>> { let mut p = tuple(( opt(tag("-")), ws(tag("raw")), @@ -1048,20 +1062,31 @@ fn block_raw<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>> )) } -fn break_statement(i: &[u8]) -> IResult<&[u8], Node<'_>> { +fn break_statement<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], Node<'a>> { let mut p = tuple((opt(tag("-")), ws(tag("break")), opt(tag("-")))); - let (i, (pws, _, nws)) = p(i)?; - Ok((i, Node::Break(Ws(pws.is_some(), nws.is_some())))) + let (j, (pws, _, nws)) = p(i)?; + if s.loop_depth.get() == 0 { + return Err(nom::Err::Failure(error_position!( + i, + nom::error::ErrorKind::Tag + ))); + } + Ok((j, Node::Break(Ws(pws.is_some(), nws.is_some())))) } -fn continue_statement(i: &[u8]) -> IResult<&[u8], Node<'_>> { +fn continue_statement<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], Node<'a>> { let mut p = tuple((opt(tag("-")), ws(tag("continue")), opt(tag("-")))); - let (i, (pws, _, nws)) = p(i)?; - Ok((i, Node::Continue(Ws(pws.is_some(), nws.is_some())))) + let (j, (pws, _, nws)) = p(i)?; + if s.loop_depth.get() == 0 { + return Err(nom::Err::Failure(error_position!( + i, + nom::error::ErrorKind::Tag + ))); + } + Ok((j, Node::Continue(Ws(pws.is_some(), nws.is_some())))) } - -fn block_node<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>> { +fn block_node<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], Node<'a>> { let mut p = tuple(( |i| tag_block_start(i, s), alt(( @@ -1076,8 +1101,8 @@ fn block_node<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>> |i| block_block(i, s), |i| block_macro(i, s), |i| block_raw(i, s), - break_statement, - continue_statement, + |i| break_statement(i, s), + |i| continue_statement(i, s), )), cut(|i| tag_block_end(i, s)), )); @@ -1085,11 +1110,11 @@ fn block_node<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>> Ok((i, contents)) } -fn block_comment_body<'a>(mut i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], &'a [u8]> { +fn block_comment_body<'a>(mut i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], &'a [u8]> { let mut level = 0; loop { - let (end, tail) = take_until(s.comment_end)(i)?; - match take_until::<_, _, Error<_>>(s.comment_start)(i) { + let (end, tail) = take_until(s.syntax.comment_end)(i)?; + match take_until::<_, _, Error<_>>(s.syntax.comment_start)(i) { Ok((start, _)) if start.as_ptr() < end.as_ptr() => { level += 1; i = &start[2..]; @@ -1103,7 +1128,7 @@ fn block_comment_body<'a>(mut i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8 } } -fn block_comment<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>> { +fn block_comment<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], Node<'a>> { let mut p = tuple(( |i| tag_comment_start(i, s), cut(tuple(( @@ -1116,7 +1141,7 @@ fn block_comment<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<' Ok((i, Node::Comment(Ws(pws.is_some(), tail.ends_with(b"-"))))) } -fn parse_template<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Vec>> { +fn parse_template<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], Vec>> { many0(alt(( complete(|i| take_content(i, s)), complete(|i| block_comment(i, s)), @@ -1125,27 +1150,31 @@ fn parse_template<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Vec(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], &'a [u8]> { - tag(s.block_start)(i) +fn tag_block_start<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], &'a [u8]> { + tag(s.syntax.block_start)(i) } -fn tag_block_end<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], &'a [u8]> { - tag(s.block_end)(i) +fn tag_block_end<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], &'a [u8]> { + tag(s.syntax.block_end)(i) } -fn tag_comment_start<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], &'a [u8]> { - tag(s.comment_start)(i) +fn tag_comment_start<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], &'a [u8]> { + tag(s.syntax.comment_start)(i) } -fn tag_comment_end<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], &'a [u8]> { - tag(s.comment_end)(i) +fn tag_comment_end<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], &'a [u8]> { + tag(s.syntax.comment_end)(i) } -fn tag_expr_start<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], &'a [u8]> { - tag(s.expr_start)(i) +fn tag_expr_start<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], &'a [u8]> { + tag(s.syntax.expr_start)(i) } -fn tag_expr_end<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], &'a [u8]> { - tag(s.expr_end)(i) +fn tag_expr_end<'a>(i: &'a [u8], s: &State<'a>) -> IResult<&'a [u8], &'a [u8]> { + tag(s.syntax.expr_end)(i) } pub fn parse<'a>(src: &'a str, syntax: &'a Syntax<'a>) -> Result>, CompileError> { - match parse_template(src.as_bytes(), syntax) { + let state = State { + syntax, + loop_depth: Cell::new(0), + }; + match parse_template(src.as_bytes(), &state) { Ok((left, res)) => { if !left.is_empty() { let s = str::from_utf8(left).unwrap(); diff --git a/testing/tests/ui/break_outside_of_loop.rs b/testing/tests/ui/break_outside_of_loop.rs new file mode 100644 index 000000000..ccbb0a9de --- /dev/null +++ b/testing/tests/ui/break_outside_of_loop.rs @@ -0,0 +1,11 @@ +use askama::Template; + +#[derive(Template)] +#[template( + source = "Have a {%break%}, have a parsing error!", + ext = "txt" +)] +struct MyTemplate; + +fn main() { +} diff --git a/testing/tests/ui/break_outside_of_loop.stderr b/testing/tests/ui/break_outside_of_loop.stderr new file mode 100644 index 000000000..99d1d25ca --- /dev/null +++ b/testing/tests/ui/break_outside_of_loop.stderr @@ -0,0 +1,8 @@ +error: problems parsing template source at row 1, column 9 near: +"break%}, have a parsing error!" + --> $DIR/break_outside_of_loop.rs:3:10 + | +3 | #[derive(Template)] + | ^^^^^^^^ + | + = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)