fileoverview import { Allowlist } from '../../third_party/tsetse/allowlist' ; import { Checker } from '../../third_party/tsetse/checker' ; import { ErrorCode } from '../../third_party/tsetse/error_code' ; import { AbstractRule } from '../../third_party/tsetse/rule' ; import { AbsoluteMatcher } from '../../third_party/tsetse/util/absolute_matcher' ; import { shouldExamineNode } from '../../third_party/tsetse/util/ast_tools' ; import { isExpressionOfAllowedTrustedType } from '../../third_party/tsetse/util/is_trusted_type' ; import { PropertyMatcher } from '../../third_party/tsetse/util/property_matcher' ; import { TRUSTED_SCRIPT } from '../../third_party/tsetse/util/trusted_types_configuration' ; import * as ts from 'typescript' ; import { RuleConfiguration } from '../../rule_configuration' ; const BANNED_NAMES = [ 'GLOBAL|setInterval' , 'GLOBAL|setTimeout' , ] ; const BANNED_PROPERTIES = [ 'Window.prototype.setInterval' , 'Window.prototype.setTimeout' , ] ; function formatErrorMessage ( bannedEntity ) { let errMsg = `Do not use ${ bannedEntity }, as calling it with a string argument can cause code-injection security vulnerabilities.` ; return errMsg ; } /**
 * Checks if the APIs are called with functions (that
 * won't trigger an eval-like effect) or a TrustedScript value if Trusted Types
 * are enabled (and it's up to the developer to make sure it
 * the value can't be misused). These patterns are safe to use, so we want to
 * exclude them from the reported errors.
 *
 * Also checks whether the unsafe API is not reassigned (e.g. `const st =
 * setTimeout`) because we are not able to detect whether the usage is safe or
 * not.
 */ function isUsedWithNonStringArgument ( n , tc ) { const par = n . parent ; if ( ! ts . isCallExpression ( par ) || par . expression !== n ) return false ; if ( par . arguments . length === 0 ) return true ; const firstArgType = tc . getTypeAtLocation ( par . arguments [ 0 ] ) ; const isFirstArgNonString = ( firstArgType . flags & ( ts . TypeFlags . String | ts . TypeFlags . StringLike | ts . TypeFlags . StringLiteral ) ) === 0 ; if ( isExpressionOfAllowedTrustedType ( tc , par . arguments [ 0 ] , TRUSTED_SCRIPT ) ) { return true ; } return isFirstArgNonString ; } function isBannedStringLiteralAccess ( n , tc , propMatcher ) { const argExp = n . argumentExpression ; return propMatcher . typeMatches ( tc . getTypeAtLocation ( n . expression ) ) && ts . isStringLiteralLike ( argExp ) && argExp . text === propMatcher . bannedProperty ; } /**
 * A type selector that resolves to AbsoluteMatcher or PropertyMatcher based on
 * the type of AST node to be matched.
 */ type NodeMatcher < T extends ts . Node > = T extends ts . Identifier ? AbsoluteMatcher : T extends ts . PropertyAccessExpression ? PropertyMatcher : T extends ts . ElementAccessExpression ? { matches : ( n : ts . ElementAccessExpression , tc : ts . TypeChecker ) => boolean ; } : { matches : ( n : ts . Node , tc : ts . TypeChecker ) => never ; } ; function checkNode < T extends ts . Node > ( tc , n , matcher ) { if ( ! shouldExamineNode ( n ) ) return ; if ( ! matcher . matches ( n , tc ) ) return ; if ( isUsedWithNonStringArgument ( n , tc ) ) return ; return n ; } /**
 * A rule that checks the uses of Window#setTimeout and Window#setInterval
 * properties; it also checks the global setTimeout and setInterval functions.
 */ export class Rule extends AbstractRule { static readonly RULE_NAME = 'ban-window-stringfunctiondef' ; readonly ruleName = Rule . RULE_NAME ; readonly code = ErrorCode . CONFORMANCE_PATTERN ; private readonly nameMatchers : readonly AbsoluteMatcher [ ] ; private readonly propMatchers : readonly PropertyMatcher [ ] ; private readonly allowlist ? : Allowlist ; constructor ( configuration = { } ) { super ( ) ; this . nameMatchers = BANNED_NAMES . map ( name => new AbsoluteMatcher ( name ) ) ; this . propMatchers = BANNED_PROPERTIES . map ( PropertyMatcher . fromSpec ) ; if ( configuration ?. allowlistEntries ) { this . allowlist = new Allowlist ( configuration ?. allowlistEntries ) ; } } register ( checker ) { for ( const nameMatcher of this . nameMatchers ) { checker . onNamedIdentifier ( nameMatcher . bannedName , ( c , n ) => { if ( ts . isPropertyAccessExpression ( n . parent ) ) return ; if ( ts . isQualifiedName ( n . parent ) ) return ; const node = checkNode ( c . typeChecker , n , nameMatcher ) ; if ( node ) { checker . addFailureAtNode ( node , formatErrorMessage ( nameMatcher . bannedName ) , Rule . RULE_NAME , this . allowlist ) ; } } , this . code ) ; } for ( const propMatcher of this . propMatchers ) { checker . onNamedPropertyAccess ( propMatcher . bannedProperty , ( c , n ) => { const node = checkNode ( c . typeChecker , n , propMatcher ) ; if ( node ) { checker . addFailureAtNode ( node , formatErrorMessage ( `${ propMatcher . bannedType }#${ propMatcher . bannedProperty }` ) , Rule . RULE_NAME , this . allowlist ) ; } } , this . code ) ; checker . onStringLiteralElementAccess ( propMatcher . bannedProperty , ( c , n ) => { const node = checkNode ( c . typeChecker , n , { matches : ( ) => isBannedStringLiteralAccess ( n , c . typeChecker , propMatcher ) } ) ; if ( node ) { checker . addFailureAtNode ( node , formatErrorMessage ( `${ propMatcher . bannedType }#${ propMatcher . bannedProperty }` ) , Rule . RULE_NAME , this . allowlist ) ; } } , this . code ) ; } } }