File indexing completed on 2024-12-29 05:27:23
0001 <?php 0002 namespace GuzzleHttp\Psr7; 0003 0004 use Psr\Http\Message\StreamInterface; 0005 0006 /** 0007 * Stream that when read returns bytes for a streaming multipart or 0008 * multipart/form-data stream. 0009 */ 0010 class MultipartStream implements StreamInterface 0011 { 0012 use StreamDecoratorTrait; 0013 0014 private $boundary; 0015 0016 /** 0017 * @param array $elements Array of associative arrays, each containing a 0018 * required "name" key mapping to the form field, 0019 * name, a required "contents" key mapping to a 0020 * StreamInterface/resource/string, an optional 0021 * "headers" associative array of custom headers, 0022 * and an optional "filename" key mapping to a 0023 * string to send as the filename in the part. 0024 * @param string $boundary You can optionally provide a specific boundary 0025 * 0026 * @throws \InvalidArgumentException 0027 */ 0028 public function __construct(array $elements = [], $boundary = null) 0029 { 0030 $this->boundary = $boundary ?: sha1(uniqid('', true)); 0031 $this->stream = $this->createStream($elements); 0032 } 0033 0034 /** 0035 * Get the boundary 0036 * 0037 * @return string 0038 */ 0039 public function getBoundary() 0040 { 0041 return $this->boundary; 0042 } 0043 0044 public function isWritable() 0045 { 0046 return false; 0047 } 0048 0049 /** 0050 * Get the headers needed before transferring the content of a POST file 0051 */ 0052 private function getHeaders(array $headers) 0053 { 0054 $str = ''; 0055 foreach ($headers as $key => $value) { 0056 $str .= "{$key}: {$value}\r\n"; 0057 } 0058 0059 return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n"; 0060 } 0061 0062 /** 0063 * Create the aggregate stream that will be used to upload the POST data 0064 */ 0065 protected function createStream(array $elements) 0066 { 0067 $stream = new AppendStream(); 0068 0069 foreach ($elements as $element) { 0070 $this->addElement($stream, $element); 0071 } 0072 0073 // Add the trailing boundary with CRLF 0074 $stream->addStream(stream_for("--{$this->boundary}--\r\n")); 0075 0076 return $stream; 0077 } 0078 0079 private function addElement(AppendStream $stream, array $element) 0080 { 0081 foreach (['contents', 'name'] as $key) { 0082 if (!array_key_exists($key, $element)) { 0083 throw new \InvalidArgumentException("A '{$key}' key is required"); 0084 } 0085 } 0086 0087 $element['contents'] = stream_for($element['contents']); 0088 0089 if (empty($element['filename'])) { 0090 $uri = $element['contents']->getMetadata('uri'); 0091 if (substr($uri, 0, 6) !== 'php://') { 0092 $element['filename'] = $uri; 0093 } 0094 } 0095 0096 list($body, $headers) = $this->createElement( 0097 $element['name'], 0098 $element['contents'], 0099 isset($element['filename']) ? $element['filename'] : null, 0100 isset($element['headers']) ? $element['headers'] : [] 0101 ); 0102 0103 $stream->addStream(stream_for($this->getHeaders($headers))); 0104 $stream->addStream($body); 0105 $stream->addStream(stream_for("\r\n")); 0106 } 0107 0108 /** 0109 * @return array 0110 */ 0111 private function createElement($name, StreamInterface $stream, $filename, array $headers) 0112 { 0113 // Set a default content-disposition header if one was no provided 0114 $disposition = $this->getHeader($headers, 'content-disposition'); 0115 if (!$disposition) { 0116 $headers['Content-Disposition'] = ($filename === '0' || $filename) 0117 ? sprintf('form-data; name="%s"; filename="%s"', 0118 $name, 0119 basename($filename)) 0120 : "form-data; name=\"{$name}\""; 0121 } 0122 0123 // Set a default content-length header if one was no provided 0124 $length = $this->getHeader($headers, 'content-length'); 0125 if (!$length) { 0126 if ($length = $stream->getSize()) { 0127 $headers['Content-Length'] = (string) $length; 0128 } 0129 } 0130 0131 // Set a default Content-Type if one was not supplied 0132 $type = $this->getHeader($headers, 'content-type'); 0133 if (!$type && ($filename === '0' || $filename)) { 0134 if ($type = mimetype_from_filename($filename)) { 0135 $headers['Content-Type'] = $type; 0136 } 0137 } 0138 0139 return [$stream, $headers]; 0140 } 0141 0142 private function getHeader(array $headers, $key) 0143 { 0144 $lowercaseHeader = strtolower($key); 0145 foreach ($headers as $k => $v) { 0146 if (strtolower($k) === $lowercaseHeader) { 0147 return $v; 0148 } 0149 } 0150 0151 return null; 0152 } 0153 }