PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.3
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.3
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / vendor_prefixed / guzzlehttp / guzzle / src / Handler / HostValidator.php

HostValidator.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.3, at vendor_prefixed/guzzlehttp/guzzle/src/Handler/HostValidator.php

170 lines 8.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace YoastSEO_Vendor\GuzzleHttp\Handler;
4
5 use YoastSEO_Vendor\GuzzleHttp\Exception\RequestException;
6 use YoastSEO_Vendor\Psr\Http\Message\RequestInterface;
7 /**
8 * Rejects request hosts that a handler could resolve differently from the host
9 * the request names.
10 *
11 * @internal
12 */
13 final class HostValidator
14 {
15 private function __construct()
16 {
17 }
18 /**
19 * Asserts that a request names one unambiguous network host.
20 *
21 * The URI host and every Host header value must use printable ASCII without
22 * percent escapes. The URI host must also exclude authority delimiters and
23 * numeric-looking parts followed by trailing dots. Handlers reparse the URI
24 * but send the Host header as given, so ambiguous spellings can name
25 * different connection and request hosts.
26 *
27 * @throws RequestException
28 */
29 public static function assertRequestHost(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request) : void
30 {
31 $host = $request->getUri()->getHost();
32 self::assertUriHostValue($host, $request);
33 self::assertNoAuthorityDelimiter($host, $request);
34 self::assertNotADottedAddress($host, $request);
35 foreach ($request->getHeader('Host') as $value) {
36 self::assertHostHeaderValue((string) $value, $request);
37 }
38 }
39 /**
40 * @throws RequestException
41 */
42 private static function assertUriHostValue(string $value, \YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request) : void
43 {
44 if (!self::isPrintableAscii($value)) {
45 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\RequestException(\sprintf('The request URI host "%s" must contain only printable ASCII characters, because a handler can otherwise connect to a host that differs from the one the request names. An internationalized host name has an A-label form that this rule accepts.', self::escape($value)), $request);
46 }
47 if (\strpos($value, '%') !== \false) {
48 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\RequestException(\sprintf('The request URI host "%s" must not contain a percent escape, because a handler can decode it and then connect to a host that differs from the one the request names.', self::escape($value)), $request);
49 }
50 }
51 /**
52 * The Host header is sent rather than reparsed for the connection, so its
53 * diagnostics describe a request authority the caller did not write.
54 *
55 * @throws RequestException
56 */
57 private static function assertHostHeaderValue(string $value, \YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request) : void
58 {
59 if (!self::isPrintableAscii($value)) {
60 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\RequestException(\sprintf('The request Host header "%s" must contain only printable ASCII characters, because an intermediary or an origin server can otherwise read it as an authority that differs from the one the request names. An internationalized host name has an A-label form that this rule accepts.', self::escape($value)), $request);
61 }
62 if (\strpos($value, '%') !== \false) {
63 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\RequestException(\sprintf('The request Host header "%s" must not contain a percent escape, because an intermediary or an origin server can decode it and then read it as an authority that differs from the one the request names.', self::escape($value)), $request);
64 }
65 }
66 /**
67 * Matches the accepted shape positively so a PCRE failure rejects.
68 */
69 private static function isPrintableAscii(string $value) : bool
70 {
71 return \preg_match('/\\A[\\x21-\\x7E]*\\z/D', $value) === 1;
72 }
73 /**
74 * Rejects a delimiter the transport could treat as the end of the URI host.
75 *
76 * This mirrors GuzzleHttp\Psr7\Uri::assertValidHost() and only affects
77 * third-party UriInterface values. Host headers may carry a port and are
78 * sent verbatim.
79 *
80 * @throws RequestException
81 */
82 private static function assertNoAuthorityDelimiter(string $host, \YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request) : void
83 {
84 $message = 'The request URI host "%s" must not contain a URI authority delimiter, because a handler reparses the URI and can then connect to a host that differs from the one the request names.';
85 // Match the accepted shape positively so a PCRE engine failure rejects.
86 if (\preg_match('/\\A[^\\/?#@\\\\]*\\z/D', $host) !== 1) {
87 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\RequestException(\sprintf($message, self::escape($host)), $request);
88 }
89 if (\strpos($host, '[') !== \false || \strpos($host, ']') !== \false) {
90 if (\strpos($host, '[') !== 0 || \substr($host, -1) !== ']') {
91 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\RequestException(\sprintf($message, self::escape($host)), $request);
92 }
93 return;
94 }
95 if (\strpos($host, ':') !== \false) {
96 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\RequestException(\sprintf($message, self::escape($host)), $request);
97 }
98 }
99 /**
100 * Rejects one to four numeric-looking parts followed by trailing dots.
101 *
102 * libcurl 8.21.0 drops a trailing dot from inet_aton-style numeric hosts
103 * before connecting, while other validators treat the input as a name.
104 * Testing the shape also rejects some out-of-range values that transports
105 * keep as names; isNumericIpv4Host() explains that fail-closed tradeoff.
106 * Plain numeric shorthand stays accepted.
107 *
108 * @throws RequestException
109 */
110 private static function assertNotADottedAddress(string $host, \YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request) : void
111 {
112 if (\substr($host, -1) !== '.') {
113 return;
114 }
115 if (!self::isNumericIpv4Host(\rtrim($host, '.'))) {
116 return;
117 }
118 throw new \YoastSEO_Vendor\GuzzleHttp\Exception\RequestException(\sprintf('The request URI host "%s" must not be written as one to four decimal, octal or hexadecimal parts followed by one or more trailing dots, because a handler can read that spelling as an IPv4 address and connect to that address while the rest of the process reads a name.', self::escape($host)), $request);
119 }
120 /**
121 * Reports whether a value has the transport's inet_aton-style shape: one
122 * to four decimal, 0-prefixed octal, or 0x-prefixed hexadecimal parts.
123 *
124 * Range and 32-bit overflow checks are deliberately omitted. This may
125 * reject a trailing-dot spelling the transport reads as a name, but avoids
126 * missing one it resolves as an address. No PCRE is used.
127 */
128 public static function isNumericIpv4Host(string $host) : bool
129 {
130 if ($host === '') {
131 return \false;
132 }
133 $parts = \explode('.', $host);
134 if (\count($parts) > 4) {
135 return \false;
136 }
137 foreach ($parts as $part) {
138 if (!self::isNumericIpv4Part($part)) {
139 return \false;
140 }
141 }
142 return \true;
143 }
144 private static function isNumericIpv4Part(string $part) : bool
145 {
146 if ($part === '') {
147 return \false;
148 }
149 if ($part[0] === '0' && isset($part[1]) && ($part[1] === 'x' || $part[1] === 'X')) {
150 return \strlen($part) > 2 && \strspn($part, '0123456789abcdefABCDEF', 2) === \strlen($part) - 2;
151 }
152 $digits = $part[0] === '0' ? '01234567' : '0123456789';
153 return \strspn($part, $digits) === \strlen($part);
154 }
155 /**
156 * Escapes non-printable bytes as uppercase \xNN for safe diagnostics.
157 * Printable delimiters and dots stay visible. The result is not a
158 * reversible encoding.
159 */
160 private static function escape(string $value) : string
161 {
162 $escaped = '';
163 for ($offset = 0, $length = \strlen($value); $offset < $length; ++$offset) {
164 $byte = \ord($value[$offset]);
165 $escaped .= $byte >= 0x21 && $byte <= 0x7e ? $value[$offset] : \sprintf('\\x%02X', $byte);
166 }
167 return $escaped;
168 }
169 }
170