PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.18
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.18
1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 All 163 releases
woocommerce-pos / vendor_prefixed / guzzlehttp / psr7 / src / MessageParser.php

MessageParser.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.18, at vendor_prefixed/guzzlehttp/psr7/src/MessageParser.php

250 lines 10.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare (strict_types=1);
4 namespace WCPOS\Vendor\GuzzleHttp\Psr7;
5
6 use WCPOS\Vendor\Psr\Http\Message\RequestInterface;
7 use WCPOS\Vendor\Psr\Http\Message\ResponseInterface;
8 /**
9 * @internal
10 */
11 final class MessageParser
12 {
13 private function __construct()
14 {
15 }
16 public static function parseMessage(string $message) : array
17 {
18 if (!$message) {
19 throw new \InvalidArgumentException('Invalid message');
20 }
21 $message = \ltrim($message, "\r\n");
22 $messageParts = \preg_split("/\r?\n\r?\n/", $message, 2);
23 if ($messageParts === \false) {
24 throw new \RuntimeException('Unable to split HTTP message: ' . \preg_last_error_msg());
25 }
26 if (\count($messageParts) !== 2) {
27 throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
28 }
29 [$rawHeaders, $body] = $messageParts;
30 $rawHeaders .= "\r\n";
31 // Put back the delimiter we split previously
32 $headerParts = \preg_split("/\r?\n/", $rawHeaders, 2);
33 if ($headerParts === \false) {
34 throw new \RuntimeException('Unable to split HTTP message headers: ' . \preg_last_error_msg());
35 }
36 if (\count($headerParts) !== 2) {
37 throw new \InvalidArgumentException('Invalid message: Missing status line');
38 }
39 [$startLine, $rawHeaders] = $headerParts;
40 $versionMatch = \preg_match('/(?:^HTTP\\/|^' . Rfc9110::TOKEN_PATTERN . ' ' . Rfc9112::REQUEST_TARGET_PATTERN . ' HTTP\\/)(' . Rfc9112::PROTOCOL_VERSION_PATTERN . ')/i', $startLine, $matches);
41 if ($versionMatch === \false) {
42 throw new \RuntimeException('Unable to parse HTTP start line: ' . \preg_last_error_msg());
43 }
44 if ($versionMatch === 1 && $matches[1] === '1.0') {
45 // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
46 $rawHeaders = \preg_replace(Rfc9112::HEADER_FOLD_REGEX, ' ', $rawHeaders);
47 if ($rawHeaders === null) {
48 throw new \RuntimeException('Unable to unfold HTTP headers: ' . \preg_last_error_msg());
49 }
50 }
51 $count = \preg_match_all(Rfc9112::HEADER_REGEX, $rawHeaders, $headerLines, \PREG_SET_ORDER);
52 /** @var list<array<int, string>> $headerLines */
53 if ($count === \false) {
54 throw new \RuntimeException('Unable to parse HTTP headers: ' . \preg_last_error_msg());
55 }
56 // If these aren't the same, then one line didn't match and there's an invalid header.
57 if ($count !== \substr_count($rawHeaders, "\n")) {
58 // Folding is deprecated, see https://datatracker.ietf.org/doc/html/rfc9112#section-5.2
59 $hasFoldedHeader = \preg_match(Rfc9112::HEADER_FOLD_REGEX, $rawHeaders);
60 if ($hasFoldedHeader === \false) {
61 throw new \RuntimeException('Unable to inspect HTTP header folding: ' . \preg_last_error_msg());
62 }
63 if ($hasFoldedHeader === 1) {
64 throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
65 }
66 throw new \InvalidArgumentException('Invalid header syntax');
67 }
68 $headers = [];
69 foreach ($headerLines as $headerLine) {
70 $headers[$headerLine[1]][] = $headerLine[2];
71 }
72 return ['start-line' => $startLine, 'headers' => $headers, 'body' => $body];
73 }
74 public static function parseRequestUri(string $path, array $headers) : string
75 {
76 $host = self::getHostFromHeaders($headers);
77 // If no host is found, then a full URI cannot be constructed.
78 // Collapse leading slashes so an origin-form target cannot be
79 // parsed as a network-path reference with its own authority.
80 if ($host === null) {
81 return self::normalizePathForOriginForm($path);
82 }
83 [$authorityHost, $port] = self::parseHostHeaderAuthority($host);
84 $scheme = $port === 443 ? 'https' : 'http';
85 return $scheme . '://' . self::composeAuthority($authorityHost, $port) . '/' . \ltrim($path, '/');
86 }
87 private static function normalizePathForOriginForm(string $path) : string
88 {
89 if (\str_starts_with($path, '//')) {
90 return '/' . \ltrim($path, '/');
91 }
92 return $path;
93 }
94 /**
95 * @return array{0: string, 1: int|null}
96 */
97 private static function parseHostHeaderAuthority(string $authority) : array
98 {
99 $parsed = Rfc9112::parseHostHeader($authority);
100 if ($parsed === null) {
101 throw new \InvalidArgumentException('Invalid request string');
102 }
103 return $parsed;
104 }
105 private static function composeAuthority(string $host, ?int $port) : string
106 {
107 return $host . ($port !== null ? ':' . $port : '');
108 }
109 /**
110 * @param array $headers Array of headers (each value an array).
111 */
112 private static function getHostFromHeaders(array $headers) : ?string
113 {
114 $host = self::getSingleHostHeader($headers);
115 if ($host === null) {
116 return null;
117 }
118 self::parseHostHeaderAuthority($host);
119 return $host;
120 }
121 /**
122 * @param array $headers Array of headers (each value an array).
123 */
124 private static function getSingleHostHeader(array $headers) : ?string
125 {
126 $host = null;
127 $found = \false;
128 foreach ($headers as $name => $values) {
129 if (Utils::asciiToLower((string) $name) !== 'host') {
130 continue;
131 }
132 if ($found || !\is_array($values) || \count($values) !== 1) {
133 throw new \InvalidArgumentException('Invalid request string');
134 }
135 $found = \true;
136 $host = \reset($values);
137 }
138 if (!$found) {
139 return null;
140 }
141 if (!\is_string($host)) {
142 throw new \InvalidArgumentException('Invalid request string');
143 }
144 return $host;
145 }
146 /**
147 * @param array $headers Array of headers (each value an array).
148 */
149 private static function parseRequestAuthorityUri(array $headers) : string
150 {
151 $host = self::getHostFromHeaders($headers);
152 if ($host === null) {
153 return '';
154 }
155 [$authorityHost, $port] = self::parseHostHeaderAuthority($host);
156 $scheme = $port === 443 ? 'https' : 'http';
157 return $scheme . '://' . self::composeAuthority($authorityHost, $port);
158 }
159 public static function parseRequest(string $message) : RequestInterface
160 {
161 $data = self::parseMessage($message);
162 $matches = [];
163 $matched = \preg_match('/^(?P<method>' . Rfc9110::TOKEN_PATTERN . ') (?P<target>' . Rfc9112::REQUEST_TARGET_PATTERN . ') HTTP\\/(?P<version>' . Rfc9112::PROTOCOL_VERSION_PATTERN . ')$/D', $data['start-line'], $matches);
164 if ($matched === \false) {
165 throw new \RuntimeException('Unable to parse request start line: ' . \preg_last_error_msg());
166 }
167 if ($matched === 0) {
168 throw new \InvalidArgumentException('Invalid request string');
169 }
170 self::getHostFromHeaders($data['headers']);
171 if (\str_starts_with($matches['target'], '/')) {
172 return new Request($matches['method'], self::parseRequestUri($matches['target'], $data['headers']), $data['headers'], $data['body'], $matches['version']);
173 }
174 $absoluteFormUri = self::parseAbsoluteFormRequestTarget($matches['target']);
175 if ($absoluteFormUri !== null) {
176 return (new Request($matches['method'], $absoluteFormUri, $data['headers'], $data['body'], $matches['version']))->withRequestTarget($matches['target']);
177 }
178 if (Rfc9112::isAsteriskFormRequestTarget($matches['method'], $matches['target'])) {
179 return (new Request($matches['method'], self::parseRequestAuthorityUri($data['headers']), $data['headers'], $data['body'], $matches['version']))->withRequestTarget($matches['target']);
180 }
181 $connectUri = self::parseConnectAuthorityFormRequestTarget($matches['method'], $matches['target']);
182 if ($connectUri !== null) {
183 return (new Request($matches['method'], $connectUri, $data['headers'], $data['body'], $matches['version']))->withRequestTarget($matches['target']);
184 }
185 throw new \InvalidArgumentException('Invalid request string');
186 }
187 private static function parseAbsoluteFormRequestTarget(string $target) : ?Uri
188 {
189 if (!Rfc9112::isAbsoluteFormRequestTarget($target)) {
190 return null;
191 }
192 $authority = \substr($target, \strpos($target, '//') + 2);
193 $authority = \substr($authority, 0, \strcspn($authority, '/?#'));
194 // RFC 9110 deprecates userinfo in message target URIs and directs
195 // recipients to treat its presence as an error, since it can obscure
196 // the authority. Host headers and CONNECT targets already reject it.
197 if (\str_contains($authority, '@')) {
198 return null;
199 }
200 try {
201 $uri = new Uri($target);
202 } catch (\InvalidArgumentException $e) {
203 return null;
204 }
205 if ($uri->getHost() === '') {
206 return null;
207 }
208 try {
209 self::parseHostHeaderAuthority(self::composeAuthority($uri->getHost(), $uri->getPort()));
210 } catch (\InvalidArgumentException $e) {
211 return null;
212 }
213 return $uri;
214 }
215 private static function parseConnectAuthorityFormRequestTarget(string $method, string $target) : ?Uri
216 {
217 if (!Rfc9112::isConnectAuthorityFormRequestTarget($method, $target)) {
218 return null;
219 }
220 $parsed = Rfc9112::parseHostHeader($target);
221 if ($parsed === null) {
222 return null;
223 }
224 [$host, $port] = $parsed;
225 if ($port === null) {
226 return null;
227 }
228 try {
229 return new Uri('//' . self::composeAuthority($host, $port));
230 } catch (\InvalidArgumentException $e) {
231 return null;
232 }
233 }
234 public static function parseResponse(string $message) : ResponseInterface
235 {
236 $data = self::parseMessage($message);
237 // According to https://datatracker.ietf.org/doc/html/rfc9112#section-4
238 // the space between status-code and reason-phrase is required. But
239 // browsers accept responses without space and reason as well.
240 $matched = \preg_match('/^HTTP\\/(?P<version>' . Rfc9112::PROTOCOL_VERSION_PATTERN . ') (?P<status>[1-5][0-9]{2})(?: (?P<reason>' . Rfc9110::FIELD_VALUE_PATTERN . '))?$/D', $data['start-line'], $matches);
241 if ($matched === \false) {
242 throw new \RuntimeException('Unable to parse response start line: ' . \preg_last_error_msg());
243 }
244 if ($matched === 0) {
245 throw new \InvalidArgumentException(\sprintf('Invalid response string: %s', DiagnosticValue::escape($data['start-line'])));
246 }
247 return new Response((int) $matches['status'], $data['headers'], $data['body'], $matches['version'], $matches['reason'] ?? null);
248 }
249 }
250