mirror of
https://github.com/askama-rs/askama.git
synced 2025-10-02 07:20:55 +00:00
Merge pull request #479 from Kijewski/issue-425
parser: reject illegal string literals
This commit is contained in:
commit
9309acaa01
@ -394,6 +394,7 @@ fn compile_time_escape<'a>(expr: &Expr<'a>, escaper: &str) -> Option<Writable<'a
|
|||||||
Expr::StrLit(StrLit {
|
Expr::StrLit(StrLit {
|
||||||
prefix: None,
|
prefix: None,
|
||||||
content,
|
content,
|
||||||
|
..
|
||||||
}) => {
|
}) => {
|
||||||
if content.find('\\').is_none() {
|
if content.find('\\').is_none() {
|
||||||
// if the literal does not contain any backslashes, then it does not need unescaping
|
// if the literal does not contain any backslashes, then it does not need unescaping
|
||||||
|
@ -275,11 +275,19 @@ impl<'a> Generator<'a, '_> {
|
|||||||
&WithSpan::new_without_span(Expr::StrLit(StrLit {
|
&WithSpan::new_without_span(Expr::StrLit(StrLit {
|
||||||
prefix: None,
|
prefix: None,
|
||||||
content: "",
|
content: "",
|
||||||
|
contains_null: false,
|
||||||
|
contains_unicode_character: false,
|
||||||
|
contains_unicode_escape: false,
|
||||||
|
contains_high_ascii: false,
|
||||||
}));
|
}));
|
||||||
const PLURAL: &WithSpan<'static, Expr<'static>> =
|
const PLURAL: &WithSpan<'static, Expr<'static>> =
|
||||||
&WithSpan::new_without_span(Expr::StrLit(StrLit {
|
&WithSpan::new_without_span(Expr::StrLit(StrLit {
|
||||||
prefix: None,
|
prefix: None,
|
||||||
content: "s",
|
content: "s",
|
||||||
|
contains_null: false,
|
||||||
|
contains_unicode_character: false,
|
||||||
|
contains_unicode_escape: false,
|
||||||
|
contains_high_ascii: false,
|
||||||
}));
|
}));
|
||||||
const ARGUMENTS: &[&FilterArgument; 3] = &[
|
const ARGUMENTS: &[&FilterArgument; 3] = &[
|
||||||
FILTER_SOURCE,
|
FILTER_SOURCE,
|
||||||
@ -494,7 +502,10 @@ impl<'a> Generator<'a, '_> {
|
|||||||
|
|
||||||
let [source, opt_escaper] = collect_filter_args(ctx, "escape", node, args, ARGUMENTS)?;
|
let [source, opt_escaper] = collect_filter_args(ctx, "escape", node, args, ARGUMENTS)?;
|
||||||
let opt_escaper = if !is_argument_placeholder(opt_escaper) {
|
let opt_escaper = if !is_argument_placeholder(opt_escaper) {
|
||||||
let Expr::StrLit(StrLit { prefix, content }) = **opt_escaper else {
|
let Expr::StrLit(StrLit {
|
||||||
|
prefix, content, ..
|
||||||
|
}) = **opt_escaper
|
||||||
|
else {
|
||||||
return Err(ctx.generate_error("invalid escaper type for escape filter", node));
|
return Err(ctx.generate_error("invalid escaper type for escape filter", node));
|
||||||
};
|
};
|
||||||
if let Some(prefix) = prefix {
|
if let Some(prefix) = prefix {
|
||||||
|
@ -1278,12 +1278,12 @@ fn test_macro_names_that_need_escaping() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[rustfmt::skip] // FIXME: rustfmt bug <https://github.com/rust-lang/rustfmt/issues/6565>
|
|
||||||
fn test_macro_calls_need_proper_tokens() -> Result<(), syn::Error> {
|
fn test_macro_calls_need_proper_tokens() -> Result<(), syn::Error> {
|
||||||
// Regression test for fuzzed error <https://github.com/askama-rs/askama/issues/459>.
|
// Regression test for fuzzed error <https://github.com/askama-rs/askama/issues/459>.
|
||||||
// Macro calls can contains any valid tokens, but only valid tokens.
|
// Macro calls can contains any valid tokens, but only valid tokens.
|
||||||
// Invalid tokens will be rejected by rust, so we must not emit them.
|
// Invalid tokens will be rejected by rust, so we must not emit them.
|
||||||
|
|
||||||
|
#[rustfmt::skip] // FIXME: rustfmt bug <https://github.com/rust-lang/rustfmt/issues/5489>
|
||||||
let input = quote! {
|
let input = quote! {
|
||||||
#[template(
|
#[template(
|
||||||
ext = "",
|
ext = "",
|
||||||
@ -1294,7 +1294,11 @@ fn test_macro_calls_need_proper_tokens() -> Result<(), syn::Error> {
|
|||||||
struct f {}
|
struct f {}
|
||||||
};
|
};
|
||||||
let output = derive_template(input, import_askama);
|
let output = derive_template(input, import_askama);
|
||||||
assert!(output.to_string().contains("expected valid tokens in macro call"));
|
assert!(
|
||||||
|
output
|
||||||
|
.to_string()
|
||||||
|
.contains("expected valid tokens in macro call")
|
||||||
|
);
|
||||||
let _: syn::File = syn::parse2(output)?;
|
let _: syn::File = syn::parse2(output)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -1311,7 +1315,7 @@ fn test_macro_call_raw_prefix_without_data() -> Result<(), syn::Error> {
|
|||||||
assert!(
|
assert!(
|
||||||
output
|
output
|
||||||
.to_string()
|
.to_string()
|
||||||
.contains("raw prefix `r#` is only allowed with raw identifiers and raw strings")
|
.contains("prefix `r#` is only allowed with raw identifiers and raw strings")
|
||||||
);
|
);
|
||||||
let _: syn::File = syn::parse2(output)?;
|
let _: syn::File = syn::parse2(output)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -1325,10 +1329,27 @@ fn test_macro_call_reserved_prefix() -> Result<(), syn::Error> {
|
|||||||
enum q {}
|
enum q {}
|
||||||
};
|
};
|
||||||
let output = derive_template(input, import_askama);
|
let output = derive_template(input, import_askama);
|
||||||
|
assert!(output.to_string().contains("reserved prefix `hello#`"));
|
||||||
|
let _: syn::File = syn::parse2(output)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_macro_call_valid_raw_cstring() -> Result<(), syn::Error> {
|
||||||
|
// Regression test for <https://github.com/askama-rs/askama/issues/478>.
|
||||||
|
// CString literals must not contain NULs.
|
||||||
|
|
||||||
|
#[rustfmt::skip] // FIXME: rustfmt bug <https://github.com/rust-lang/rustfmt/issues/5489>
|
||||||
|
let input = quote! {
|
||||||
|
#[template(ext = "", source = "{{ c\"\0\" }}")]
|
||||||
|
// ^^ NUL is not allowed in cstring literals
|
||||||
|
enum l {}
|
||||||
|
};
|
||||||
|
let output = derive_template(input, import_askama);
|
||||||
assert!(
|
assert!(
|
||||||
output
|
output
|
||||||
.to_string()
|
.to_string()
|
||||||
.contains("reserved prefix `hello#`, only `r#` is allowed")
|
.contains("null characters in C string literals are not supported")
|
||||||
);
|
);
|
||||||
let _: syn::File = syn::parse2(output)?;
|
let _: syn::File = syn::parse2(output)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -7,13 +7,12 @@ use winnow::combinator::{
|
|||||||
alt, cut_err, fail, not, opt, peek, preceded, repeat, separated, terminated,
|
alt, cut_err, fail, not, opt, peek, preceded, repeat, separated, terminated,
|
||||||
};
|
};
|
||||||
use winnow::error::ParserError as _;
|
use winnow::error::ParserError as _;
|
||||||
use winnow::token::one_of;
|
|
||||||
|
|
||||||
use crate::node::CondTest;
|
use crate::node::CondTest;
|
||||||
use crate::{
|
use crate::{
|
||||||
CharLit, ErrorContext, Level, Num, ParseErr, ParseResult, PathOrIdentifier, Span, StrLit,
|
CharLit, ErrorContext, Level, Num, ParseErr, ParseResult, PathOrIdentifier, Span, StrLit,
|
||||||
WithSpan, char_lit, filter, identifier, keyword, num_lit, path_or_identifier, skip_ws0,
|
StrPrefix, WithSpan, char_lit, filter, identifier, keyword, num_lit, path_or_identifier,
|
||||||
skip_ws1, str_lit, ws,
|
skip_ws0, skip_ws1, str_lit, ws,
|
||||||
};
|
};
|
||||||
|
|
||||||
macro_rules! expr_prec_layer {
|
macro_rules! expr_prec_layer {
|
||||||
@ -773,12 +772,12 @@ impl<'a> Suffix<'a> {
|
|||||||
fn token<'a>(i: &mut &'a str) -> ParseResult<'a, Token> {
|
fn token<'a>(i: &mut &'a str) -> ParseResult<'a, Token> {
|
||||||
// <https://doc.rust-lang.org/reference/tokens.html>
|
// <https://doc.rust-lang.org/reference/tokens.html>
|
||||||
let some_other = alt((
|
let some_other = alt((
|
||||||
// keywords + (raw) identifiers + raw strings
|
|
||||||
identifier_or_prefixed_string,
|
|
||||||
// literals
|
// literals
|
||||||
Expr::char.value(Token::SomeOther),
|
Expr::char.value(Token::SomeOther),
|
||||||
Expr::str.value(Token::SomeOther),
|
Expr::str.value(Token::SomeOther),
|
||||||
Expr::num.value(Token::SomeOther),
|
Expr::num.value(Token::SomeOther),
|
||||||
|
// keywords + (raw) identifiers + raw strings
|
||||||
|
identifier_or_prefixed_string.value(Token::SomeOther),
|
||||||
// lifetimes
|
// lifetimes
|
||||||
('\'', identifier, not(peek('\''))).value(Token::SomeOther),
|
('\'', identifier, not(peek('\''))).value(Token::SomeOther),
|
||||||
// punctuations
|
// punctuations
|
||||||
@ -788,43 +787,39 @@ impl<'a> Suffix<'a> {
|
|||||||
alt((open.map(Token::Open), close.map(Token::Close), some_other)).parse_next(i)
|
alt((open.map(Token::Open), close.map(Token::Close), some_other)).parse_next(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn identifier_or_prefixed_string<'a>(i: &mut &'a str) -> ParseResult<'a, Token> {
|
fn identifier_or_prefixed_string<'a>(i: &mut &'a str) -> ParseResult<'a, ()> {
|
||||||
|
// <https://doc.rust-lang.org/reference/tokens.html#r-lex.token.literal.str-raw.syntax>
|
||||||
|
|
||||||
let prefix = identifier.parse_next(i)?;
|
let prefix = identifier.parse_next(i)?;
|
||||||
if opt('#').parse_next(i)?.is_none() {
|
let hashes: usize = repeat(.., '#').parse_next(i)?;
|
||||||
// a simple identifier
|
if hashes >= 256 {
|
||||||
return Ok(Token::SomeOther);
|
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||||
|
"a maximum of 255 hashes `#` are allowed with raw strings",
|
||||||
|
prefix,
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
match prefix {
|
let str_kind = match prefix {
|
||||||
// raw cstring or byte slice
|
// raw cstring or byte slice
|
||||||
"cr" | "br" => {}
|
"br" => Some(StrPrefix::Binary),
|
||||||
|
"cr" => Some(StrPrefix::CLike),
|
||||||
// raw string string or identifier
|
// raw string string or identifier
|
||||||
"r" => {
|
"r" => None,
|
||||||
if opt(identifier).parse_next(i)?.is_some() {
|
// a simple identifier
|
||||||
return Ok(Token::SomeOther);
|
_ if hashes == 0 => return Ok(()),
|
||||||
}
|
|
||||||
}
|
|
||||||
// reserved prefix: reject
|
// reserved prefix: reject
|
||||||
_ => {
|
_ => {
|
||||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||||
format!(
|
format!("reserved prefix `{}#`", prefix.escape_debug()),
|
||||||
"reserved prefix `{}#`, only `r#` is allowed",
|
|
||||||
prefix.escape_debug(),
|
|
||||||
),
|
|
||||||
prefix,
|
prefix,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
let hashes: usize = repeat(.., '#').parse_next(i)?;
|
if opt('"').parse_next(i)?.is_some() {
|
||||||
if opt('"').parse_next(i)?.is_none() {
|
// got a raw string
|
||||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
|
||||||
"raw prefix `r#` is only allowed with raw identifiers and raw strings",
|
|
||||||
prefix,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some((_, j)) = i.split_once(&format!("\"#{:#<hashes$}", "")) else {
|
let Some((inner, j)) = i.split_once(&format!("\"{:#<hashes$}", "")) else {
|
||||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||||
"unterminated raw string",
|
"unterminated raw string",
|
||||||
prefix,
|
prefix,
|
||||||
@ -832,7 +827,56 @@ impl<'a> Suffix<'a> {
|
|||||||
};
|
};
|
||||||
*i = j;
|
*i = j;
|
||||||
|
|
||||||
Ok(Token::SomeOther)
|
let msg = match str_kind {
|
||||||
|
Some(StrPrefix::Binary) => inner
|
||||||
|
.bytes()
|
||||||
|
.any(|b| !b.is_ascii())
|
||||||
|
.then_some("binary string literals must not contain non-ASCII characters"),
|
||||||
|
Some(StrPrefix::CLike) => inner
|
||||||
|
.bytes()
|
||||||
|
.any(|b| b == 0)
|
||||||
|
.then_some("cstring literals must not contain NUL characters"),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
if let Some(msg) = msg {
|
||||||
|
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(msg, prefix)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else if hashes == 0 {
|
||||||
|
// a simple identifier
|
||||||
|
Ok(())
|
||||||
|
} else if opt(identifier).parse_next(i)?.is_some() {
|
||||||
|
// got a raw identifier
|
||||||
|
|
||||||
|
if str_kind.is_some() {
|
||||||
|
// an invalid raw identifier like `cr#async`
|
||||||
|
Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||||
|
format!(
|
||||||
|
"reserved prefix `{}#`, only `r#` is allowed with raw identifiers",
|
||||||
|
prefix.escape_debug(),
|
||||||
|
),
|
||||||
|
prefix,
|
||||||
|
)))
|
||||||
|
} else if hashes > 1 {
|
||||||
|
// an invalid raw identifier like `r##async`
|
||||||
|
Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||||
|
"only one `#` is allowed in raw identifier delimitation",
|
||||||
|
prefix,
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
// a raw identifier like `r#async`
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||||
|
format!(
|
||||||
|
"prefix `{}#` is only allowed with raw identifiers and raw strings",
|
||||||
|
prefix.escape_debug(),
|
||||||
|
),
|
||||||
|
prefix,
|
||||||
|
)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash<'a>(i: &mut &'a str) -> ParseResult<'a, Token> {
|
fn hash<'a>(i: &mut &'a str) -> ParseResult<'a, Token> {
|
||||||
@ -850,16 +894,36 @@ impl<'a> Suffix<'a> {
|
|||||||
fn punctuation<'a>(i: &mut &'a str) -> ParseResult<'a, ()> {
|
fn punctuation<'a>(i: &mut &'a str) -> ParseResult<'a, ()> {
|
||||||
// <https://doc.rust-lang.org/reference/tokens.html#punctuation>
|
// <https://doc.rust-lang.org/reference/tokens.html#punctuation>
|
||||||
// hash '#' omitted
|
// hash '#' omitted
|
||||||
let one = one_of([
|
|
||||||
'+', '-', '*', '/', '%', '^', '!', '&', '|', '=', '>', '<', '@', '_', '.', ',',
|
const ONE_CHAR: &[u8] = b"+-*/%^!&|=><@_.,;:$?~";
|
||||||
';', ':', '$', '?', '~',
|
const TWO_CHARS: &[&str] = &[
|
||||||
]);
|
|
||||||
let two = alt((
|
|
||||||
"&&", "||", "<<", ">>", "+=", "-=", "*=", "/=", "%=", "^=", "&=", "|=", "==", "!=",
|
"&&", "||", "<<", ">>", "+=", "-=", "*=", "/=", "%=", "^=", "&=", "|=", "==", "!=",
|
||||||
">=", "<=", "..", "::", "->", "=>", "<-",
|
">=", "<=", "..", "::", "->", "=>", "<-",
|
||||||
));
|
];
|
||||||
let three = alt(("<<=", ">>=", "...", "..="));
|
const THREE_CHARS: &[&str] = &["<<=", ">>=", "...", "..="];
|
||||||
alt((three.value(()), two.value(()), one.value(()))).parse_next(i)
|
|
||||||
|
// need to check long to short
|
||||||
|
if let Some((head, tail)) = i.split_at_checked(3) {
|
||||||
|
if THREE_CHARS.contains(&head) {
|
||||||
|
*i = tail;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((head, tail)) = i.split_at_checked(2) {
|
||||||
|
if TWO_CHARS.contains(&head) {
|
||||||
|
*i = tail;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((head, tail)) = i.split_at_checked(1) {
|
||||||
|
if let [head] = head.as_bytes() {
|
||||||
|
if ONE_CHAR.contains(head) {
|
||||||
|
*i = tail;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fail(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open<'a>(i: &mut &'a str) -> ParseResult<'a, Group> {
|
fn open<'a>(i: &mut &'a str) -> ParseResult<'a, Group> {
|
||||||
|
@ -19,10 +19,12 @@ use std::sync::Arc;
|
|||||||
use std::{fmt, str};
|
use std::{fmt, str};
|
||||||
|
|
||||||
use winnow::ascii::take_escaped;
|
use winnow::ascii::take_escaped;
|
||||||
use winnow::combinator::{alt, cut_err, delimited, fail, not, opt, peek, preceded, repeat};
|
use winnow::combinator::{
|
||||||
|
alt, cut_err, delimited, fail, not, opt, peek, preceded, repeat, terminated,
|
||||||
|
};
|
||||||
use winnow::error::FromExternalError;
|
use winnow::error::FromExternalError;
|
||||||
use winnow::stream::Stream as _;
|
use winnow::stream::{AsChar, Stream as _};
|
||||||
use winnow::token::{any, one_of, take_till, take_while};
|
use winnow::token::{any, none_of, one_of, take_till, take_while};
|
||||||
use winnow::{ModalParser, Parser};
|
use winnow::{ModalParser, Parser};
|
||||||
|
|
||||||
use crate::ascii_str::{AsciiChar, AsciiStr};
|
use crate::ascii_str::{AsciiChar, AsciiStr};
|
||||||
@ -544,28 +546,166 @@ impl fmt::Display for StrPrefix {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct StrLit<'a> {
|
pub struct StrLit<'a> {
|
||||||
pub prefix: Option<StrPrefix>,
|
/// the unparsed (but validated) content
|
||||||
pub content: &'a str,
|
pub content: &'a str,
|
||||||
}
|
/// whether the string literal is unprefixed, a cstring or binary slice
|
||||||
|
pub prefix: Option<StrPrefix>,
|
||||||
fn str_lit_without_prefix<'a>(i: &mut &'a str) -> ParseResult<'a> {
|
/// contains a NUL character, either escaped `'\0'` or the very characters;
|
||||||
let s = delimited(
|
/// not allowed in cstring literals
|
||||||
'"',
|
pub contains_null: bool,
|
||||||
opt(take_escaped(take_till(1.., ['\\', '"']), '\\', any)),
|
/// contains a non-ASCII character, either as `\u{123456}` or as an unescaped character;
|
||||||
'"',
|
/// not allowed in binary slices
|
||||||
)
|
pub contains_unicode_character: bool,
|
||||||
.parse_next(i)?;
|
/// contains unicode escape sequences like `\u{12}` (regardless of its range);
|
||||||
Ok(s.unwrap_or_default())
|
/// not allowed in binary slices
|
||||||
|
pub contains_unicode_escape: bool,
|
||||||
|
/// contains a non-ASCII range escape sequence like `\x80`;
|
||||||
|
/// not allowed in unprefix strings
|
||||||
|
pub contains_high_ascii: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn str_lit<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> {
|
fn str_lit<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> {
|
||||||
let (prefix, content) = (opt(alt(('b', 'c'))), str_lit_without_prefix).parse_next(i)?;
|
// <https://doc.rust-lang.org/reference/tokens.html#r-lex.token.literal.str.syntax>
|
||||||
let prefix = match prefix {
|
|
||||||
Some('b') => Some(StrPrefix::Binary),
|
fn inner<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> {
|
||||||
Some('c') => Some(StrPrefix::CLike),
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
_ => None,
|
enum Sequence<'a> {
|
||||||
|
Text(&'a str),
|
||||||
|
Close,
|
||||||
|
Escape,
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = *i;
|
||||||
|
let mut contains_null = false;
|
||||||
|
let mut contains_unicode_character = false;
|
||||||
|
let mut contains_unicode_escape = false;
|
||||||
|
let mut contains_high_ascii = false;
|
||||||
|
|
||||||
|
while !i.is_empty() {
|
||||||
|
let seq = alt((
|
||||||
|
repeat::<_, _, (), _, _>(1.., none_of(['\\', '"']))
|
||||||
|
.take()
|
||||||
|
.map(Sequence::Text),
|
||||||
|
'\\'.value(Sequence::Escape),
|
||||||
|
peek('"').value(Sequence::Close),
|
||||||
|
))
|
||||||
|
.parse_next(i)?;
|
||||||
|
|
||||||
|
match seq {
|
||||||
|
Sequence::Text(s) => {
|
||||||
|
contains_unicode_character =
|
||||||
|
contains_unicode_character || s.bytes().any(|c: u8| !c.is_ascii());
|
||||||
|
contains_null = contains_null || s.bytes().any(|c: u8| c == 0);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Sequence::Close => break,
|
||||||
|
Sequence::Escape => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
match any.parse_next(i)? {
|
||||||
|
'\'' | '"' | 'n' | 'r' | 't' | '\\' => continue,
|
||||||
|
'0' => {
|
||||||
|
contains_null = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
'x' => {
|
||||||
|
let code = take_while(2, AsChar::is_hex_digit).parse_next(i)?;
|
||||||
|
match u8::from_str_radix(code, 16).unwrap() {
|
||||||
|
0 => contains_null = true,
|
||||||
|
128.. => contains_high_ascii = true,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'u' => {
|
||||||
|
contains_unicode_escape = true;
|
||||||
|
let code = delimited('{', take_while(1..6, AsChar::is_hex_digit), '}')
|
||||||
|
.parse_next(i)?;
|
||||||
|
match u32::from_str_radix(code, 16).unwrap() {
|
||||||
|
0 => contains_null = true,
|
||||||
|
0xd800..0xe000 => {
|
||||||
|
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||||
|
"unicode escape must not be a surrogate",
|
||||||
|
start,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
128.. => contains_unicode_character = true,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return fail(i),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(StrLit {
|
||||||
|
content: "",
|
||||||
|
prefix: None,
|
||||||
|
contains_null,
|
||||||
|
contains_unicode_character,
|
||||||
|
contains_unicode_escape,
|
||||||
|
contains_high_ascii,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = *i;
|
||||||
|
|
||||||
|
let prefix = terminated(
|
||||||
|
opt(alt((
|
||||||
|
'b'.value(StrPrefix::Binary),
|
||||||
|
'c'.value(StrPrefix::CLike),
|
||||||
|
))),
|
||||||
|
'"',
|
||||||
|
)
|
||||||
|
.parse_next(i)?;
|
||||||
|
|
||||||
|
let lit = opt(terminated(inner.with_taken(), '"')).parse_next(i)?;
|
||||||
|
let Some((mut lit, content)) = lit else {
|
||||||
|
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||||
|
"unclosed or broken string",
|
||||||
|
start,
|
||||||
|
)));
|
||||||
};
|
};
|
||||||
Ok(StrLit { prefix, content })
|
lit.content = content;
|
||||||
|
lit.prefix = prefix;
|
||||||
|
|
||||||
|
let msg = match prefix {
|
||||||
|
Some(StrPrefix::Binary) => {
|
||||||
|
if lit.contains_unicode_character {
|
||||||
|
Some("non-ASCII character in byte string literal")
|
||||||
|
} else if lit.contains_unicode_escape {
|
||||||
|
Some("unicode escape in byte string")
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(StrPrefix::CLike) => lit
|
||||||
|
.contains_null
|
||||||
|
.then_some("null characters in C string literals are not supported"),
|
||||||
|
None => lit.contains_high_ascii.then_some("out of range hex escape"),
|
||||||
|
};
|
||||||
|
if let Some(msg) = msg {
|
||||||
|
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(msg, start)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(lit)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn str_lit_without_prefix<'a>(i: &mut &'a str) -> ParseResult<'a> {
|
||||||
|
let start = *i;
|
||||||
|
let lit = str_lit.parse_next(i)?;
|
||||||
|
|
||||||
|
let kind = match lit.prefix {
|
||||||
|
Some(StrPrefix::Binary) => Some("binary slice"),
|
||||||
|
Some(StrPrefix::CLike) => Some("cstring"),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
if let Some(kind) = kind {
|
||||||
|
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||||
|
format!("expected an unprefixed normal string, not a {kind}"),
|
||||||
|
start,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(lit.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
@ -1478,7 +1618,11 @@ mod test {
|
|||||||
"",
|
"",
|
||||||
StrLit {
|
StrLit {
|
||||||
prefix: Some(StrPrefix::Binary),
|
prefix: Some(StrPrefix::Binary),
|
||||||
content: "hello"
|
content: "hello",
|
||||||
|
contains_null: false,
|
||||||
|
contains_unicode_character: false,
|
||||||
|
contains_unicode_escape: false,
|
||||||
|
contains_high_ascii: false,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -1488,7 +1632,11 @@ mod test {
|
|||||||
"",
|
"",
|
||||||
StrLit {
|
StrLit {
|
||||||
prefix: Some(StrPrefix::CLike),
|
prefix: Some(StrPrefix::CLike),
|
||||||
content: "hello"
|
content: "hello",
|
||||||
|
contains_null: false,
|
||||||
|
contains_unicode_character: false,
|
||||||
|
contains_unicode_escape: false,
|
||||||
|
contains_high_ascii: false,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -237,6 +237,10 @@ fn test_parse_var_call() {
|
|||||||
WithSpan::no_span(Expr::StrLit(StrLit {
|
WithSpan::no_span(Expr::StrLit(StrLit {
|
||||||
content: "123",
|
content: "123",
|
||||||
prefix: None,
|
prefix: None,
|
||||||
|
contains_null: false,
|
||||||
|
contains_unicode_character: false,
|
||||||
|
contains_unicode_escape: false,
|
||||||
|
contains_high_ascii: false,
|
||||||
})),
|
})),
|
||||||
WithSpan::no_span(int_lit("3"))
|
WithSpan::no_span(int_lit("3"))
|
||||||
],
|
],
|
||||||
@ -283,6 +287,10 @@ fn test_parse_path_call() {
|
|||||||
WithSpan::no_span(Expr::StrLit(StrLit {
|
WithSpan::no_span(Expr::StrLit(StrLit {
|
||||||
content: "123",
|
content: "123",
|
||||||
prefix: None,
|
prefix: None,
|
||||||
|
contains_null: false,
|
||||||
|
contains_unicode_character: false,
|
||||||
|
contains_unicode_escape: false,
|
||||||
|
contains_high_ascii: false,
|
||||||
})),
|
})),
|
||||||
WithSpan::no_span(int_lit("3"))
|
WithSpan::no_span(int_lit("3"))
|
||||||
],
|
],
|
||||||
|
Binary file not shown.
1
testing/templates/macro-call-raw-string-many-hashes.html
Normal file
1
testing/templates/macro-call-raw-string-many-hashes.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
{{ z!(hello################################################################################################################################################################################################################################################################world) }}
|
@ -42,7 +42,7 @@ struct ThreeTimesOk2 {
|
|||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(
|
#[template(
|
||||||
ext = "",
|
ext = "",
|
||||||
source = "\u{c}{{vu7218/63e3666663-666/3330e633/63e3666663666/3333<c\"}\u{1}2}\0\"<c7}}2\"\"\"\"\0\0\0\0"
|
source = "\u{c}{{vu7218/63e3666663-666/3330e633/63e3666663666/3333<c\"}\u{1}2}\"<c7}}2\"\"\"\"\0\0\0\0"
|
||||||
)]
|
)]
|
||||||
struct Regression {}
|
struct Regression {}
|
||||||
|
|
||||||
|
@ -32,8 +32,8 @@ error: comparison operators cannot be chained; consider using explicit parenthes
|
|||||||
|
|
||||||
error: comparison operators cannot be chained; consider using explicit parentheses, e.g. `(_ < _) < _`
|
error: comparison operators cannot be chained; consider using explicit parentheses, e.g. `(_ < _) < _`
|
||||||
--> <source attribute>:1:52
|
--> <source attribute>:1:52
|
||||||
"<c\"}\u{1}2}\0\"<c7}}2\"\"\"\"\0\0\0\0"
|
"<c\"}\u{1}2}\"<c7}}2\"\"\"\"\0\0\0\0"
|
||||||
--> tests/ui/comparator-chaining.rs:45:14
|
--> tests/ui/comparator-chaining.rs:45:14
|
||||||
|
|
|
|
||||||
45 | source = "\u{c}{{vu7218/63e3666663-666/3330e633/63e3666663666/3333<c\"}\u{1}2}\0\"<c7}}2\"\"\"\"\0\0\0\0"
|
45 | source = "\u{c}{{vu7218/63e3666663-666/3330e633/63e3666663666/3333<c\"}\u{1}2}\"<c7}}2\"\"\"\"\0\0\0\0"
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
105
testing/tests/ui/illegal-string-literals.rs
Normal file
105
testing/tests/ui/illegal-string-literals.rs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
use askama::Template;
|
||||||
|
|
||||||
|
// Strings must be closed
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ "hello world }}"#)]
|
||||||
|
struct Unclosed1;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ "hello world\" }}"#)]
|
||||||
|
struct Unclosed2;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ b"hello world }}"#)]
|
||||||
|
struct Unclosed3;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ b"hello world\" }}"#)]
|
||||||
|
struct Unclosed4;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ c"hello world }}"#)]
|
||||||
|
struct Unclosed5;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ c"hello world\" }}"#)]
|
||||||
|
struct Unclosed6;
|
||||||
|
|
||||||
|
// Unprefix string literals must not contain hex escapes sequences higher than `\x7f`
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ "hello \x80 world" }}"#)]
|
||||||
|
struct HighAscii1;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ "hello \xff world" }}"#)]
|
||||||
|
struct HighAscii2;
|
||||||
|
|
||||||
|
// Cstring literals must not contain null characters
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ "hello \0 world" }}"#)]
|
||||||
|
struct NulEscapeSequence1;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ "hello \x00 world" }}"#)]
|
||||||
|
struct NulEscapeSequence2;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ "hello \u{0} world" }}"#)]
|
||||||
|
struct NulEscapeSequence3;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = "{{ \"hello \0 world\" }}")]
|
||||||
|
struct NulCharacter;
|
||||||
|
|
||||||
|
// Binary slice literals must not contain non-ASCII characters
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ "hello 😉 world" }}"#)]
|
||||||
|
struct UnicodeCharacter;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ "hello \u{128521} world" }}"#)]
|
||||||
|
struct UnicodeEscapeSequence;
|
||||||
|
|
||||||
|
// Surrogate characters (even if paired) are not allowed at all
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ "hello \u{d83d}\u{de09} world" }}"#)]
|
||||||
|
struct SurrogatePaired1;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ "hello \u{d83d} world" }}"#)]
|
||||||
|
struct SurrogateLow1;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ "hello \u{de09} world" }}"#)]
|
||||||
|
struct SurrogateHigh1;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ b"hello \u{d83d}\u{de09} world" }}"#)]
|
||||||
|
struct SurrogatePaired2;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ b"hello \u{d83d} world" }}"#)]
|
||||||
|
struct SurrogateLow2;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ b"hello \u{de09} world" }}"#)]
|
||||||
|
struct SurrogateHigh2;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ c"hello \u{d83d}\u{de09} world" }}"#)]
|
||||||
|
struct SurrogatePaired3;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ c"hello \u{d83d} world" }}"#)]
|
||||||
|
struct SurrogateLow3;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(ext = "txt", source = r#"{{ c"hello \u{de09} world" }}"#)]
|
||||||
|
struct SurrogateHigh3;
|
||||||
|
|
||||||
|
fn main() {}
|
143
testing/tests/ui/illegal-string-literals.stderr
Normal file
143
testing/tests/ui/illegal-string-literals.stderr
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
error: unclosed or broken string
|
||||||
|
--> <source attribute>:1:3
|
||||||
|
"\"hello world }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:6:34
|
||||||
|
|
|
||||||
|
6 | #[template(ext = "txt", source = r#"{{ "hello world }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: unclosed or broken string
|
||||||
|
--> <source attribute>:1:3
|
||||||
|
"\"hello world\\\" }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:10:34
|
||||||
|
|
|
||||||
|
10 | #[template(ext = "txt", source = r#"{{ "hello world\" }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: unclosed or broken string
|
||||||
|
--> <source attribute>:1:3
|
||||||
|
"b\"hello world }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:14:34
|
||||||
|
|
|
||||||
|
14 | #[template(ext = "txt", source = r#"{{ b"hello world }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: unclosed or broken string
|
||||||
|
--> <source attribute>:1:3
|
||||||
|
"b\"hello world\\\" }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:18:34
|
||||||
|
|
|
||||||
|
18 | #[template(ext = "txt", source = r#"{{ b"hello world\" }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: unclosed or broken string
|
||||||
|
--> <source attribute>:1:3
|
||||||
|
"c\"hello world }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:22:34
|
||||||
|
|
|
||||||
|
22 | #[template(ext = "txt", source = r#"{{ c"hello world }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: unclosed or broken string
|
||||||
|
--> <source attribute>:1:3
|
||||||
|
"c\"hello world\\\" }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:26:34
|
||||||
|
|
|
||||||
|
26 | #[template(ext = "txt", source = r#"{{ c"hello world\" }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: out of range hex escape
|
||||||
|
--> <source attribute>:1:3
|
||||||
|
"\"hello \\x80 world\" }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:32:34
|
||||||
|
|
|
||||||
|
32 | #[template(ext = "txt", source = r#"{{ "hello \x80 world" }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: out of range hex escape
|
||||||
|
--> <source attribute>:1:3
|
||||||
|
"\"hello \\xff world\" }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:36:34
|
||||||
|
|
|
||||||
|
36 | #[template(ext = "txt", source = r#"{{ "hello \xff world" }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: unclosed or broken string
|
||||||
|
--> <source attribute>:1:3
|
||||||
|
"\"hello \\u{128521} world\" }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:64:34
|
||||||
|
|
|
||||||
|
64 | #[template(ext = "txt", source = r#"{{ "hello \u{128521} world" }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: unicode escape must not be a surrogate
|
||||||
|
--> <source attribute>:1:4
|
||||||
|
"hello \\u{d83d}\\u{de09} world\" }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:70:34
|
||||||
|
|
|
||||||
|
70 | #[template(ext = "txt", source = r#"{{ "hello \u{d83d}\u{de09} world" }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: unicode escape must not be a surrogate
|
||||||
|
--> <source attribute>:1:4
|
||||||
|
"hello \\u{d83d} world\" }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:74:34
|
||||||
|
|
|
||||||
|
74 | #[template(ext = "txt", source = r#"{{ "hello \u{d83d} world" }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: unicode escape must not be a surrogate
|
||||||
|
--> <source attribute>:1:4
|
||||||
|
"hello \\u{de09} world\" }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:78:34
|
||||||
|
|
|
||||||
|
78 | #[template(ext = "txt", source = r#"{{ "hello \u{de09} world" }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: unicode escape must not be a surrogate
|
||||||
|
--> <source attribute>:1:5
|
||||||
|
"hello \\u{d83d}\\u{de09} world\" }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:82:34
|
||||||
|
|
|
||||||
|
82 | #[template(ext = "txt", source = r#"{{ b"hello \u{d83d}\u{de09} world" }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: unicode escape must not be a surrogate
|
||||||
|
--> <source attribute>:1:5
|
||||||
|
"hello \\u{d83d} world\" }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:86:34
|
||||||
|
|
|
||||||
|
86 | #[template(ext = "txt", source = r#"{{ b"hello \u{d83d} world" }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: unicode escape must not be a surrogate
|
||||||
|
--> <source attribute>:1:5
|
||||||
|
"hello \\u{de09} world\" }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:90:34
|
||||||
|
|
|
||||||
|
90 | #[template(ext = "txt", source = r#"{{ b"hello \u{de09} world" }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: unicode escape must not be a surrogate
|
||||||
|
--> <source attribute>:1:5
|
||||||
|
"hello \\u{d83d}\\u{de09} world\" }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:94:34
|
||||||
|
|
|
||||||
|
94 | #[template(ext = "txt", source = r#"{{ c"hello \u{d83d}\u{de09} world" }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: unicode escape must not be a surrogate
|
||||||
|
--> <source attribute>:1:5
|
||||||
|
"hello \\u{d83d} world\" }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:98:34
|
||||||
|
|
|
||||||
|
98 | #[template(ext = "txt", source = r#"{{ c"hello \u{d83d} world" }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: unicode escape must not be a surrogate
|
||||||
|
--> <source attribute>:1:5
|
||||||
|
"hello \\u{de09} world\" }}"
|
||||||
|
--> tests/ui/illegal-string-literals.rs:102:34
|
||||||
|
|
|
||||||
|
102 | #[template(ext = "txt", source = r#"{{ c"hello \u{de09} world" }}"#)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
108
testing/tests/ui/raw-prefix.rs
Normal file
108
testing/tests/ui/raw-prefix.rs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// Regression test for <https://github.com/askama-rs/askama/issues/478>.
|
||||||
|
|
||||||
|
use askama::Template;
|
||||||
|
|
||||||
|
// Cstring literals must not contain NULs.
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "{{ z!(cr#\"\0\"#) }}",
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct MacroCallRawCstring1;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "{{ z!(cr##\"\0\"##) }}",
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct MacroCallRawCstring2;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "{{ z!(cr###\"\0\"###) }}",
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct MacroCallRawCstring3;
|
||||||
|
|
||||||
|
// Binary string literals must not contain NULs.
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "{{ z!(br#\"😶🌫️\"#) }}",
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct MacroCallRawBinaryString1;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "{{ z!(br##\"😶🌫️\"##) }}",
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct MacroCallRawBinaryString2;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "{{ z!(br###\"😶🌫️\"###) }}",
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct MacroCallRawBinaryString3;
|
||||||
|
|
||||||
|
// Only `r#` is allowed as prefix idenfiers
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "{{ z!(br#async) }}",
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct MacroCallIllegalPrefix1;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "{{ z!(cr#async) }}",
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct MacroCallIllegalPrefix2;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "{{ z!(r##async) }}",
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct MacroCallIllegalPrefix3;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "{{ z!(br##async) }}",
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct MacroCallIllegalPrefix4;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "{{ z!(cr##async) }}",
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct MacroCallIllegalPrefix5;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "{{ z!(hello#world) }}",
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct MacroCallReservedPrefix1;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(
|
||||||
|
source = "{{ z!(hello##world) }}",
|
||||||
|
ext = "txt"
|
||||||
|
)]
|
||||||
|
struct MacroCallReservedPrefix2;
|
||||||
|
|
||||||
|
// No more than 255 hashes
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "macro-call-raw-string-many-hashes.html")]
|
||||||
|
struct MacroCallManyHashes;
|
||||||
|
|
||||||
|
fn main() {}
|
111
testing/tests/ui/raw-prefix.stderr
Normal file
111
testing/tests/ui/raw-prefix.stderr
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
error: cstring literals must not contain NUL characters
|
||||||
|
--> <source attribute>:1:6
|
||||||
|
"cr#\"\0\"#) }}"
|
||||||
|
--> tests/ui/raw-prefix.rs:9:14
|
||||||
|
|
|
||||||
|
9 | source = "{{ z!(cr#\"\0\"#) }}",
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: cstring literals must not contain NUL characters
|
||||||
|
--> <source attribute>:1:6
|
||||||
|
"cr##\"\0\"##) }}"
|
||||||
|
--> tests/ui/raw-prefix.rs:16:14
|
||||||
|
|
|
||||||
|
16 | source = "{{ z!(cr##\"\0\"##) }}",
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: cstring literals must not contain NUL characters
|
||||||
|
--> <source attribute>:1:6
|
||||||
|
"cr###\"\0\"###) }}"
|
||||||
|
--> tests/ui/raw-prefix.rs:23:14
|
||||||
|
|
|
||||||
|
23 | source = "{{ z!(cr###\"\0\"###) }}",
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: binary string literals must not contain non-ASCII characters
|
||||||
|
--> <source attribute>:1:6
|
||||||
|
"br#\"😶\u{200d}🌫\u{fe0f}\"#) }}"
|
||||||
|
--> tests/ui/raw-prefix.rs:32:14
|
||||||
|
|
|
||||||
|
32 | source = "{{ z!(br#\"😶🌫️\"#) }}",
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: binary string literals must not contain non-ASCII characters
|
||||||
|
--> <source attribute>:1:6
|
||||||
|
"br##\"😶\u{200d}🌫\u{fe0f}\"##) }}"
|
||||||
|
--> tests/ui/raw-prefix.rs:39:14
|
||||||
|
|
|
||||||
|
39 | source = "{{ z!(br##\"😶🌫️\"##) }}",
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: binary string literals must not contain non-ASCII characters
|
||||||
|
--> <source attribute>:1:6
|
||||||
|
"br###\"😶\u{200d}🌫\u{fe0f}\"###) }}"
|
||||||
|
--> tests/ui/raw-prefix.rs:46:14
|
||||||
|
|
|
||||||
|
46 | source = "{{ z!(br###\"😶🌫️\"###) }}",
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: reserved prefix `br#`, only `r#` is allowed with raw identifiers
|
||||||
|
--> <source attribute>:1:6
|
||||||
|
"br#async) }}"
|
||||||
|
--> tests/ui/raw-prefix.rs:55:14
|
||||||
|
|
|
||||||
|
55 | source = "{{ z!(br#async) }}",
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: reserved prefix `cr#`, only `r#` is allowed with raw identifiers
|
||||||
|
--> <source attribute>:1:6
|
||||||
|
"cr#async) }}"
|
||||||
|
--> tests/ui/raw-prefix.rs:62:14
|
||||||
|
|
|
||||||
|
62 | source = "{{ z!(cr#async) }}",
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: only one `#` is allowed in raw identifier delimitation
|
||||||
|
--> <source attribute>:1:6
|
||||||
|
"r##async) }}"
|
||||||
|
--> tests/ui/raw-prefix.rs:69:14
|
||||||
|
|
|
||||||
|
69 | source = "{{ z!(r##async) }}",
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: reserved prefix `br#`, only `r#` is allowed with raw identifiers
|
||||||
|
--> <source attribute>:1:6
|
||||||
|
"br##async) }}"
|
||||||
|
--> tests/ui/raw-prefix.rs:76:14
|
||||||
|
|
|
||||||
|
76 | source = "{{ z!(br##async) }}",
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: reserved prefix `cr#`, only `r#` is allowed with raw identifiers
|
||||||
|
--> <source attribute>:1:6
|
||||||
|
"cr##async) }}"
|
||||||
|
--> tests/ui/raw-prefix.rs:83:14
|
||||||
|
|
|
||||||
|
83 | source = "{{ z!(cr##async) }}",
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: reserved prefix `hello#`
|
||||||
|
--> <source attribute>:1:6
|
||||||
|
"hello#world) }}"
|
||||||
|
--> tests/ui/raw-prefix.rs:90:14
|
||||||
|
|
|
||||||
|
90 | source = "{{ z!(hello#world) }}",
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: reserved prefix `hello#`
|
||||||
|
--> <source attribute>:1:6
|
||||||
|
"hello##world) }}"
|
||||||
|
--> tests/ui/raw-prefix.rs:97:14
|
||||||
|
|
|
||||||
|
97 | source = "{{ z!(hello##world) }}",
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: a maximum of 255 hashes `#` are allowed with raw strings
|
||||||
|
--> testing/templates/macro-call-raw-string-many-hashes.html:1:6
|
||||||
|
"hello###########################################################################"...
|
||||||
|
--> tests/ui/raw-prefix.rs:105:19
|
||||||
|
|
|
||||||
|
105 | #[template(path = "macro-call-raw-string-many-hashes.html")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
Loading…
x
Reference in New Issue
Block a user