Skip to content

Commit

Permalink
Ensure that {%break%} is only used inside of a loop
Browse files Browse the repository at this point in the history
  • Loading branch information
Kijewski committed Aug 5, 2021
1 parent 1ab010f commit 9e55253
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 47 deletions.
123 changes: 76 additions & 47 deletions askama_shared/src/parser.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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};

Expand Down Expand Up @@ -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<usize>,
}

fn take_content<'a>(i: &'a [u8], s: &State<'_>) -> 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() {
Expand Down Expand Up @@ -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<'_>) -> 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| {
Expand Down Expand Up @@ -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<'_>) -> IResult<&'a [u8], Cond<'a>> {
let mut p = tuple((
|i| tag_block_start(i, s),
opt(tag("-")),
Expand All @@ -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<'_>) -> IResult<&'a [u8], Node<'a>> {
let mut p = tuple((
opt(tag("-")),
cond_if,
Expand All @@ -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<'_>) -> IResult<&'a [u8], When<'a>> {
let mut p = tuple((
|i| tag_block_start(i, s),
opt(tag("-")),
Expand All @@ -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<'_>) -> IResult<&'a [u8], When<'a>> {
let mut p = tuple((
|i| tag_block_start(i, s),
opt(tag("-")),
Expand All @@ -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<'_>) -> IResult<&'a [u8], Node<'a>> {
let mut p = tuple((
opt(tag("-")),
ws(tag("match")),
Expand Down Expand Up @@ -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<'_>) -> IResult<&'a [u8], Vec<Node<'a>>> {
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<'_>) -> IResult<&'a [u8], Node<'a>> {
let mut p = tuple((
opt(tag("-")),
ws(tag("for")),
Expand All @@ -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("-")),
Expand Down Expand Up @@ -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<'_>) -> IResult<&'a [u8], Node<'a>> {
let mut start = tuple((
opt(tag("-")),
ws(tag("block")),
Expand Down Expand Up @@ -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<'_>) -> IResult<&'a [u8], Node<'a>> {
let mut p = tuple((
opt(tag("-")),
ws(tag("macro")),
Expand Down Expand Up @@ -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<'_>) -> IResult<&'a [u8], Node<'a>> {
let mut p = tuple((
opt(tag("-")),
ws(tag("raw")),
Expand All @@ -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<'_>) -> 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<'_>) -> 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<'_>) -> IResult<&'a [u8], Node<'a>> {
let mut p = tuple((
|i| tag_block_start(i, s),
alt((
Expand All @@ -1076,20 +1101,20 @@ 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)),
));
let (i, (_, contents, _)) = p(i)?;
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<'_>) -> 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..];
Expand All @@ -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<'_>) -> IResult<&'a [u8], Node<'a>> {
let mut p = tuple((
|i| tag_comment_start(i, s),
cut(tuple((
Expand All @@ -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<Node<'a>>> {
fn parse_template<'a>(i: &'a [u8], s: &State<'_>) -> IResult<&'a [u8], Vec<Node<'a>>> {
many0(alt((
complete(|i| take_content(i, s)),
complete(|i| block_comment(i, s)),
Expand All @@ -1125,27 +1150,31 @@ fn parse_template<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Vec<N
)))(i)
}

fn tag_block_start<'a>(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<'_>) -> 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<'_>) -> 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<'_>) -> 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<'_>) -> 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<'_>) -> 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<'_>) -> IResult<&'a [u8], &'a [u8]> {
tag(s.syntax.expr_end)(i)
}

pub fn parse<'a>(src: &'a str, syntax: &'a Syntax<'a>) -> Result<Vec<Node<'a>>, 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();
Expand Down
11 changes: 11 additions & 0 deletions testing/tests/ui/break_outside_of_loop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use askama::Template;

#[derive(Template)]
#[template(
source = "Have a {%break%}, have a parsing error!",
ext = "txt"
)]
struct MyTemplate;

fn main() {
}
8 changes: 8 additions & 0 deletions testing/tests/ui/break_outside_of_loop.stderr
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 9e55253

Please sign in to comment.