<?php
if ( ! class_exists( 'PHP_CodeSniffer\Files\File' ) ) {
	return;
}

class Aegisify_Sniffs_Security_UnsafeBrowserSinkSniff implements PHP_CodeSniffer\Sniffs\Sniff {

	public function register() {
		return array( T_STRING );
	}

	public function process( PHP_CodeSniffer\Files\File $phpcsFile, $stackPtr ) {
		$ext = strtolower( pathinfo( $phpcsFile->getFilename(), PATHINFO_EXTENSION ) );
		if ( ! in_array( $ext, array( 'js', 'jsx', 'mjs', 'cjs' ), true ) ) {
			return;
		}
		$tokens  = $phpcsFile->getTokens();
		$content = strtolower( $tokens[ $stackPtr ]['content'] );

		if ( 'innerhtml' === $content ) {
			$phpcsFile->addError( 'Potential unsafe DOM sink via innerHTML assignment.', $stackPtr, 'UnsafeBrowserSink' );
			return;
		}

		if ( 'document' === $content ) {
			$window = $this->collectWindow( $tokens, $stackPtr, 8 );
			if ( false !== strpos( $window, 'write' ) ) {
				$phpcsFile->addError( 'Potential unsafe browser sink via document.write or equivalent HTML string rendering.', $stackPtr, 'UnsafeBrowserSink' );
				return;
			}
		}

		if ( in_array( $content, array( 'localstorage', 'sessionstorage' ), true ) ) {
			$window = $this->collectWindow( $tokens, $stackPtr, 12 );
			if ( false !== strpos( $window, 'setitem' ) && preg_match( '/token|bearer|auth|password|secret|cookie/', $window ) ) {
				$phpcsFile->addWarning( 'Sensitive token-like data appears to be persisted to browser storage.', $stackPtr, 'UnsafeBrowserSink' );
			}
		}
	}

	private function collectWindow( array $tokens, $stackPtr, $distance ) {
		$out = array();
		$max = min( count( $tokens ) - 1, $stackPtr + $distance );
		for ( $i = $stackPtr; $i <= $max; $i++ ) {
			$out[] = strtolower( $tokens[ $i ]['content'] );
		}
		return implode( ' ', $out );
	}
}
