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

UrlValidator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
ccs 2
cts 2
cp 1
crap 1
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