md5 from the official list. * @return array Counts and lists: checked, modified, missing, unknown, truncated. */ public static function compare( $root, array $checksums ) { $root = rtrim( (string) $root, '/\\' ) . '/'; $result = array( 'checked' => 0, 'modified' => array(), 'missing' => array(), 'unknown' => array(), 'counts' => array( 'modified' => 0, 'missing' => 0, 'unknown' => 0, ), 'truncated' => false, ); $known = array(); foreach ( $checksums as $file => $md5 ) { $file = ltrim( str_replace( '\\', '/', (string) $file ), '/' ); if ( '' === $file || 0 === strpos( $file, 'wp-content/' ) || false !== strpos( $file, '..' ) ) { continue; } $known[ $file ] = true; $path = $root . $file; ++$result['checked']; if ( ! is_file( $path ) ) { self::add( $result, 'missing', $file ); } elseif ( ! hash_equals( strtolower( (string) $md5 ), (string) md5_file( $path ) ) ) { self::add( $result, 'modified', $file ); } } // Unknown files inside wp-admin/ and wp-includes/. $walked = 0; foreach ( self::core_dirs() as $dir ) { if ( ! is_dir( $root . $dir ) ) { continue; } $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $root . $dir, \FilesystemIterator::SKIP_DOTS ), \RecursiveIteratorIterator::LEAVES_ONLY ); foreach ( $iterator as $info ) { if ( ++$walked > self::MAX_WALK ) { $result['truncated'] = true; break 2; } if ( ! $info->isFile() ) { continue; } $relative = str_replace( '\\', '/', substr( $info->getPathname(), strlen( $root ) ) ); if ( ! isset( $known[ $relative ] ) ) { self::add( $result, 'unknown', $relative ); } } } // Unknown PHP files in the site root (wp-config.php is expected there). foreach ( (array) glob( $root . '*.php' ) as $path ) { $name = basename( (string) $path ); if ( 'wp-config.php' !== $name && ! isset( $known[ $name ] ) ) { self::add( $result, 'unknown', $name ); } } foreach ( array( 'modified', 'missing', 'unknown' ) as $list ) { sort( $result[ $list ] ); } return $result; } /** * Add a file to a list, respecting the cap. * * @param array $result Result (by reference). * @param string $list List name. * @param string $file Relative path. */ private static function add( array &$result, $list, $file ) { ++$result['counts'][ $list ]; if ( count( $result[ $list ] ) < self::MAX_LIST ) { $result[ $list ][] = $file; } else { $result['truncated'] = true; } } }