Limits the recursion depth of the parser.

This commit is contained in:
Sunli 2022-08-09 11:06:20 +08:00
parent 2451725188
commit d339e011fa
8 changed files with 2119 additions and 2261 deletions

View File

@ -13,7 +13,7 @@ version = "4.0.6"
[dependencies]
async-graphql-value = { path = "../value", version = "4.0.6" }
pest = "2.1.3"
pest = "2.2.1"
serde = { version = "1.0.125", features = ["derive"] }
serde_json = "1.0.64"

View File

@ -16,7 +16,7 @@ selection_set = { "{" ~ selection+ ~ "}" }
selection = { field | inline_fragment | fragment_spread }
field = { alias? ~ name ~ arguments? ~ directives? ~ selection_set? }
alias = { name ~ ":" }
fragment_spread = { "..." ~ name ~ directives? }
fragment_spread = { "..." ~ !type_condition ~ name ~ directives? }
inline_fragment = { "..." ~ type_condition? ~ directives? ~ selection_set }
fragment_definition = { "fragment" ~ name ~ type_condition ~ directives? ~ selection_set }

View File

@ -78,6 +78,8 @@ pub enum Error {
},
/// The document does not contain any operation.
MissingOperation,
/// Recursion limit exceeded.
RecursionLimitExceeded,
}
impl Error {
@ -106,6 +108,7 @@ impl Error {
ErrorPositions::new_2(*second, *first)
}
Self::MissingOperation => ErrorPositions::new_0(),
Self::RecursionLimitExceeded => ErrorPositions::new_0(),
}
}
}
@ -126,6 +129,7 @@ impl Display for Error {
write!(f, "fragment {} is defined twice", fragment)
}
Self::MissingOperation => f.write_str("document does not contain an operation"),
Self::RecursionLimitExceeded => f.write_str("recursion limit exceeded."),
}
}
}

View File

@ -2,6 +2,17 @@ use async_graphql_value::Name;
use super::*;
const MAX_RECURSION_DEPTH: usize = 64;
macro_rules! recursion_depth {
($remaining_depth:ident) => {{
if $remaining_depth == 0 {
return Err(Error::RecursionLimitExceeded);
}
$remaining_depth - 1
}};
}
/// Parse a GraphQL query document.
///
/// # Errors
@ -10,13 +21,10 @@ use super::*;
pub fn parse_query<T: AsRef<str>>(input: T) -> Result<ExecutableDocument> {
let mut pc = PositionCalculator::new(input.as_ref());
let items = parse_definition_items(
exactly_one(GraphQLParser::parse(
Rule::executable_document,
input.as_ref(),
)?),
&mut pc,
)?;
eprintln!("1");
let pairs = GraphQLParser::parse(Rule::executable_document, input.as_ref())?;
eprintln!("2");
let items = parse_definition_items(exactly_one(pairs), &mut pc)?;
let mut operations = None;
let mut fragments: HashMap<_, Positioned<FragmentDefinition>> = HashMap::new();
@ -149,7 +157,7 @@ fn parse_operation_definition_item(
ty: OperationType::Query,
variable_definitions: Vec::new(),
directives: Vec::new(),
selection_set: parse_selection_set(pair, pc)?,
selection_set: parse_selection_set(pair, pc, MAX_RECURSION_DEPTH)?,
},
},
_ => unreachable!(),
@ -172,7 +180,7 @@ fn parse_named_operation_definition(
parse_variable_definitions(pair, pc)
})?;
let directives = parse_opt_directives(&mut pairs, pc)?;
let selection_set = parse_selection_set(pairs.next().unwrap(), pc)?;
let selection_set = parse_selection_set(pairs.next().unwrap(), pc, MAX_RECURSION_DEPTH)?;
debug_assert_eq!(pairs.next(), None);
@ -231,6 +239,7 @@ fn parse_variable_definition(
fn parse_selection_set(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
remaining_depth: usize,
) -> Result<Positioned<SelectionSet>> {
debug_assert_eq!(pair.as_rule(), Rule::selection_set);
@ -240,14 +249,18 @@ fn parse_selection_set(
SelectionSet {
items: pair
.into_inner()
.map(|pair| parse_selection(pair, pc))
.map(|pair| parse_selection(pair, pc, remaining_depth))
.collect::<Result<_>>()?,
},
pos,
))
}
fn parse_selection(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Selection>> {
fn parse_selection(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
remaining_depth: usize,
) -> Result<Positioned<Selection>> {
debug_assert_eq!(pair.as_rule(), Rule::selection);
let pos = pc.step(&pair);
@ -255,16 +268,22 @@ fn parse_selection(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Posi
Ok(Positioned::new(
match pair.as_rule() {
Rule::field => Selection::Field(parse_field(pair, pc)?),
Rule::field => Selection::Field(parse_field(pair, pc, remaining_depth)?),
Rule::fragment_spread => Selection::FragmentSpread(parse_fragment_spread(pair, pc)?),
Rule::inline_fragment => Selection::InlineFragment(parse_inline_fragment(pair, pc)?),
Rule::inline_fragment => {
Selection::InlineFragment(parse_inline_fragment(pair, pc, remaining_depth)?)
}
_ => unreachable!(),
},
pos,
))
}
fn parse_field(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Field>> {
fn parse_field(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
remaining_depth: usize,
) -> Result<Positioned<Field>> {
debug_assert_eq!(pair.as_rule(), Rule::field);
let pos = pc.step(&pair);
@ -277,7 +296,7 @@ fn parse_field(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Position
})?;
let directives = parse_opt_directives(&mut pairs, pc)?;
let selection_set = parse_if_rule(&mut pairs, Rule::selection_set, |pair| {
parse_selection_set(pair, pc)
parse_selection_set(pair, pc, recursion_depth!(remaining_depth))
})?;
debug_assert_eq!(pairs.next(), None);
@ -325,6 +344,7 @@ fn parse_fragment_spread(
fn parse_inline_fragment(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
remaining_depth: usize,
) -> Result<Positioned<InlineFragment>> {
debug_assert_eq!(pair.as_rule(), Rule::inline_fragment);
@ -335,7 +355,8 @@ fn parse_inline_fragment(
parse_type_condition(pair, pc)
})?;
let directives = parse_opt_directives(&mut pairs, pc)?;
let selection_set = parse_selection_set(pairs.next().unwrap(), pc)?;
let selection_set =
parse_selection_set(pairs.next().unwrap(), pc, recursion_depth!(remaining_depth))?;
debug_assert_eq!(pairs.next(), None);
@ -366,7 +387,7 @@ fn parse_fragment_definition_item(
let name = parse_name(pairs.next().unwrap(), pc)?;
let type_condition = parse_type_condition(pairs.next().unwrap(), pc)?;
let directives = parse_opt_directives(&mut pairs, pc)?;
let selection_set = parse_selection_set(pairs.next().unwrap(), pc)?;
let selection_set = parse_selection_set(pairs.next().unwrap(), pc, MAX_RECURSION_DEPTH)?;
debug_assert_eq!(pairs.next(), None);

File diff suppressed because it is too large Load Diff

View File

@ -406,6 +406,7 @@ mod tests {
fn test_parser() {
for entry in fs::read_dir("tests/services").unwrap() {
let entry = entry.unwrap();
eprintln!("Parsing file {}", entry.path().display());
GraphQLParser::parse(
Rule::service_document,
&fs::read_to_string(entry.path()).unwrap(),

View File

@ -0,0 +1,12 @@
use async_graphql_parser::*;
#[test]
fn test_recursion_limit() {
let depth = 65;
let field = "a {".repeat(depth) + &"}".repeat(depth);
let query = format!("query {{ {} }}", field.replace("{}", "{b}"));
assert_eq!(
parse_query(query).unwrap_err(),
Error::RecursionLimitExceeded
);
}

View File

@ -114,9 +114,9 @@ impl<Query, Mutation, Subscription> SchemaBuilder<Query, Mutation, Subscription>
self
}
/// Set the maximum recursive depth a query can have. (default: 256)
/// Set the maximum recursive depth a query can have. (default: 32)
///
/// If the value is too large, stack overflow may occur, usually `256` is
/// If the value is too large, stack overflow may occur, usually `32` is
/// enough.
#[must_use]
pub fn limit_recursive_depth(mut self, depth: usize) -> Self {
@ -355,7 +355,7 @@ where
data: Default::default(),
complexity: None,
depth: None,
recursive_depth: 256,
recursive_depth: 32,
extensions: Default::default(),
custom_directives: Default::default(),
}