PluginProbe
BerqWP – All-In-One Optimization for Core Web Vitals, Cache, CDN, Images, CSS & JavaScript / 4.1.13
BerqWP – All-In-One Optimization for Core Web Vitals, Cache, CDN, Images, CSS & JavaScript v4.1.13
4.1.20 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.14 4.1.13 4.1.12 4.1.11 4.1.10 4.0.30 4.0.29 4.0.28 4.0.27 4.0.26 4.0.24 4.0.25 4.0.23 4.0.22 4.0.21 4.0.19 4.0.18 4.0.17 4.0.16 All 174 releases
searchpro / BerqWP / vendor / guzzlehttp / psr7 / src / MultipartStream.php

MultipartStream.php in BerqWP – All-In-One Optimization for Core Web Vitals, Cache, CDN, Images, CSS & JavaScript 4.1.13, at BerqWP/vendor/guzzlehttp/psr7/src/MultipartStream.php

207 lines 6.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
5 namespace GuzzleHttp\Psr7;
6
7 use Psr\Http\Message\StreamInterface;
8
9 /**
10 * Stream that when read returns bytes for a streaming multipart or
11 * multipart/form-data stream.
12 */
13 final class MultipartStream implements StreamInterface
14 {
15 use StreamDecoratorTrait;
16
17 /** @var string */
18 private $boundary;
19
20 /** @var StreamInterface */
21 private $stream;
22
23 /**
24 * @param array $elements Array of associative arrays, each containing a
25 * required "name" key mapping to the form field,
26 * name, a required "contents" key mapping to any
27 * value accepted by Utils::streamFor() (scalar,
28 * null, resource, StreamInterface, Iterator, or
29 * callable), or an array for nested expansion.
30 * Optional keys include "headers" (associative
31 * array of custom headers) and "filename" (string
32 * to send as the filename in the part).
33 * When "contents" is an array, it is recursively
34 * expanded into multiple fields using bracket notation
35 * (e.g., name[0][key]). Empty arrays produce no fields.
36 * The "filename" and "headers" options cannot be used
37 * with array contents.
38 * @param string $boundary You can optionally provide a specific boundary
39 *
40 * @throws \InvalidArgumentException
41 */
42 public function __construct(array $elements = [], ?string $boundary = null)
43 {
44 $this->boundary = $boundary ?: bin2hex(random_bytes(20));
45 $this->stream = $this->createStream($elements);
46 }
47
48 public function getBoundary(): string
49 {
50 return $this->boundary;
51 }
52
53 public function isWritable(): bool
54 {
55 return false;
56 }
57
58 /**
59 * Get the headers needed before transferring the content of a POST file
60 *
61 * @param string[] $headers
62 */
63 private function getHeaders(array $headers): string
64 {
65 $str = '';
66 foreach ($headers as $key => $value) {
67 $str .= "{$key}: {$value}\r\n";
68 }
69
70 return "--{$this->boundary}\r\n".trim($str)."\r\n\r\n";
71 }
72
73 /**
74 * Create the aggregate stream that will be used to upload the POST data
75 */
76 protected function createStream(array $elements = []): StreamInterface
77 {
78 $stream = new AppendStream();
79
80 foreach ($elements as $element) {
81 if (!is_array($element)) {
82 throw new \UnexpectedValueException('An array is expected');
83 }
84 $this->addElement($stream, $element);
85 }
86
87 // Add the trailing boundary with CRLF
88 $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n"));
89
90 return $stream;
91 }
92
93 private function addElement(AppendStream $stream, array $element): void
94 {
95 foreach (['contents', 'name'] as $key) {
96 if (!array_key_exists($key, $element)) {
97 throw new \InvalidArgumentException("A '{$key}' key is required");
98 }
99 }
100
101 if (!is_string($element['name']) && !is_int($element['name'])) {
102 throw new \InvalidArgumentException("The 'name' key must be a string or integer");
103 }
104
105 if (is_array($element['contents'])) {
106 if (array_key_exists('filename', $element) || array_key_exists('headers', $element)) {
107 throw new \InvalidArgumentException(
108 "The 'filename' and 'headers' options cannot be used when 'contents' is an array"
109 );
110 }
111
112 $this->addNestedElements($stream, $element['contents'], (string) $element['name']);
113
114 return;
115 }
116
117 $element['contents'] = Utils::streamFor($element['contents']);
118
119 if (empty($element['filename'])) {
120 $uri = $element['contents']->getMetadata('uri');
121 if ($uri && \is_string($uri) && \substr($uri, 0, 6) !== 'php://' && \substr($uri, 0, 7) !== 'data://') {
122 $element['filename'] = $uri;
123 }
124 }
125
126 [$body, $headers] = $this->createElement(
127 (string) $element['name'],
128 $element['contents'],
129 $element['filename'] ?? null,
130 $element['headers'] ?? []
131 );
132
133 $stream->addStream(Utils::streamFor($this->getHeaders($headers)));
134 $stream->addStream($body);
135 $stream->addStream(Utils::streamFor("\r\n"));
136 }
137
138 /**
139 * Recursively expand array contents into multiple form fields.
140 *
141 * @param array<array-key, mixed> $contents
142 */
143 private function addNestedElements(AppendStream $stream, array $contents, string $root): void
144 {
145 foreach ($contents as $key => $value) {
146 $fieldName = $root === '' ? sprintf('[%s]', (string) $key) : sprintf('%s[%s]', $root, (string) $key);
147
148 if (is_array($value)) {
149 $this->addNestedElements($stream, $value, $fieldName);
150 } else {
151 $this->addElement($stream, ['name' => $fieldName, 'contents' => $value]);
152 }
153 }
154 }
155
156 /**
157 * @param string[] $headers
158 *
159 * @return array{0: StreamInterface, 1: string[]}
160 */
161 private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array
162 {
163 // Set a default content-disposition header if one was no provided
164 $disposition = self::getHeader($headers, 'content-disposition');
165 if (!$disposition) {
166 $headers['Content-Disposition'] = ($filename === '0' || $filename)
167 ? sprintf(
168 'form-data; name="%s"; filename="%s"',
169 $name,
170 basename($filename)
171 )
172 : "form-data; name=\"{$name}\"";
173 }
174
175 // Set a default content-length header if one was no provided
176 $length = self::getHeader($headers, 'content-length');
177 if (!$length) {
178 if ($length = $stream->getSize()) {
179 $headers['Content-Length'] = (string) $length;
180 }
181 }
182
183 // Set a default Content-Type if one was not supplied
184 $type = self::getHeader($headers, 'content-type');
185 if (!$type && ($filename === '0' || $filename)) {
186 $headers['Content-Type'] = MimeType::fromFilename($filename) ?? 'application/octet-stream';
187 }
188
189 return [$stream, $headers];
190 }
191
192 /**
193 * @param string[] $headers
194 */
195 private static function getHeader(array $headers, string $key): ?string
196 {
197 $lowercaseHeader = strtolower($key);
198 foreach ($headers as $k => $v) {
199 if (strtolower((string) $k) === $lowercaseHeader) {
200 return $v;
201 }
202 }
203
204 return null;
205 }
206 }
207