250 || ! preg_match( '~\A[a-zA-Z0-9_-][a-zA-Z0-9_./-]*\z~D', $path ) ) { return self::error( 'bad_path', 'Invalid plugin/theme relative path.' ); } foreach ( explode( '/', $path ) as $part ) { if ( '' === $part || '.' === $part || '..' === $part || 0 === strpos( $part, '.' ) ) { return self::error( 'bad_path', 'Hidden files and traversal are blocked.' ); } } if ( ! preg_match( '/\.(php|css|js|json|html|txt|twig)\z/iD', $path ) || preg_match( '~(^|/)(wp-config\.php|[^/]*(?:secret|credential|private.key|backup)[^/]*|composer\.(json|lock)|package-lock\.json)$~iD', $path ) ) { return self::error( 'restricted_file', 'Unsupported extension or potentially sensitive file.' ); } require_once ABSPATH . 'wp-admin/includes/plugin.php'; if ( 'plugin' === $kind ) { $found = false; foreach ( array_keys( get_plugins() ) as $file ) { if ( 0 === strpos( $file, $slug . '/' ) ) { $found = true; break; } } if ( ! $found ) { return self::error( 'unknown_plugin', 'Installed directory-based plugin required.' ); } $parent = WP_PLUGIN_DIR; } else { $themes = wp_get_themes(); if ( ! isset( $themes[ $slug ] ) ) { return self::error( 'unknown_theme', 'Theme not installed.' ); } $parent = $themes[ $slug ]->get_theme_root(); } $base = $parent . '/' . $slug; $root = realpath( $base ); $parent_real = realpath( $parent ); if ( ! $root || ! $parent_real || is_link( $base ) || 0 !== strpos( $root, $parent_real . DIRECTORY_SEPARATOR ) ) { return self::error( 'bad_root', 'Symlinked or external component root blocked.' ); } $cursor = $base; foreach ( explode( '/', $path ) as $part ) { $cursor .= '/' . $part; if ( is_link( $cursor ) ) { return self::error( 'symlink', 'Symlinked files and directories are blocked.' ); } } $target = realpath( $cursor ); if ( ! $target || ! is_file( $target ) || 0 !== strpos( $target, $root . DIRECTORY_SEPARATOR ) ) { return self::error( 'not_found', 'File unavailable or outside component root.' ); } clearstatcache( true, $target ); $stat = stat( $target ); if ( ! $stat || 1 !== $stat['nlink'] || $stat['size'] > self::LIMIT ) { return self::error( 'restricted_file', 'Hard links and files larger than 128 KiB are blocked.' ); } return $target; } /** * Read a file with its hash. * * @param array $a Input. * @return array|WP_Error */ public static function read( $a ) { if ( ! AdMindra_MCP::permission() ) { return self::error( 'forbidden', 'Administrator required.' ); } $file = self::resolve( $a ); if ( is_wp_error( $file ) ) { return $file; } $content = file_get_contents( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- local file, resolved above. if ( false === $content ) { return self::error( 'read_failed', 'Cannot read file.' ); } return array( 'content' => $content, 'sha256' => hash( 'sha256', $content ), 'bytes' => strlen( $content ), ); } }