← All changes
|
includes/sdk/google/guzzlehttp/psr7/src/MultipartStream.php
+55
-22
1.1.1
→
1.4.2
View file →
| @@ -1,10 +1,10 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | declare (strict_types=1); |
| 4 | -namespace Dudlewebs\WPMCS\GuzzleHttp\Psr7; | |
| 4 | +namespace Dudlewebs\WPMCS\GCP\GuzzleHttp\Psr7; | |
| 5 | 5 | |
| 6 | -use Dudlewebs\WPMCS\Psr\Http\Message\StreamInterface; | |
| 6 | +use Dudlewebs\WPMCS\GCP\Psr\Http\Message\StreamInterface; | |
| 7 | 7 | /** |
| 8 | 8 | * Stream that when read returns bytes for a streaming multipart or |
| 9 | 9 | * multipart/form-data stream. |
| 10 | 10 | */ |
| @@ -17,13 +17,20 @@ | ||
| 17 | 17 | private $stream; |
| 18 | 18 | /** |
| 19 | 19 | * @param array $elements Array of associative arrays, each containing a |
| 20 | 20 | * required "name" key mapping to the form field, |
| 21 | - * name, a required "contents" key mapping to a | |
| 22 | - * StreamInterface/resource/string, an optional | |
| 23 | - * "headers" associative array of custom headers, | |
| 24 | - * and an optional "filename" key mapping to a | |
| 25 | - * string to send as the filename in the part. | |
| 21 | + * name, a required "contents" key mapping to any | |
| 22 | + * value accepted by Utils::streamFor() (scalar, | |
| 23 | + * null, resource, StreamInterface, Iterator, or | |
| 24 | + * callable), or an array for nested expansion. | |
| 25 | + * Optional keys include "headers" (associative | |
| 26 | + * array of custom headers) and "filename" (string | |
| 27 | + * to send as the filename in the part). | |
| 28 | + * When "contents" is an array, it is recursively | |
| 29 | + * expanded into multiple fields using bracket notation | |
| 30 | + * (e.g., name[0][key]). Empty arrays produce no fields. | |
| 31 | + * The "filename" and "headers" options cannot be used | |
| 32 | + * with array contents. | |
| 26 | 33 | * @param string $boundary You can optionally provide a specific boundary |
| 27 | 34 | * |
| 28 | 35 | * @throws \InvalidArgumentException |
| 29 | 36 | */ |
| @@ -28,16 +35,16 @@ | ||
| 28 | 35 | * @throws \InvalidArgumentException |
| 29 | 36 | */ |
| 30 | 37 | public function __construct(array $elements = [], ?string $boundary = null) |
| 31 | 38 | { |
| 32 | - $this->boundary = $boundary ?: bin2hex(random_bytes(20)); | |
| 39 | + $this->boundary = $boundary ?: \bin2hex(\random_bytes(20)); | |
| 33 | 40 | $this->stream = $this->createStream($elements); |
| 34 | 41 | } |
| 35 | - public function getBoundary(): string | |
| 42 | + public function getBoundary() : string | |
| 36 | 43 | { |
| 37 | 44 | return $this->boundary; |
| 38 | 45 | } |
| 39 | - public function isWritable(): bool | |
| 46 | + public function isWritable() : bool | |
| 40 | 47 | { |
| 41 | 48 | return \false; |
| 42 | 49 | } |
| 43 | 50 | /** |
| @@ -44,24 +51,24 @@ | ||
| 44 | 51 | * Get the headers needed before transferring the content of a POST file |
| 45 | 52 | * |
| 46 | 53 | * @param string[] $headers |
| 47 | 54 | */ |
| 48 | - private function getHeaders(array $headers): string | |
| 55 | + private function getHeaders(array $headers) : string | |
| 49 | 56 | { |
| 50 | 57 | $str = ''; |
| 51 | 58 | foreach ($headers as $key => $value) { |
| 52 | 59 | $str .= "{$key}: {$value}\r\n"; |
| 53 | 60 | } |
| 54 | - return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n"; | |
| 61 | + return "--{$this->boundary}\r\n" . \trim($str) . "\r\n\r\n"; | |
| 55 | 62 | } |
| 56 | 63 | /** |
| 57 | 64 | * Create the aggregate stream that will be used to upload the POST data |
| 58 | 65 | */ |
| 59 | - protected function createStream(array $elements = []): StreamInterface | |
| 66 | + protected function createStream(array $elements = []) : StreamInterface | |
| 60 | 67 | { |
| 61 | 68 | $stream = new AppendStream(); |
| 62 | 69 | foreach ($elements as $element) { |
| 63 | - if (!is_array($element)) { | |
| 70 | + if (!\is_array($element)) { | |
| 64 | 71 | throw new \UnexpectedValueException('An array is expected'); |
| 65 | 72 | } |
| 66 | 73 | $this->addElement($stream, $element); |
| 67 | 74 | } |
| @@ -68,15 +75,25 @@ | ||
| 68 | 75 | // Add the trailing boundary with CRLF |
| 69 | 76 | $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n")); |
| 70 | 77 | return $stream; |
| 71 | 78 | } |
| 72 | - private function addElement(AppendStream $stream, array $element): void | |
| 79 | + private function addElement(AppendStream $stream, array $element) : void | |
| 73 | 80 | { |
| 74 | 81 | foreach (['contents', 'name'] as $key) { |
| 75 | - if (!array_key_exists($key, $element)) { | |
| 82 | + if (!\array_key_exists($key, $element)) { | |
| 76 | 83 | throw new \InvalidArgumentException("A '{$key}' key is required"); |
| 77 | 84 | } |
| 78 | 85 | } |
| 86 | + if (!\is_string($element['name']) && !\is_int($element['name'])) { | |
| 87 | + throw new \InvalidArgumentException("The 'name' key must be a string or integer"); | |
| 88 | + } | |
| 89 | + if (\is_array($element['contents'])) { | |
| 90 | + if (\array_key_exists('filename', $element) || \array_key_exists('headers', $element)) { | |
| 91 | + throw new \InvalidArgumentException("The 'filename' and 'headers' options cannot be used when 'contents' is an array"); | |
| 92 | + } | |
| 93 | + $this->addNestedElements($stream, $element['contents'], (string) $element['name']); | |
| 94 | + return; | |
| 95 | + } | |
| 79 | 96 | $element['contents'] = Utils::streamFor($element['contents']); |
| 80 | 97 | if (empty($element['filename'])) { |
| 81 | 98 | $uri = $element['contents']->getMetadata('uri'); |
| 82 | 99 | if ($uri && \is_string($uri) && \substr($uri, 0, 6) !== 'php://' && \substr($uri, 0, 7) !== 'data://') { |
| @@ -82,24 +99,40 @@ | ||
| 82 | 99 | if ($uri && \is_string($uri) && \substr($uri, 0, 6) !== 'php://' && \substr($uri, 0, 7) !== 'data://') { |
| 83 | 100 | $element['filename'] = $uri; |
| 84 | 101 | } |
| 85 | 102 | } |
| 86 | - [$body, $headers] = $this->createElement($element['name'], $element['contents'], $element['filename'] ?? null, $element['headers'] ?? []); | |
| 103 | + [$body, $headers] = $this->createElement((string) $element['name'], $element['contents'], $element['filename'] ?? null, $element['headers'] ?? []); | |
| 87 | 104 | $stream->addStream(Utils::streamFor($this->getHeaders($headers))); |
| 88 | 105 | $stream->addStream($body); |
| 89 | 106 | $stream->addStream(Utils::streamFor("\r\n")); |
| 90 | 107 | } |
| 91 | 108 | /** |
| 109 | + * Recursively expand array contents into multiple form fields. | |
| 110 | + * | |
| 111 | + * @param array<array-key, mixed> $contents | |
| 112 | + */ | |
| 113 | + private function addNestedElements(AppendStream $stream, array $contents, string $root) : void | |
| 114 | + { | |
| 115 | + foreach ($contents as $key => $value) { | |
| 116 | + $fieldName = $root === '' ? \sprintf('[%s]', (string) $key) : \sprintf('%s[%s]', $root, (string) $key); | |
| 117 | + if (\is_array($value)) { | |
| 118 | + $this->addNestedElements($stream, $value, $fieldName); | |
| 119 | + } else { | |
| 120 | + $this->addElement($stream, ['name' => $fieldName, 'contents' => $value]); | |
| 121 | + } | |
| 122 | + } | |
| 123 | + } | |
| 124 | + /** | |
| 92 | 125 | * @param string[] $headers |
| 93 | 126 | * |
| 94 | 127 | * @return array{0: StreamInterface, 1: string[]} |
| 95 | 128 | */ |
| 96 | - private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array | |
| 129 | + private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers) : array | |
| 97 | 130 | { |
| 98 | 131 | // Set a default content-disposition header if one was no provided |
| 99 | 132 | $disposition = self::getHeader($headers, 'content-disposition'); |
| 100 | 133 | if (!$disposition) { |
| 101 | - $headers['Content-Disposition'] = $filename === '0' || $filename ? sprintf('form-data; name="%s"; filename="%s"', $name, basename($filename)) : "form-data; name=\"{$name}\""; | |
| 134 | + $headers['Content-Disposition'] = $filename === '0' || $filename ? \sprintf('form-data; name="%s"; filename="%s"', $name, \basename($filename)) : "form-data; name=\"{$name}\""; | |
| 102 | 135 | } |
| 103 | 136 | // Set a default content-length header if one was no provided |
| 104 | 137 | $length = self::getHeader($headers, 'content-length'); |
| 105 | 138 | if (!$length) { |
| @@ -116,13 +149,13 @@ | ||
| 116 | 149 | } |
| 117 | 150 | /** |
| 118 | 151 | * @param string[] $headers |
| 119 | 152 | */ |
| 120 | - private static function getHeader(array $headers, string $key): ?string | |
| 153 | + private static function getHeader(array $headers, string $key) : ?string | |
| 121 | 154 | { |
| 122 | - $lowercaseHeader = strtolower($key); | |
| 155 | + $lowercaseHeader = \strtolower($key); | |
| 123 | 156 | foreach ($headers as $k => $v) { |
| 124 | - if (strtolower((string) $k) === $lowercaseHeader) { | |
| 157 | + if (\strtolower((string) $k) === $lowercaseHeader) { | |
| 125 | 158 | return $v; |
| 126 | 159 | } |
| 127 | 160 | } |
| 128 | 161 | return null; |