78 lines
		
	
	
		
			2.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			78 lines
		
	
	
		
			2.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { GraphQLError } from '../../error/GraphQLError.mjs';
 | |
| import { Kind } from '../../language/kinds.mjs';
 | |
| const MAX_LISTS_DEPTH = 3;
 | |
| export function MaxIntrospectionDepthRule(context) {
 | |
|   /**
 | |
|    * Counts the depth of list fields in "__Type" recursively and
 | |
|    * returns `true` if the limit has been reached.
 | |
|    */
 | |
|   function checkDepth(node, visitedFragments = Object.create(null), depth = 0) {
 | |
|     if (node.kind === Kind.FRAGMENT_SPREAD) {
 | |
|       const fragmentName = node.name.value;
 | |
| 
 | |
|       if (visitedFragments[fragmentName] === true) {
 | |
|         // Fragment cycles are handled by `NoFragmentCyclesRule`.
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       const fragment = context.getFragment(fragmentName);
 | |
| 
 | |
|       if (!fragment) {
 | |
|         // Missing fragments checks are handled by `KnownFragmentNamesRule`.
 | |
|         return false;
 | |
|       } // Rather than following an immutable programming pattern which has
 | |
|       // significant memory and garbage collection overhead, we've opted to
 | |
|       // take a mutable approach for efficiency's sake. Importantly visiting a
 | |
|       // fragment twice is fine, so long as you don't do one visit inside the
 | |
|       // other.
 | |
| 
 | |
|       try {
 | |
|         visitedFragments[fragmentName] = true;
 | |
|         return checkDepth(fragment, visitedFragments, depth);
 | |
|       } finally {
 | |
|         visitedFragments[fragmentName] = undefined;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (
 | |
|       node.kind === Kind.FIELD && // check all introspection lists
 | |
|       (node.name.value === 'fields' ||
 | |
|         node.name.value === 'interfaces' ||
 | |
|         node.name.value === 'possibleTypes' ||
 | |
|         node.name.value === 'inputFields')
 | |
|     ) {
 | |
|       // eslint-disable-next-line no-param-reassign
 | |
|       depth++;
 | |
| 
 | |
|       if (depth >= MAX_LISTS_DEPTH) {
 | |
|         return true;
 | |
|       }
 | |
|     } // handles fields and inline fragments
 | |
| 
 | |
|     if ('selectionSet' in node && node.selectionSet) {
 | |
|       for (const child of node.selectionSet.selections) {
 | |
|         if (checkDepth(child, visitedFragments, depth)) {
 | |
|           return true;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return {
 | |
|     Field(node) {
 | |
|       if (node.name.value === '__schema' || node.name.value === '__type') {
 | |
|         if (checkDepth(node)) {
 | |
|           context.reportError(
 | |
|             new GraphQLError('Maximum introspection depth exceeded', {
 | |
|               nodes: [node],
 | |
|             }),
 | |
|           );
 | |
|           return false;
 | |
|         }
 | |
|       }
 | |
|     },
 | |
|   };
 | |
| }
 |