Limits the recursion depth of the parser.
This commit is contained in:
parent
2451725188
commit
d339e011fa
|
@ -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"
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue