*/ private array $seeds; /** @var list */ private array $allowed_roots; private string $hash_key; /** * @param list $allowed_roots Independent configured roots. * @param list> $seeds Canonical seed candidates. */ public function __construct( array $allowed_roots, array $seeds, string $hash_key ) { $this->allowed_roots = array(); foreach ( $allowed_roots as $root ) { $canonical_root = realpath( $root ); if ( false !== $canonical_root && is_dir( $canonical_root ) && ! is_link( $root ) ) { $this->allowed_roots[] = $this->normalize( $canonical_root ); } } $this->allowed_roots = array_values( array_unique( $this->allowed_roots ) ); if ( array() === $this->allowed_roots || '' === $hash_key ) { throw new InvalidArgumentException( 'invalid_code_scope' ); } $this->hash_key = $hash_key; $this->seeds = array(); foreach ( $seeds as $seed ) { if ( ! isset( $seed['scope'], $seed['path'], $seed['type'] ) || ! is_string( $seed['scope'] ) || ! is_string( $seed['path'] ) || ! is_string( $seed['type'] ) || ! $this->valid_scope( $seed['scope'] ) || ! in_array( $seed['type'], array( 'directory', 'file' ), true ) ) { continue; } $path = realpath( $seed['path'] ); if ( false === $path || is_link( $seed['path'] ) ) { continue; } $path = $this->normalize( $path ); if ( ! $this->within_allowed_root( $path ) || ( 'directory' === $seed['type'] && ! is_dir( $path ) ) || ( 'file' === $seed['type'] && ! is_file( $path ) ) ) { continue; } $this->seeds[] = array( 'scope' => $seed['scope'], 'path' => $path, 'type' => $seed['type'], ); } } /** * @return list */ public function seeds(): array { return $this->seeds; } /** * Revalidate every runtime path loaded from plugin storage. * * @return array{scope:string,path:string,type:string}|null */ public function evaluate( string $scope, string $path, string $type ): ?array { if ( ! $this->valid_scope( $scope ) || ! in_array( $type, array( 'directory', 'file' ), true ) || is_link( $path ) ) { return null; } $canonical = realpath( $path ); if ( false === $canonical ) { return null; } $canonical = $this->normalize( $canonical ); foreach ( $this->seeds as $seed ) { if ( $scope !== $seed['scope'] ) { continue; } $allowed = 'file' === $seed['type'] ? $canonical === $seed['path'] : $this->within( $canonical, $seed['path'] ); if ( $allowed && ( 'directory' === $type ? is_dir( $canonical ) : is_file( $canonical ) ) ) { return array( 'scope' => $scope, 'path' => $canonical, 'type' => $type, ); } } return null; } public function reference( string $scope, string $path ): string { return hash_hmac( 'sha256', $scope . "\0" . $this->normalize( $path ), $this->hash_key ); } public function masked_path( string $scope, string $path, string $type ): string { $extension = 'file' === $type ? strtolower( (string) pathinfo( $path, PATHINFO_EXTENSION ) ) : ''; $suffix = '' === $extension ? '' : '.' . substr( preg_replace( '/[^a-z0-9]/', '', $extension ) ?? '', 0, 8 ); return str_replace( '_', '-', $scope ) . '/' . $type . '-' . substr( $this->reference( $scope, $path ), 0, 12 ) . $suffix; } private function within( string $path, string $root ): bool { return $path === $root || 0 === strpos( $path . '/', rtrim( $root, '/' ) . '/' ); } private function within_allowed_root( string $path ): bool { foreach ( $this->allowed_roots as $root ) { if ( $this->within( $path, $root ) ) { return true; } } return false; } private function normalize( string $path ): string { return rtrim( str_replace( '\\', '/', $path ), '/' ); } private function valid_scope( string $scope ): bool { return in_array( $scope, array( 'active_theme', 'parent_theme', 'mu_plugins', 'drop_ins', 'code_snippets' ), true ) || 1 === preg_match( '/^vendor_(?:plugin|theme)_[a-f0-9]{12}$/', $scope ); } }