Completed
Push — master ( 8cf1a9...f6bddc )
by Alejandro
15s queued 11s
created

UrlValidator::doValidateUrl()   A

Complexity

Conditions 5
Paths 10

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 12
nc 10
nop 2
dl 0
loc 22
rs 9.5555
c 0
b 0
f 0
ccs 12
cts 12
cp 1
crap 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Shlinkio\Shlink\Core\Util;
6
7
use Fig\Http\Message\RequestMethodInterface;
8
use Fig\Http\Message\StatusCodeInterface;
9
use GuzzleHttp\ClientInterface;
10
use GuzzleHttp\Exception\GuzzleException;
11
use GuzzleHttp\RequestOptions;
12
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
13
use Zend\Diactoros\Uri;
14
15
use function Functional\contains;
16
use function idn_to_ascii;
17
18
use const IDNA_DEFAULT;
19
use const INTL_IDNA_VARIANT_UTS46;
20
21
class UrlValidator implements UrlValidatorInterface, RequestMethodInterface, StatusCodeInterface
22
{
23
    private const MAX_REDIRECTS = 15;
24
25
    /** @var ClientInterface */
26
    private $httpClient;
27
28 18
    public function __construct(ClientInterface $httpClient)
29
    {
30 18
        $this->httpClient = $httpClient;
31
    }
32
33
    /**
34
     * @throws InvalidUrlException
35
     */
36 18
    public function validateUrl(string $url): void
37
    {
38 18
        $this->doValidateUrl($url);
39
    }
40
41
    /**
42
     * @throws InvalidUrlException
43
     */
44 18
    private function doValidateUrl(string $url, int $redirectNum = 1): void
45
    {
46
        // FIXME Guzzle is about to add support for this https://github.com/guzzle/guzzle/pull/2286
47
        //       Remove custom implementation and manual redirect handling when Guzzle's PR is merged
48 18
        $uri = new Uri($url);
49 18
        $originalHost = $uri->getHost();
50 18
        $normalizedHost = idn_to_ascii($originalHost, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46);
51 18
        if ($originalHost !== $normalizedHost) {
52 1
            $uri = $uri->withHost($normalizedHost);
53
        }
54
55
        try {
56 18
            $resp = $this->httpClient->request(self::METHOD_GET, (string) $uri, [
57
//                RequestOptions::ALLOW_REDIRECTS => ['max' => self::MAX_REDIRECTS],
58 18
                RequestOptions::ALLOW_REDIRECTS => false,
59
            ]);
60
61 17
            if ($redirectNum < self::MAX_REDIRECTS && $this->statusIsRedirect($resp->getStatusCode())) {
62 17
                $this->doValidateUrl($resp->getHeaderLine('Location'), $redirectNum + 1);
63
            }
64 15
        } catch (GuzzleException $e) {
65 15
            throw InvalidUrlException::fromUrl($url, $e);
66
        }
67
    }
68
69 17
    private function statusIsRedirect(int $statusCode): bool
70
    {
71 17
        return contains([self::STATUS_MOVED_PERMANENTLY, self::STATUS_FOUND], $statusCode);
72
    }
73
}
74