PluginProbe
AutomatorWP – No-Code Workflow Automation, Integration & Webhooks Plugin, now with AI / 6.0.3
AutomatorWP – No-Code Workflow Automation, Integration & Webhooks Plugin, now with AI v6.0.3
6.0.3 6.0.2 6.0.1 6.0.0 5.8.6 5.8.5 5.8.4 5.8.3 5.8.2 5.8.1 5.8.0 5.7.9.2 5.7.9.1 5.7.8 5.7.9 5.7.6 5.7.7 5.7.5 5.7.4 5.7.3 5.7.2 5.7.1 trunk 5.6.0 5.6.1 All 34 releases
automatorwp / integrations / mailchimp / vendor / guzzlehttp / psr7 / src / Utils.php

Utils.php in AutomatorWP – No-Code Workflow Automation, Integration & Webhooks Plugin, now with AI 6.0.3, at integrations/mailchimp/vendor/guzzlehttp/psr7/src/Utils.php

478 lines 15.6 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
5 namespace GuzzleHttp\Psr7;
6
7 use Psr\Http\Message\RequestInterface;
8 use Psr\Http\Message\ServerRequestInterface;
9 use Psr\Http\Message\StreamInterface;
10 use Psr\Http\Message\UriInterface;
11
12 final class Utils
13 {
14 /**
15 * Remove the items given by the keys, case insensitively from the data.
16 *
17 * @param (string|int)[] $keys
18 */
19 public static function caselessRemove(array $keys, array $data): array
20 {
21 $result = [];
22
23 foreach ($keys as &$key) {
24 $key = strtolower((string) $key);
25 }
26
27 foreach ($data as $k => $v) {
28 if (!in_array(strtolower((string) $k), $keys)) {
29 $result[$k] = $v;
30 }
31 }
32
33 return $result;
34 }
35
36 /**
37 * Copy the contents of a stream into another stream until the given number
38 * of bytes have been read.
39 *
40 * @param StreamInterface $source Stream to read from
41 * @param StreamInterface $dest Stream to write to
42 * @param int $maxLen Maximum number of bytes to read. Pass -1
43 * to read the entire stream.
44 *
45 * @throws \RuntimeException on error.
46 */
47 public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void
48 {
49 $bufferSize = 8192;
50
51 if ($maxLen === -1) {
52 while (!$source->eof()) {
53 if (!$dest->write($source->read($bufferSize))) {
54 break;
55 }
56 }
57 } else {
58 $remaining = $maxLen;
59 while ($remaining > 0 && !$source->eof()) {
60 $buf = $source->read(min($bufferSize, $remaining));
61 $len = strlen($buf);
62 if (!$len) {
63 break;
64 }
65 $remaining -= $len;
66 $dest->write($buf);
67 }
68 }
69 }
70
71 /**
72 * Copy the contents of a stream into a string until the given number of
73 * bytes have been read.
74 *
75 * @param StreamInterface $stream Stream to read
76 * @param int $maxLen Maximum number of bytes to read. Pass -1
77 * to read the entire stream.
78 *
79 * @throws \RuntimeException on error.
80 */
81 public static function copyToString(StreamInterface $stream, int $maxLen = -1): string
82 {
83 $buffer = '';
84
85 if ($maxLen === -1) {
86 while (!$stream->eof()) {
87 $buf = $stream->read(1048576);
88 if ($buf === '') {
89 break;
90 }
91 $buffer .= $buf;
92 }
93
94 return $buffer;
95 }
96
97 $len = 0;
98 while (!$stream->eof() && $len < $maxLen) {
99 $buf = $stream->read($maxLen - $len);
100 if ($buf === '') {
101 break;
102 }
103 $buffer .= $buf;
104 $len = strlen($buffer);
105 }
106
107 return $buffer;
108 }
109
110 /**
111 * Calculate a hash of a stream.
112 *
113 * This method reads the entire stream to calculate a rolling hash, based
114 * on PHP's `hash_init` functions.
115 *
116 * @param StreamInterface $stream Stream to calculate the hash for
117 * @param string $algo Hash algorithm (e.g. md5, crc32, etc)
118 * @param bool $rawOutput Whether or not to use raw output
119 *
120 * @throws \RuntimeException on error.
121 */
122 public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string
123 {
124 $pos = $stream->tell();
125
126 if ($pos > 0) {
127 $stream->rewind();
128 }
129
130 $ctx = hash_init($algo);
131 while (!$stream->eof()) {
132 hash_update($ctx, $stream->read(1048576));
133 }
134
135 $out = hash_final($ctx, $rawOutput);
136 $stream->seek($pos);
137
138 return $out;
139 }
140
141 /**
142 * Clone and modify a request with the given changes.
143 *
144 * This method is useful for reducing the number of clones needed to mutate
145 * a message.
146 *
147 * The changes can be one of:
148 * - method: (string) Changes the HTTP method.
149 * - set_headers: (array) Sets the given headers.
150 * - remove_headers: (array) Remove the given headers.
151 * - body: (mixed) Sets the given body.
152 * - uri: (UriInterface) Set the URI.
153 * - query: (string) Set the query string value of the URI.
154 * - version: (string) Set the protocol version.
155 *
156 * @param RequestInterface $request Request to clone and modify.
157 * @param array $changes Changes to apply.
158 */
159 public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface
160 {
161 if (!$changes) {
162 return $request;
163 }
164
165 $headers = $request->getHeaders();
166
167 if (!isset($changes['uri'])) {
168 $uri = $request->getUri();
169 } else {
170 // Remove the host header if one is on the URI
171 if ($host = $changes['uri']->getHost()) {
172 $changes['set_headers']['Host'] = $host;
173
174 if ($port = $changes['uri']->getPort()) {
175 $standardPorts = ['http' => 80, 'https' => 443];
176 $scheme = $changes['uri']->getScheme();
177 if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
178 $changes['set_headers']['Host'] .= ':'.$port;
179 }
180 }
181 }
182 $uri = $changes['uri'];
183 }
184
185 if (!empty($changes['remove_headers'])) {
186 $headers = self::caselessRemove($changes['remove_headers'], $headers);
187 }
188
189 if (!empty($changes['set_headers'])) {
190 $headers = self::caselessRemove(array_keys($changes['set_headers']), $headers);
191 $headers = $changes['set_headers'] + $headers;
192 }
193
194 if (isset($changes['query'])) {
195 $uri = $uri->withQuery($changes['query']);
196 }
197
198 if ($request instanceof ServerRequestInterface) {
199 $new = (new ServerRequest(
200 $changes['method'] ?? $request->getMethod(),
201 $uri,
202 $headers,
203 $changes['body'] ?? $request->getBody(),
204 $changes['version'] ?? $request->getProtocolVersion(),
205 $request->getServerParams()
206 ))
207 ->withParsedBody($request->getParsedBody())
208 ->withQueryParams($request->getQueryParams())
209 ->withCookieParams($request->getCookieParams())
210 ->withUploadedFiles($request->getUploadedFiles());
211
212 foreach ($request->getAttributes() as $key => $value) {
213 $new = $new->withAttribute($key, $value);
214 }
215
216 return $new;
217 }
218
219 return new Request(
220 $changes['method'] ?? $request->getMethod(),
221 $uri,
222 $headers,
223 $changes['body'] ?? $request->getBody(),
224 $changes['version'] ?? $request->getProtocolVersion()
225 );
226 }
227
228 /**
229 * Read a line from the stream up to the maximum allowed buffer length.
230 *
231 * @param StreamInterface $stream Stream to read from
232 * @param int|null $maxLength Maximum buffer length
233 */
234 public static function readLine(StreamInterface $stream, ?int $maxLength = null): string
235 {
236 $buffer = '';
237 $size = 0;
238
239 while (!$stream->eof()) {
240 if ('' === ($byte = $stream->read(1))) {
241 return $buffer;
242 }
243 $buffer .= $byte;
244 // Break when a new line is found or the max length - 1 is reached
245 if ($byte === "\n" || ++$size === $maxLength - 1) {
246 break;
247 }
248 }
249
250 return $buffer;
251 }
252
253 /**
254 * Redact the password in the user info part of a URI.
255 */
256 public static function redactUserInfo(UriInterface $uri): UriInterface
257 {
258 $userInfo = $uri->getUserInfo();
259
260 if (false !== ($pos = \strpos($userInfo, ':'))) {
261 return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***');
262 }
263
264 return $uri;
265 }
266
267 /**
268 * Create a new stream based on the input type.
269 *
270 * Options is an associative array that can contain the following keys:
271 * - metadata: Array of custom metadata.
272 * - size: Size of the stream.
273 *
274 * This method accepts the following `$resource` types:
275 * - `Psr\Http\Message\StreamInterface`: Returns the value as-is.
276 * - `string`: Creates a stream object that uses the given string as the contents.
277 * - `resource`: Creates a stream object that wraps the given PHP stream resource.
278 * - `Iterator`: If the provided value implements `Iterator`, then a read-only
279 * stream object will be created that wraps the given iterable. Each time the
280 * stream is read from, data from the iterator will fill a buffer and will be
281 * continuously called until the buffer is equal to the requested read size.
282 * Subsequent read calls will first read from the buffer and then call `next`
283 * on the underlying iterator until it is exhausted.
284 * - `object` with `__toString()`: If the object has the `__toString()` method,
285 * the object will be cast to a string and then a stream will be returned that
286 * uses the string value.
287 * - `NULL`: When `null` is passed, an empty stream object is returned.
288 * - `callable` When a callable is passed, a read-only stream object will be
289 * created that invokes the given callable. The callable is invoked with the
290 * number of suggested bytes to read. The callable can return any number of
291 * bytes, but MUST return `false` when there is no more data to return. The
292 * stream object that wraps the callable will invoke the callable until the
293 * number of requested bytes are available. Any additional bytes will be
294 * buffered and used in subsequent reads.
295 *
296 * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data
297 * @param array{size?: int, metadata?: array} $options Additional options
298 *
299 * @throws \InvalidArgumentException if the $resource arg is not valid.
300 */
301 public static function streamFor($resource = '', array $options = []): StreamInterface
302 {
303 if (is_scalar($resource)) {
304 $stream = self::tryFopen('php://temp', 'r+');
305 if ($resource !== '') {
306 fwrite($stream, (string) $resource);
307 fseek($stream, 0);
308 }
309
310 return new Stream($stream, $options);
311 }
312
313 switch (gettype($resource)) {
314 case 'resource':
315 /*
316 * The 'php://input' is a special stream with quirks and inconsistencies.
317 * We avoid using that stream by reading it into php://temp
318 */
319
320 /** @var resource $resource */
321 if ((\stream_get_meta_data($resource)['uri'] ?? '') === 'php://input') {
322 $stream = self::tryFopen('php://temp', 'w+');
323 stream_copy_to_stream($resource, $stream);
324 fseek($stream, 0);
325 $resource = $stream;
326 }
327
328 return new Stream($resource, $options);
329 case 'object':
330 /** @var object $resource */
331 if ($resource instanceof StreamInterface) {
332 return $resource;
333 } elseif ($resource instanceof \Iterator) {
334 return new PumpStream(function () use ($resource) {
335 if (!$resource->valid()) {
336 return false;
337 }
338 $result = $resource->current();
339 $resource->next();
340
341 return $result;
342 }, $options);
343 } elseif (method_exists($resource, '__toString')) {
344 return self::streamFor((string) $resource, $options);
345 }
346 break;
347 case 'NULL':
348 return new Stream(self::tryFopen('php://temp', 'r+'), $options);
349 }
350
351 if (is_callable($resource)) {
352 return new PumpStream($resource, $options);
353 }
354
355 throw new \InvalidArgumentException('Invalid resource type: '.gettype($resource));
356 }
357
358 /**
359 * Safely opens a PHP stream resource using a filename.
360 *
361 * When fopen fails, PHP normally raises a warning. This function adds an
362 * error handler that checks for errors and throws an exception instead.
363 *
364 * @param string $filename File to open
365 * @param string $mode Mode used to open the file
366 *
367 * @return resource
368 *
369 * @throws \RuntimeException if the file cannot be opened
370 */
371 public static function tryFopen(string $filename, string $mode)
372 {
373 $ex = null;
374 set_error_handler(static function (int $errno, string $errstr) use ($filename, $mode, &$ex): bool {
375 $ex = new \RuntimeException(sprintf(
376 'Unable to open "%s" using mode "%s": %s',
377 $filename,
378 $mode,
379 $errstr
380 ));
381
382 return true;
383 });
384
385 try {
386 /** @var resource $handle */
387 $handle = fopen($filename, $mode);
388 } catch (\Throwable $e) {
389 $ex = new \RuntimeException(sprintf(
390 'Unable to open "%s" using mode "%s": %s',
391 $filename,
392 $mode,
393 $e->getMessage()
394 ), 0, $e);
395 }
396
397 restore_error_handler();
398
399 if ($ex) {
400 /** @var \RuntimeException $ex */
401 throw $ex;
402 }
403
404 return $handle;
405 }
406
407 /**
408 * Safely gets the contents of a given stream.
409 *
410 * When stream_get_contents fails, PHP normally raises a warning. This
411 * function adds an error handler that checks for errors and throws an
412 * exception instead.
413 *
414 * @param resource $stream
415 *
416 * @throws \RuntimeException if the stream cannot be read
417 */
418 public static function tryGetContents($stream): string
419 {
420 $ex = null;
421 set_error_handler(static function (int $errno, string $errstr) use (&$ex): bool {
422 $ex = new \RuntimeException(sprintf(
423 'Unable to read stream contents: %s',
424 $errstr
425 ));
426
427 return true;
428 });
429
430 try {
431 /** @var string|false $contents */
432 $contents = stream_get_contents($stream);
433
434 if ($contents === false) {
435 $ex = new \RuntimeException('Unable to read stream contents');
436 }
437 } catch (\Throwable $e) {
438 $ex = new \RuntimeException(sprintf(
439 'Unable to read stream contents: %s',
440 $e->getMessage()
441 ), 0, $e);
442 }
443
444 restore_error_handler();
445
446 if ($ex) {
447 /** @var \RuntimeException $ex */
448 throw $ex;
449 }
450
451 return $contents;
452 }
453
454 /**
455 * Returns a UriInterface for the given value.
456 *
457 * This function accepts a string or UriInterface and returns a
458 * UriInterface for the given value. If the value is already a
459 * UriInterface, it is returned as-is.
460 *
461 * @param string|UriInterface $uri
462 *
463 * @throws \InvalidArgumentException
464 */
465 public static function uriFor($uri): UriInterface
466 {
467 if ($uri instanceof UriInterface) {
468 return $uri;
469 }
470
471 if (is_string($uri)) {
472 return new Uri($uri);
473 }
474
475 throw new \InvalidArgumentException('URI must be a string or UriInterface');
476 }
477 }
478