mantis-matrix-integration – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 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 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 }