source: vendor/guzzlehttp/psr7/src/MultipartStream.php@ e3d4e0a

Last change on this file since e3d4e0a was e3d4e0a, checked in by Vlado 222039 <vlado.popovski@…>, 7 days ago

Upload project files

  • Property mode set to 100644
File size: 5.1 KB
RevLine 
[e3d4e0a]1<?php
2
3declare(strict_types=1);
4
5namespace GuzzleHttp\Psr7;
6
7use Psr\Http\Message\StreamInterface;
8
9/**
10 * Stream that when read returns bytes for a streaming multipart or
11 * multipart/form-data stream.
12 */
13final 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 a
27 * StreamInterface/resource/string, an optional
28 * "headers" associative array of custom headers,
29 * and an optional "filename" key mapping to a
30 * string to send as the filename in the part.
31 * @param string $boundary You can optionally provide a specific boundary
32 *
33 * @throws \InvalidArgumentException
34 */
35 public function __construct(array $elements = [], ?string $boundary = null)
36 {
37 $this->boundary = $boundary ?: bin2hex(random_bytes(20));
38 $this->stream = $this->createStream($elements);
39 }
40
41 public function getBoundary(): string
42 {
43 return $this->boundary;
44 }
45
46 public function isWritable(): bool
47 {
48 return false;
49 }
50
51 /**
52 * Get the headers needed before transferring the content of a POST file
53 *
54 * @param string[] $headers
55 */
56 private function getHeaders(array $headers): string
57 {
58 $str = '';
59 foreach ($headers as $key => $value) {
60 $str .= "{$key}: {$value}\r\n";
61 }
62
63 return "--{$this->boundary}\r\n".trim($str)."\r\n\r\n";
64 }
65
66 /**
67 * Create the aggregate stream that will be used to upload the POST data
68 */
69 protected function createStream(array $elements = []): StreamInterface
70 {
71 $stream = new AppendStream();
72
73 foreach ($elements as $element) {
74 if (!is_array($element)) {
75 throw new \UnexpectedValueException('An array is expected');
76 }
77 $this->addElement($stream, $element);
78 }
79
80 // Add the trailing boundary with CRLF
81 $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n"));
82
83 return $stream;
84 }
85
86 private function addElement(AppendStream $stream, array $element): void
87 {
88 foreach (['contents', 'name'] as $key) {
89 if (!array_key_exists($key, $element)) {
90 throw new \InvalidArgumentException("A '{$key}' key is required");
91 }
92 }
93
94 $element['contents'] = Utils::streamFor($element['contents']);
95
96 if (empty($element['filename'])) {
97 $uri = $element['contents']->getMetadata('uri');
98 if ($uri && \is_string($uri) && \substr($uri, 0, 6) !== 'php://' && \substr($uri, 0, 7) !== 'data://') {
99 $element['filename'] = $uri;
100 }
101 }
102
103 [$body, $headers] = $this->createElement(
104 $element['name'],
105 $element['contents'],
106 $element['filename'] ?? null,
107 $element['headers'] ?? []
108 );
109
110 $stream->addStream(Utils::streamFor($this->getHeaders($headers)));
111 $stream->addStream($body);
112 $stream->addStream(Utils::streamFor("\r\n"));
113 }
114
115 /**
116 * @param string[] $headers
117 *
118 * @return array{0: StreamInterface, 1: string[]}
119 */
120 private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array
121 {
122 // Set a default content-disposition header if one was no provided
123 $disposition = self::getHeader($headers, 'content-disposition');
124 if (!$disposition) {
125 $headers['Content-Disposition'] = ($filename === '0' || $filename)
126 ? sprintf(
127 'form-data; name="%s"; filename="%s"',
128 $name,
129 basename($filename)
130 )
131 : "form-data; name=\"{$name}\"";
132 }
133
134 // Set a default content-length header if one was no provided
135 $length = self::getHeader($headers, 'content-length');
136 if (!$length) {
137 if ($length = $stream->getSize()) {
138 $headers['Content-Length'] = (string) $length;
139 }
140 }
141
142 // Set a default Content-Type if one was not supplied
143 $type = self::getHeader($headers, 'content-type');
144 if (!$type && ($filename === '0' || $filename)) {
145 $headers['Content-Type'] = MimeType::fromFilename($filename) ?? 'application/octet-stream';
146 }
147
148 return [$stream, $headers];
149 }
150
151 /**
152 * @param string[] $headers
153 */
154 private static function getHeader(array $headers, string $key): ?string
155 {
156 $lowercaseHeader = strtolower($key);
157 foreach ($headers as $k => $v) {
158 if (strtolower((string) $k) === $lowercaseHeader) {
159 return $v;
160 }
161 }
162
163 return null;
164 }
165}
Note: See TracBrowser for help on using the repository browser.