import { flatten , get , isPlainObject , merge , set } from 'lodash' ; import logger from '../logger' ; import { Meta } from '../types' ; import { Query , Aggregate , Filter } from '@directus/shared/types' ; import { Accountability } from '@directus/shared/types' ; import { parseFilter } from '@directus/shared/utils' ; export function sanitizeQuery ( rawQuery , accountability ? ) { const query = { } ; if ( rawQuery . limit !== undefined ) { const limit = sanitizeLimit ( rawQuery . limit ) ; if ( typeof limit === 'number' ) { query . limit = limit ; } } if ( rawQuery . fields ) { query . fields = sanitizeFields ( rawQuery . fields ) ; } if ( rawQuery . groupBy ) { query . group = sanitizeFields ( rawQuery . groupBy ) ; } if ( rawQuery . aggregate ) { query . aggregate = sanitizeAggregate ( rawQuery . aggregate ) ; } if ( rawQuery . sort ) { query . sort = sanitizeSort ( rawQuery . sort ) ; } if ( rawQuery . filter ) { query . filter = sanitizeFilter ( rawQuery . filter , accountability || null ) ; } if ( rawQuery . offset ) { query . offset = sanitizeOffset ( rawQuery . offset ) ; } if ( rawQuery . page ) { query . page = sanitizePage ( rawQuery . page ) ; } if ( rawQuery . meta ) { ( query as any ) . meta = sanitizeMeta ( rawQuery . meta ) ; } if ( rawQuery . search && typeof rawQuery . search === 'string' ) { query . search = rawQuery . search ; } if ( rawQuery . export ) { query . export = rawQuery . export as 'json' | 'csv' ; } if ( rawQuery . deep as Record < string , any > ) { if ( ! query . deep ) query . deep = { } ; query . deep = sanitizeDeep ( rawQuery . deep , accountability ) ; } if ( rawQuery . alias ) { query . alias = sanitizeAlias ( rawQuery . alias ) ; } return query ; } function sanitizeFields ( rawFields ) { if ( ! rawFields ) return ; let fields = [ ] ; if ( typeof rawFields === 'string' ) fields = rawFields . split ( ',' ) ; else if ( Array . isArray ( rawFields ) ) fields = rawFields as string [ ] ; fields = flatten ( fields . map ( ( field ) => ( field . includes ( ',' ) ? field . split ( ',' ) : field ) ) ) ; fields = fields . map ( ( field ) => field . trim ( ) ) ; return fields ; } function sanitizeSort ( rawSort ) { let fields = [ ] ; if ( typeof rawSort === 'string' ) fields = rawSort . split ( ',' ) ; else if ( Array . isArray ( rawSort ) ) fields = rawSort as string [ ] ; return fields ; } function sanitizeAggregate ( rawAggregate ) { let aggregate = rawAggregate ; if ( typeof rawAggregate === 'string' ) { try { aggregate = JSON . parse ( rawAggregate ) ; } catch { logger . warn ( 'Invalid value passed for filter query parameter.' ) ; } } for ( const [ operation , fields ] of Object . entries ( aggregate ) ) { if ( typeof fields === 'string' ) aggregate [ operation as keyof Aggregate ] = ( fields as string ) . split ( ',' ) ; else if ( Array . isArray ( fields ) ) aggregate [ operation as keyof Aggregate ] = fields as string [ ] ; } return aggregate as Aggregate ; } function sanitizeFilter ( rawFilter , accountability ) { let filters = rawFilter ; if ( typeof rawFilter === 'string' ) { try { filters = JSON . parse ( rawFilter ) ; } catch { logger . warn ( 'Invalid value passed for filter query parameter.' ) ; } } return parseFilter ( filters , accountability ) ; } function sanitizeLimit ( rawLimit ) { if ( rawLimit === undefined || rawLimit === null ) return null ; return Number ( rawLimit ) ; } function sanitizeOffset ( rawOffset ) { return Number ( rawOffset ) ; } function sanitizePage ( rawPage ) { return Number ( rawPage ) ; } function sanitizeMeta ( rawMeta ) { if ( rawMeta === '*' ) { return Object . values ( Meta ) ; } if ( rawMeta . includes ( ',' ) ) { return rawMeta . split ( ',' ) ; } if ( Array . isArray ( rawMeta ) ) { return rawMeta ; } return [ rawMeta ] ; } function sanitizeDeep ( deep , accountability ? ) { const result = { } ; if ( typeof deep === 'string' ) { try { deep = JSON . parse ( deep ) ; } catch { logger . warn ( 'Invalid value passed for deep query parameter.' ) ; } } parse ( deep ) ; return result ; function parse ( level , path = [ ] ) { const parsedLevel = { } ; for ( const [ key , value ] of Object . entries ( level ) ) { if ( ! key ) break ; if ( key . startsWith ( '_' ) ) { const parsedSubQuery = sanitizeQuery ( { [ key . substring ( 1 ) ] : value } , accountability ) ; const [ parsedKey , parsedValue ] = Object . entries ( parsedSubQuery ) [ 0 ] ; parsedLevel [ `_${ parsedKey }` ] = parsedValue ; } else if ( isPlainObject ( value ) ) { parse ( value , [ ... path , key ] ) ; } } if ( Object . keys ( parsedLevel ) . length > 0 ) { set ( result , path , merge ( { } , get ( result , path , { } ) , parsedLevel ) ) ; } } } function sanitizeAlias ( rawAlias ) { let alias = rawAlias ; if ( typeof rawAlias === 'string' ) { try { alias = JSON . parse ( rawAlias ) ; } catch ( err ) { logger . warn ( 'Invalid value passed for alias query parameter.' ) ; } } return alias ; }