80, 'https' => 443, ); /** * Origin адреса. * * @param string $url Адрес целиком или уже origin. * @return string|null «схема://домен[:порт]» или null, если адрес не http(s) или без домена. */ public static function of( string $url ): ?string { $parts = parse_url( trim( $url ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- чистая логика без WordPress, проверяется тестами. if ( ! is_array( $parts ) || empty( $parts['scheme'] ) || empty( $parts['host'] ) ) { return null; } $scheme = strtolower( $parts['scheme'] ); if ( ! isset( self::DEFAULT_PORTS[ $scheme ] ) ) { return null; } $origin = $scheme . '://' . strtolower( $parts['host'] ); if ( isset( $parts['port'] ) && self::DEFAULT_PORTS[ $scheme ] !== (int) $parts['port'] ) { $origin .= ':' . (int) $parts['port']; } return $origin; } /** * Является ли строка уже origin — без пути, параметров, «#» и учётных данных. * * Заголовок Origin от браузера всегда такой; проверка нужна, чтобы не принять за адрес * админки что-то постороннее. * * @param string $value Проверяемая строка. * @return bool */ public static function is_bare_origin( string $value ): bool { $parts = parse_url( trim( $value ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- чистая логика без WordPress, проверяется тестами. if ( ! is_array( $parts ) || null === self::of( $value ) ) { return false; } foreach ( array( 'path', 'query', 'fragment', 'user', 'pass' ) as $extra ) { if ( isset( $parts[ $extra ] ) && '' !== $parts[ $extra ] ) { return false; } } return true; } }