StreamSocketProvider::getRawCertificate()   B
last analyzed

Complexity

Conditions 4
Paths 10

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 28
rs 8.5806
cc 4
eloc 17
nc 10
nop 0
1
<?php
2
3
namespace Jalle19\CertificateParser\Provider;
4
5
use Jalle19\CertificateParser\Provider\Exception\ConnectionTimeoutException;
6
use Jalle19\CertificateParser\Provider\Exception\DomainMismatchException;
7
use Jalle19\CertificateParser\Provider\Exception\NameResolutionException;
8
use Jalle19\CertificateParser\Provider\Exception\CertificateNotFoundException;
9
use Jalle19\CertificateParser\Provider\Exception\ProviderException;
10
11
/**
12
 * Class StreamSocketProvider
13
 * @package Jalle19\CertificateParser\Provider
14
 */
15
class StreamSocketProvider implements ProviderInterface
16
{
17
18
    const DEFAULT_TIMEOUT_SECONDS = 15;
19
    const DEFAULT_PORT            = 443;
20
21
    /**
22
     * @var string
23
     */
24
    private $hostname;
25
26
    /**
27
     * @var int
28
     */
29
    private $port;
30
31
    /**
32
     * @var int
33
     */
34
    private $timeout;
35
36
    /**
37
     * @var StreamContext
38
     */
39
    private $streamContext;
40
41
    /**
42
     * @var array maps partial error messages to exception types
43
     */
44
    private static $errorExceptionMap = [
45
        // Check for name resolution errors
46
        'getaddrinfo failed'                => NameResolutionException::class,
47
        // Check for unknown SSL protocol (usually means no SSL is configured on the endpoint)
48
        'GET_SERVER_HELLO:unknown protocol' => CertificateNotFoundException::class,
49
        // Check for domain mismatches
50
        'did not match expected'            => DomainMismatchException::class,
51
        // Check for connection timeouts
52
        'timed out'                         => ConnectionTimeoutException::class,
53
    ];
54
55
56
    /**
57
     * StreamSocketProvider constructor.
58
     *
59
     * @param string             $hostname
60
     * @param int                $port          (optional)
61
     * @param int                $timeout       (optional)
62
     * @param StreamContext|null $streamContext (optional)
63
     */
64
    public function __construct(
65
        $hostname,
66
        $port = self::DEFAULT_PORT,
67
        $timeout = self::DEFAULT_TIMEOUT_SECONDS,
68
        $streamContext = null
69
    ) {
70
        $this->hostname      = $hostname;
71
        $this->port          = $port;
72
        $this->timeout       = $timeout;
73
        $this->streamContext = $streamContext ?: new StreamContext();
74
    }
75
76
77
    /**
78
     * @return StreamContext
79
     */
80
    public function getStreamContext()
81
    {
82
        return $this->streamContext;
83
    }
84
    
85
86
    /**
87
     * @param StreamContext $streamContext
88
     */
89
    public function setStreamContext($streamContext)
90
    {
91
        $this->streamContext = $streamContext;
92
    }
93
94
95
    /**
96
     * @inheritdoc
97
     */
98
    public function getRawCertificate()
99
    {
100
        try {
101
            $client = stream_socket_client(
102
                $this->getRequestUrl(),
103
                $errorNumber,
104
                $errorDescription,
105
                $this->timeout,
106
                STREAM_CLIENT_CONNECT,
107
                $this->streamContext->getResource()
108
            );
109
110
            $response = stream_context_get_params($client);
111
112
            return $response['options']['ssl']['peer_certificate'];
113
        } catch (\Throwable $e) {
114
            $errorMessage = $e->getMessage();
115
116
            // Throw mapped exceptions
117
            foreach (self::$errorExceptionMap as $needle => $exceptionClass) {
118
                if (strpos($errorMessage, $needle) !== false) {
119
                    throw new $exceptionClass($errorMessage);
120
                }
121
            }
122
123
            throw new ProviderException($errorMessage);
124
        }
125
    }
126
127
128
    /**
129
     * @return string
130
     */
131
    private function getRequestUrl()
132
    {
133
        return 'ssl://' . $this->hostname . ':' . $this->port;
134
    }
135
136
}
137