PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / trunk
Yoast SEO – Advanced SEO with real-time guidance and built-in AI vtrunk
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
← All changes | vendor_prefixed/guzzlehttp/psr7/src/Message.php +47 -10 28.0 → trunk View file →
@@ -15,9 +15,9 @@
15 15 */
16 16 public static function toString(\YoastSEO_Vendor\Psr\Http\Message\MessageInterface $message) : string
17 17 {
18 18 if ($message instanceof \YoastSEO_Vendor\Psr\Http\Message\RequestInterface) {
19 - $msg = \trim($message->getMethod() . ' ' . $message->getRequestTarget()) . ' HTTP/' . $message->getProtocolVersion();
19 + $msg = \trim($message->getMethod() . ' ' . $message->getRequestTarget(), " \n\r\t\x00\v") . ' HTTP/' . $message->getProtocolVersion();
20 20 if (!$message->hasHeader('host')) {
21 21 $msg .= "\r\nHost: " . $message->getUri()->getHost();
22 22 }
23 23 } elseif ($message instanceof \YoastSEO_Vendor\Psr\Http\Message\ResponseInterface) {
@@ -25,9 +25,9 @@
25 25 } else {
26 26 throw new \InvalidArgumentException('Unknown message type');
27 27 }
28 28 foreach ($message->getHeaders() as $name => $values) {
29 - if (\is_string($name) && \strtolower($name) === 'set-cookie') {
29 + if (\is_string($name) && \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::asciiToLower($name) === 'set-cookie') {
30 30 foreach ($values as $value) {
31 31 $msg .= "\r\n{$name}: " . $value;
32 32 }
33 33 } else {
@@ -142,9 +142,12 @@
142 142 throw new \InvalidArgumentException('Invalid message');
143 143 }
144 144 $message = \ltrim($message, "\r\n");
145 145 $messageParts = \preg_split("/\r?\n\r?\n/", $message, 2);
146 - if ($messageParts === \false || \count($messageParts) !== 2) {
146 + if ($messageParts === \false) {
147 + throw new \RuntimeException('Unable to split HTTP message: ' . \preg_last_error_msg());
148 + }
149 + if (\count($messageParts) !== 2) {
147 150 throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
148 151 }
149 152 [$rawHeaders, $body] = $messageParts;
150 153 $rawHeaders .= "\r\n";
@@ -149,22 +152,39 @@
149 152 [$rawHeaders, $body] = $messageParts;
150 153 $rawHeaders .= "\r\n";
151 154 // Put back the delimiter we split previously
152 155 $headerParts = \preg_split("/\r?\n/", $rawHeaders, 2);
153 - if ($headerParts === \false || \count($headerParts) !== 2) {
156 + if ($headerParts === \false) {
157 + throw new \RuntimeException('Unable to split HTTP message headers: ' . \preg_last_error_msg());
158 + }
159 + if (\count($headerParts) !== 2) {
154 160 throw new \InvalidArgumentException('Invalid message: Missing status line');
155 161 }
156 162 [$startLine, $rawHeaders] = $headerParts;
157 - if (\preg_match("/(?:^HTTP\\/|^[A-Z]+ \\S+ HTTP\\/)(\\d+(?:\\.\\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') {
163 + $versionMatch = \preg_match("/(?:^HTTP\\/|^[A-Z]+ \\S+ HTTP\\/)(\\d+(?:\\.\\d+)?)/i", $startLine, $matches);
164 + if ($versionMatch === \false) {
165 + throw new \RuntimeException('Unable to parse HTTP start line: ' . \preg_last_error_msg());
166 + }
167 + if ($versionMatch === 1 && $matches[1] === '1.0') {
158 168 // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
159 169 $rawHeaders = \preg_replace(\YoastSEO_Vendor\GuzzleHttp\Psr7\Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders);
170 + if ($rawHeaders === null) {
171 + throw new \RuntimeException('Unable to unfold HTTP headers: ' . \preg_last_error_msg());
172 + }
160 173 }
161 174 /** @var array[] $headerLines */
162 175 $count = \preg_match_all(\YoastSEO_Vendor\GuzzleHttp\Psr7\Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, \PREG_SET_ORDER);
176 + if ($count === \false) {
177 + throw new \RuntimeException('Unable to parse HTTP headers: ' . \preg_last_error_msg());
178 + }
163 179 // If these aren't the same, then one line didn't match and there's an invalid header.
164 180 if ($count !== \substr_count($rawHeaders, "\n")) {
165 181 // Folding is deprecated, see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4
166 - if (\preg_match(\YoastSEO_Vendor\GuzzleHttp\Psr7\Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
182 + $hasFoldedHeader = \preg_match(\YoastSEO_Vendor\GuzzleHttp\Psr7\Rfc7230::HEADER_FOLD_REGEX, $rawHeaders);
183 + if ($hasFoldedHeader === \false) {
184 + throw new \RuntimeException('Unable to inspect HTTP header folding: ' . \preg_last_error_msg());
185 + }
186 + if ($hasFoldedHeader === 1) {
167 187 throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
168 188 }
169 189 throw new \InvalidArgumentException('Invalid header syntax');
170 190 }
@@ -183,14 +203,23 @@
183 203 public static function parseRequestUri(string $path, array $headers) : string
184 204 {
185 205 $host = self::getHostFromHeaders($headers);
186 206 // If no host is found, then a full URI cannot be constructed.
207 + // Collapse leading slashes so an origin-form target cannot be
208 + // parsed as a network-path reference with its own authority.
187 209 if ($host === null) {
188 - return $path;
210 + return self::normalizePathForOriginForm($path);
189 211 }
190 212 $scheme = \substr($host, -4) === ':443' ? 'https' : 'http';
191 213 return $scheme . '://' . $host . '/' . \ltrim($path, '/');
192 214 }
215 + private static function normalizePathForOriginForm(string $path) : string
216 + {
217 + if (0 === \strpos($path, '//')) {
218 + return '/' . \ltrim($path, '/');
219 + }
220 + return $path;
221 + }
193 222 /**
194 223 * @param array $headers Array of headers (each value an array).
195 224 */
196 225 private static function getHostFromHeaders(array $headers) : ?string
@@ -197,9 +226,9 @@
197 226 {
198 227 $hostKey = \array_filter(\array_keys($headers), function ($k) {
199 228 // Numeric array keys are converted to int by PHP.
200 229 $k = (string) $k;
201 - return \strtolower($k) === 'host';
230 + return \YoastSEO_Vendor\GuzzleHttp\Psr7\Utils::asciiToLower($k) === 'host';
202 231 });
203 232 if (!$hostKey) {
204 233 return null;
205 234 }
@@ -220,9 +249,13 @@
220 249 if (\strpbrk($data['start-line'], "\r\n") !== \false) {
221 250 throw new \InvalidArgumentException('Invalid request string');
222 251 }
223 252 $matches = [];
224 - if (!\preg_match('/^[\\S]+\\s+([a-zA-Z]+:\\/\\/|\\/).*/', $data['start-line'], $matches)) {
253 + $requestStartLineMatch = \preg_match('/^[\\S]+\\s+([a-zA-Z]+:\\/\\/|\\/).*/', $data['start-line'], $matches);
254 + if ($requestStartLineMatch === \false) {
255 + throw new \RuntimeException('Unable to parse request start line: ' . \preg_last_error_msg());
256 + }
257 + if ($requestStartLineMatch === 0) {
225 258 throw new \InvalidArgumentException('Invalid request string');
226 259 }
227 260 $parts = \explode(' ', $data['start-line'], 3);
228 261 $version = isset($parts[2]) ? \explode('/', $parts[2])[1] : '1.1';
@@ -242,9 +275,13 @@
242 275 }
243 276 // According to https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2
244 277 // the space between status-code and reason-phrase is required. But
245 278 // browsers accept responses without space and reason as well.
246 - if (!\preg_match('/^HTTP\\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
279 + $responseStartLineMatch = \preg_match('/^HTTP\\/.* [0-9]{3}( .*|$)/D', $data['start-line']);
280 + if ($responseStartLineMatch === \false) {
281 + throw new \RuntimeException('Unable to parse response start line: ' . \preg_last_error_msg());
282 + }
283 + if ($responseStartLineMatch === 0) {
247 284 throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']);
248 285 }
249 286 $parts = \explode(' ', $data['start-line'], 3);
250 287 return new \YoastSEO_Vendor\GuzzleHttp\Psr7\Response((int) $parts[1], $data['headers'], $data['body'], \explode('/', $parts[0])[1], $parts[2] ?? null);