agent/php/ElasticApm/Impl/Util/UrlUtil.php (111 lines of code) (raw):
<?php
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
declare(strict_types=1);
namespace Elastic\Apm\Impl\Util;
/**
* Code in this file is part of implementation internals and thus it is not covered by the backward compatibility.
*
* @internal
*/
final class UrlUtil
{
use StaticClassTrait;
public static function splitHostPort(string $hostPort, ?string &$host, ?int &$port): bool
{
$lastColonPos = strrpos($hostPort, ':');
if ($lastColonPos === false) {
$doesIncludePort = false;
} else {
$lastClosingSquareBracketPos = strrpos($hostPort, ']');
if ($lastClosingSquareBracketPos === false) {
// There is no closing square bracket
$firstColonPos = strpos($hostPort, ':');
// If there is only one colon it means the host is NOT IPv6 and there is a port after the colon
// otherwise there is more than colon it means the host is IPv6 but there is no closing square bracket
// which means there is no port
$doesIncludePort = ($firstColonPos === $lastColonPos);
} else {
// $lastClosingSquareBracketPos is the position of the last closing square bracket
// If the last closing square bracket is before the last colon
// it means the part after the last colon is a potential port
// otherwise the last colon is inside square brackets which means it's IPv6 and there's no port
$doesIncludePort = ($lastClosingSquareBracketPos < $lastColonPos);
}
}
if (!$doesIncludePort) {
$host = self::stripSquareBrackets($hostPort);
$port = null;
return true;
}
/** @var int $lastColonPos */
$host = ($lastColonPos === 0) ? null : self::stripSquareBrackets(substr($hostPort, 0, $lastColonPos));
if ($lastColonPos === (strlen($hostPort) - 1)) {
$port = null;
} else {
$portPart = trim(substr($hostPort, $lastColonPos + 1));
$port = is_numeric($portPart) ? intval($portPart) : null;
}
return true;
}
private static function stripSquareBrackets(string $host): string
{
$hostTrimmed = trim($host);
return
(TextUtil::isPrefixOf('[', $hostTrimmed) && TextUtil::isSuffixOf(']', $hostTrimmed))
? trim(substr($hostTrimmed, 1, -1))
: $hostTrimmed;
}
public static function splitPathQuery(string $pathQuery, ?string &$path, ?string &$query): bool
{
$parsedUrl = parse_url($pathQuery);
if (!is_array($parsedUrl)) {
return false;
}
$pathPart = ArrayUtil::getValueIfKeyExistsElse('path', $parsedUrl, null);
if ($pathPart !== null && !is_string($pathPart)) {
$pathPart = null;
}
$path = $pathPart;
$queryPart = ArrayUtil::getValueIfKeyExistsElse('query', $parsedUrl, null);
if ($queryPart !== null && !is_string($queryPart)) {
$queryPart = null;
}
$query = $queryPart;
return true;
}
public static function extractHostPart(string $url): ?string
{
$result = parse_url($url, PHP_URL_HOST);
if (!is_string($result)) {
return null;
}
return $result;
}
public static function isHttp(string $url): bool
{
/** @noinspection HttpUrlsUsage */
return TextUtil::isPrefixOf('http://', $url, /* isCaseSensitive */ false)
|| TextUtil::isPrefixOf('https://', $url, /* isCaseSensitive */ false);
}
public static function defaultPortForScheme(string $scheme): ?int
{
if (strcasecmp($scheme, 'http') === 0) {
return 80;
}
if (strcasecmp($scheme, 'https') === 0) {
return 443;
}
return null;
}
public static function buildRequestBaseUrl(UrlParts $urlParts): string
{
$result = ($urlParts->scheme ?? 'http') . '://';
$result .= $urlParts->host ?? 'localhost';
if ($urlParts->port !== null) {
$result .= ':' . $urlParts->port;
}
return $result;
}
public static function normalizeUrlPath(string $urlPath): string
{
return TextUtil::isPrefixOf('/', $urlPath) ? $urlPath : ('/' . $urlPath);
}
public static function buildRequestMethodArg(UrlParts $urlParts): string
{
$result = $urlParts->path === null
? '/'
: self::normalizeUrlPath($urlParts->path);
if ($urlParts->query !== null) {
$result .= '?' . $urlParts->query;
}
return $result;
}
public static function buildFullUrl(UrlParts $urlParts): string
{
return self::buildRequestBaseUrl($urlParts) . self::buildRequestMethodArg($urlParts);
}
}